fix: Lint fixes of utils tests
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -15,11 +15,18 @@ from app.utils.audio import (
|
|||||||
get_sound_file_path,
|
get_sound_file_path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Constants
|
||||||
|
SHA256_HASH_LENGTH = 64
|
||||||
|
BINARY_FILE_SIZE = 700
|
||||||
|
EXPECTED_DURATION_MS_1 = 123456 # 123.456 seconds * 1000
|
||||||
|
EXPECTED_DURATION_MS_2 = 60000 # 60 seconds * 1000
|
||||||
|
EXPECTED_DURATION_MS_3 = 45123 # 45.123 seconds * 1000
|
||||||
|
|
||||||
|
|
||||||
class TestAudioUtils:
|
class TestAudioUtils:
|
||||||
"""Test audio utility functions."""
|
"""Test audio utility functions."""
|
||||||
|
|
||||||
def test_get_file_hash(self):
|
def test_get_file_hash(self) -> None:
|
||||||
"""Test file hash calculation."""
|
"""Test file hash calculation."""
|
||||||
# Create a temporary file with known content
|
# Create a temporary file with known content
|
||||||
test_content = "test content for hashing"
|
test_content = "test content for hashing"
|
||||||
@@ -36,13 +43,13 @@ class TestAudioUtils:
|
|||||||
|
|
||||||
# Verify the hash is correct
|
# Verify the hash is correct
|
||||||
assert result_hash == expected_hash
|
assert result_hash == expected_hash
|
||||||
assert len(result_hash) == 64 # SHA-256 hash length
|
assert len(result_hash) == SHA256_HASH_LENGTH # SHA-256 hash length
|
||||||
assert isinstance(result_hash, str)
|
assert isinstance(result_hash, str)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_hash_binary_content(self):
|
def test_get_file_hash_binary_content(self) -> None:
|
||||||
"""Test file hash calculation with binary content."""
|
"""Test file hash calculation with binary content."""
|
||||||
# Create a temporary file with binary content
|
# Create a temporary file with binary content
|
||||||
test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd"
|
test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd"
|
||||||
@@ -59,13 +66,13 @@ class TestAudioUtils:
|
|||||||
|
|
||||||
# Verify the hash is correct
|
# Verify the hash is correct
|
||||||
assert result_hash == expected_hash
|
assert result_hash == expected_hash
|
||||||
assert len(result_hash) == 64 # SHA-256 hash length
|
assert len(result_hash) == SHA256_HASH_LENGTH # SHA-256 hash length
|
||||||
assert isinstance(result_hash, str)
|
assert isinstance(result_hash, str)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_hash_empty_file(self):
|
def test_get_file_hash_empty_file(self) -> None:
|
||||||
"""Test file hash calculation for empty file."""
|
"""Test file hash calculation for empty file."""
|
||||||
# Create an empty temporary file
|
# Create an empty temporary file
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||||
@@ -80,13 +87,13 @@ class TestAudioUtils:
|
|||||||
|
|
||||||
# Verify the hash is correct
|
# Verify the hash is correct
|
||||||
assert result_hash == expected_hash
|
assert result_hash == expected_hash
|
||||||
assert len(result_hash) == 64 # SHA-256 hash length
|
assert len(result_hash) == SHA256_HASH_LENGTH # SHA-256 hash length
|
||||||
assert isinstance(result_hash, str)
|
assert isinstance(result_hash, str)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_hash_large_file(self):
|
def test_get_file_hash_large_file(self) -> None:
|
||||||
"""Test file hash calculation for large file (tests chunked reading)."""
|
"""Test file hash calculation for large file (tests chunked reading)."""
|
||||||
# Create a large temporary file (larger than 4096 bytes chunk size)
|
# Create a large temporary file (larger than 4096 bytes chunk size)
|
||||||
test_content = "A" * 10000 # 10KB of 'A' characters
|
test_content = "A" * 10000 # 10KB of 'A' characters
|
||||||
@@ -103,13 +110,13 @@ class TestAudioUtils:
|
|||||||
|
|
||||||
# Verify the hash is correct
|
# Verify the hash is correct
|
||||||
assert result_hash == expected_hash
|
assert result_hash == expected_hash
|
||||||
assert len(result_hash) == 64 # SHA-256 hash length
|
assert len(result_hash) == SHA256_HASH_LENGTH # SHA-256 hash length
|
||||||
assert isinstance(result_hash, str)
|
assert isinstance(result_hash, str)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_size(self):
|
def test_get_file_size(self) -> None:
|
||||||
"""Test file size calculation."""
|
"""Test file size calculation."""
|
||||||
# Create a temporary file with known content
|
# Create a temporary file with known content
|
||||||
test_content = "test content for size calculation"
|
test_content = "test content for size calculation"
|
||||||
@@ -132,7 +139,7 @@ class TestAudioUtils:
|
|||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_size_empty_file(self):
|
def test_get_file_size_empty_file(self) -> None:
|
||||||
"""Test file size calculation for empty file."""
|
"""Test file size calculation for empty file."""
|
||||||
# Create an empty temporary file
|
# Create an empty temporary file
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||||
@@ -149,7 +156,7 @@ class TestAudioUtils:
|
|||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
def test_get_file_size_binary_file(self):
|
def test_get_file_size_binary_file(self) -> None:
|
||||||
"""Test file size calculation for binary file."""
|
"""Test file size calculation for binary file."""
|
||||||
# Create a temporary file with binary content
|
# Create a temporary file with binary content
|
||||||
test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd" * 100 # 700 bytes
|
test_bytes = b"\x00\x01\x02\x03\xff\xfe\xfd" * 100 # 700 bytes
|
||||||
@@ -163,14 +170,14 @@ class TestAudioUtils:
|
|||||||
|
|
||||||
# Verify the size is correct
|
# Verify the size is correct
|
||||||
assert result_size == len(test_bytes)
|
assert result_size == len(test_bytes)
|
||||||
assert result_size == 700
|
assert result_size == BINARY_FILE_SIZE
|
||||||
assert isinstance(result_size, int)
|
assert isinstance(result_size, int)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_success(self, mock_probe):
|
def test_get_audio_duration_success(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test successful audio duration extraction."""
|
"""Test successful audio duration extraction."""
|
||||||
# Mock ffmpeg.probe to return duration
|
# Mock ffmpeg.probe to return duration
|
||||||
mock_probe.return_value = {"format": {"duration": "123.456"}}
|
mock_probe.return_value = {"format": {"duration": "123.456"}}
|
||||||
@@ -179,12 +186,12 @@ class TestAudioUtils:
|
|||||||
duration = get_audio_duration(temp_path)
|
duration = get_audio_duration(temp_path)
|
||||||
|
|
||||||
# Verify duration is converted correctly (seconds to milliseconds)
|
# Verify duration is converted correctly (seconds to milliseconds)
|
||||||
assert duration == 123456 # 123.456 seconds * 1000 = 123456 ms
|
assert duration == EXPECTED_DURATION_MS_1 # 123.456 seconds * 1000 = 123456 ms
|
||||||
assert isinstance(duration, int)
|
assert isinstance(duration, int)
|
||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_integer_duration(self, mock_probe):
|
def test_get_audio_duration_integer_duration(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction with integer duration."""
|
"""Test audio duration extraction with integer duration."""
|
||||||
# Mock ffmpeg.probe to return integer duration
|
# Mock ffmpeg.probe to return integer duration
|
||||||
mock_probe.return_value = {"format": {"duration": "60"}}
|
mock_probe.return_value = {"format": {"duration": "60"}}
|
||||||
@@ -193,12 +200,12 @@ class TestAudioUtils:
|
|||||||
duration = get_audio_duration(temp_path)
|
duration = get_audio_duration(temp_path)
|
||||||
|
|
||||||
# Verify duration is converted correctly
|
# Verify duration is converted correctly
|
||||||
assert duration == 60000 # 60 seconds * 1000 = 60000 ms
|
assert duration == EXPECTED_DURATION_MS_2 # 60 seconds * 1000 = 60000 ms
|
||||||
assert isinstance(duration, int)
|
assert isinstance(duration, int)
|
||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_zero_duration(self, mock_probe):
|
def test_get_audio_duration_zero_duration(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction with zero duration."""
|
"""Test audio duration extraction with zero duration."""
|
||||||
# Mock ffmpeg.probe to return zero duration
|
# Mock ffmpeg.probe to return zero duration
|
||||||
mock_probe.return_value = {"format": {"duration": "0.0"}}
|
mock_probe.return_value = {"format": {"duration": "0.0"}}
|
||||||
@@ -212,7 +219,9 @@ class TestAudioUtils:
|
|||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_fractional_duration(self, mock_probe):
|
def test_get_audio_duration_fractional_duration(
|
||||||
|
self, mock_probe: MagicMock,
|
||||||
|
) -> None:
|
||||||
"""Test audio duration extraction with fractional seconds."""
|
"""Test audio duration extraction with fractional seconds."""
|
||||||
# Mock ffmpeg.probe to return fractional duration
|
# Mock ffmpeg.probe to return fractional duration
|
||||||
mock_probe.return_value = {"format": {"duration": "45.123"}}
|
mock_probe.return_value = {"format": {"duration": "45.123"}}
|
||||||
@@ -221,12 +230,12 @@ class TestAudioUtils:
|
|||||||
duration = get_audio_duration(temp_path)
|
duration = get_audio_duration(temp_path)
|
||||||
|
|
||||||
# Verify duration is converted and rounded correctly
|
# Verify duration is converted and rounded correctly
|
||||||
assert duration == 45123 # 45.123 seconds * 1000 = 45123 ms
|
assert duration == EXPECTED_DURATION_MS_3 # 45.123 seconds * 1000 = 45123 ms
|
||||||
assert isinstance(duration, int)
|
assert isinstance(duration, int)
|
||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_ffmpeg_error(self, mock_probe):
|
def test_get_audio_duration_ffmpeg_error(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction when ffmpeg fails."""
|
"""Test audio duration extraction when ffmpeg fails."""
|
||||||
# Mock ffmpeg.probe to raise an exception
|
# Mock ffmpeg.probe to raise an exception
|
||||||
mock_probe.side_effect = Exception("FFmpeg error: file not found")
|
mock_probe.side_effect = Exception("FFmpeg error: file not found")
|
||||||
@@ -240,7 +249,7 @@ class TestAudioUtils:
|
|||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_missing_format(self, mock_probe):
|
def test_get_audio_duration_missing_format(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction when format info is missing."""
|
"""Test audio duration extraction when format info is missing."""
|
||||||
# Mock ffmpeg.probe to return data without format info
|
# Mock ffmpeg.probe to return data without format info
|
||||||
mock_probe.return_value = {"streams": []}
|
mock_probe.return_value = {"streams": []}
|
||||||
@@ -254,7 +263,7 @@ class TestAudioUtils:
|
|||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_missing_duration(self, mock_probe):
|
def test_get_audio_duration_missing_duration(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction when duration is missing."""
|
"""Test audio duration extraction when duration is missing."""
|
||||||
# Mock ffmpeg.probe to return format without duration
|
# Mock ffmpeg.probe to return format without duration
|
||||||
mock_probe.return_value = {"format": {"size": "1024"}}
|
mock_probe.return_value = {"format": {"size": "1024"}}
|
||||||
@@ -268,7 +277,7 @@ class TestAudioUtils:
|
|||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
@patch("app.utils.audio.ffmpeg.probe")
|
@patch("app.utils.audio.ffmpeg.probe")
|
||||||
def test_get_audio_duration_invalid_duration(self, mock_probe):
|
def test_get_audio_duration_invalid_duration(self, mock_probe: MagicMock) -> None:
|
||||||
"""Test audio duration extraction with invalid duration value."""
|
"""Test audio duration extraction with invalid duration value."""
|
||||||
# Mock ffmpeg.probe to return invalid duration
|
# Mock ffmpeg.probe to return invalid duration
|
||||||
mock_probe.return_value = {"format": {"duration": "invalid"}}
|
mock_probe.return_value = {"format": {"duration": "invalid"}}
|
||||||
@@ -281,7 +290,7 @@ class TestAudioUtils:
|
|||||||
assert isinstance(duration, int)
|
assert isinstance(duration, int)
|
||||||
mock_probe.assert_called_once_with(str(temp_path))
|
mock_probe.assert_called_once_with(str(temp_path))
|
||||||
|
|
||||||
def test_get_file_hash_nonexistent_file(self):
|
def test_get_file_hash_nonexistent_file(self) -> None:
|
||||||
"""Test file hash calculation for nonexistent file."""
|
"""Test file hash calculation for nonexistent file."""
|
||||||
nonexistent_path = Path("/fake/nonexistent/file.mp3")
|
nonexistent_path = Path("/fake/nonexistent/file.mp3")
|
||||||
|
|
||||||
@@ -289,7 +298,7 @@ class TestAudioUtils:
|
|||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
get_file_hash(nonexistent_path)
|
get_file_hash(nonexistent_path)
|
||||||
|
|
||||||
def test_get_file_size_nonexistent_file(self):
|
def test_get_file_size_nonexistent_file(self) -> None:
|
||||||
"""Test file size calculation for nonexistent file."""
|
"""Test file size calculation for nonexistent file."""
|
||||||
nonexistent_path = Path("/fake/nonexistent/file.mp3")
|
nonexistent_path = Path("/fake/nonexistent/file.mp3")
|
||||||
|
|
||||||
@@ -297,7 +306,7 @@ class TestAudioUtils:
|
|||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
get_file_size(nonexistent_path)
|
get_file_size(nonexistent_path)
|
||||||
|
|
||||||
def test_get_sound_file_path_sdb_original(self):
|
def test_get_sound_file_path_sdb_original(self) -> None:
|
||||||
"""Test getting sound file path for SDB type original file."""
|
"""Test getting sound file path for SDB type original file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -311,7 +320,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/originals/soundboard/test.mp3")
|
expected = Path("sounds/originals/soundboard/test.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_sdb_normalized(self):
|
def test_get_sound_file_path_sdb_normalized(self) -> None:
|
||||||
"""Test getting sound file path for SDB type normalized file."""
|
"""Test getting sound file path for SDB type normalized file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=1,
|
id=1,
|
||||||
@@ -326,7 +335,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/normalized/soundboard/normalized.mp3")
|
expected = Path("sounds/normalized/soundboard/normalized.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_tts_original(self):
|
def test_get_sound_file_path_tts_original(self) -> None:
|
||||||
"""Test getting sound file path for TTS type original file."""
|
"""Test getting sound file path for TTS type original file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=2,
|
id=2,
|
||||||
@@ -340,7 +349,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/originals/text_to_speech/tts_file.wav")
|
expected = Path("sounds/originals/text_to_speech/tts_file.wav")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_tts_normalized(self):
|
def test_get_sound_file_path_tts_normalized(self) -> None:
|
||||||
"""Test getting sound file path for TTS type normalized file."""
|
"""Test getting sound file path for TTS type normalized file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=2,
|
id=2,
|
||||||
@@ -355,7 +364,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/normalized/text_to_speech/normalized.mp3")
|
expected = Path("sounds/normalized/text_to_speech/normalized.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_ext_original(self):
|
def test_get_sound_file_path_ext_original(self) -> None:
|
||||||
"""Test getting sound file path for EXT type original file."""
|
"""Test getting sound file path for EXT type original file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=3,
|
id=3,
|
||||||
@@ -369,7 +378,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/originals/extracted/extracted.mp3")
|
expected = Path("sounds/originals/extracted/extracted.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_ext_normalized(self):
|
def test_get_sound_file_path_ext_normalized(self) -> None:
|
||||||
"""Test getting sound file path for EXT type normalized file."""
|
"""Test getting sound file path for EXT type normalized file."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=3,
|
id=3,
|
||||||
@@ -384,7 +393,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/normalized/extracted/normalized.mp3")
|
expected = Path("sounds/normalized/extracted/normalized.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_unknown_type_fallback(self):
|
def test_get_sound_file_path_unknown_type_fallback(self) -> None:
|
||||||
"""Test getting sound file path for unknown type falls back to lowercase."""
|
"""Test getting sound file path for unknown type falls back to lowercase."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=4,
|
id=4,
|
||||||
@@ -398,7 +407,7 @@ class TestAudioUtils:
|
|||||||
expected = Path("sounds/originals/custom/unknown.mp3")
|
expected = Path("sounds/originals/custom/unknown.mp3")
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
def test_get_sound_file_path_normalized_without_filename(self):
|
def test_get_sound_file_path_normalized_without_filename(self) -> None:
|
||||||
"""Test getting sound file path when normalized but no normalized_filename."""
|
"""Test getting sound file path when normalized but no normalized_filename."""
|
||||||
sound = Sound(
|
sound = Sound(
|
||||||
id=5,
|
id=5,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Tests for cookie utilities."""
|
"""Tests for cookie utilities."""
|
||||||
|
# ruff: noqa: ANN201, E501
|
||||||
|
|
||||||
from app.utils.cookies import extract_access_token_from_cookies, parse_cookies
|
from app.utils.cookies import extract_access_token_from_cookies, parse_cookies
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
"""Tests for credit decorators."""
|
"""Tests for credit decorators."""
|
||||||
|
# ruff: noqa: ARG001, ANN001, E501, PT012
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
|
from typing import Never
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -17,7 +20,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
"""Test requires_credits decorator."""
|
"""Test requires_credits decorator."""
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_credit_service(self):
|
def mock_credit_service(self) -> AsyncMock:
|
||||||
"""Create a mock credit service."""
|
"""Create a mock credit service."""
|
||||||
service = AsyncMock(spec=CreditService)
|
service = AsyncMock(spec=CreditService)
|
||||||
service.validate_and_reserve_credits = AsyncMock()
|
service.validate_and_reserve_credits = AsyncMock()
|
||||||
@@ -25,12 +28,14 @@ class TestRequiresCreditsDecorator:
|
|||||||
return service
|
return service
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def credit_service_factory(self, mock_credit_service):
|
def credit_service_factory(self, mock_credit_service: AsyncMock) -> Callable[[], AsyncMock]:
|
||||||
"""Create a credit service factory."""
|
"""Create a credit service factory."""
|
||||||
return lambda: mock_credit_service
|
return lambda: mock_credit_service
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_success(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_success(
|
||||||
|
self, credit_service_factory: Callable[[], AsyncMock], mock_credit_service: AsyncMock,
|
||||||
|
) -> None:
|
||||||
"""Test decorator with successful action."""
|
"""Test decorator with successful action."""
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -52,10 +57,12 @@ class TestRequiresCreditsDecorator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_with_metadata(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_with_metadata(
|
||||||
|
self, credit_service_factory: Callable[[], AsyncMock], mock_credit_service: AsyncMock,
|
||||||
|
) -> None:
|
||||||
"""Test decorator with metadata extraction."""
|
"""Test decorator with metadata extraction."""
|
||||||
|
|
||||||
def extract_metadata(user_id: int, sound_name: str) -> dict:
|
def extract_metadata(user_id: int, sound_name: str) -> dict[str, str]:
|
||||||
return {"sound_name": sound_name}
|
return {"sound_name": sound_name}
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -77,7 +84,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_failed_action(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_failed_action(self, credit_service_factory, mock_credit_service) -> None:
|
||||||
"""Test decorator with failed action."""
|
"""Test decorator with failed action."""
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -96,7 +103,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_exception_in_action(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_exception_in_action(self, credit_service_factory, mock_credit_service) -> None:
|
||||||
"""Test decorator when action raises exception."""
|
"""Test decorator when action raises exception."""
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -105,7 +112,8 @@ class TestRequiresCreditsDecorator:
|
|||||||
user_id_param="user_id",
|
user_id_param="user_id",
|
||||||
)
|
)
|
||||||
async def test_action(user_id: int) -> str:
|
async def test_action(user_id: int) -> str:
|
||||||
raise ValueError("Test error")
|
msg = "Test error"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Test error"):
|
with pytest.raises(ValueError, match="Test error"):
|
||||||
await test_action(user_id=123)
|
await test_action(user_id=123)
|
||||||
@@ -115,7 +123,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_insufficient_credits(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_insufficient_credits(self, credit_service_factory, mock_credit_service) -> None:
|
||||||
"""Test decorator with insufficient credits."""
|
"""Test decorator with insufficient credits."""
|
||||||
mock_credit_service.validate_and_reserve_credits.side_effect = InsufficientCreditsError(1, 0)
|
mock_credit_service.validate_and_reserve_credits.side_effect = InsufficientCreditsError(1, 0)
|
||||||
|
|
||||||
@@ -134,7 +142,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
mock_credit_service.deduct_credits.assert_not_called()
|
mock_credit_service.deduct_credits.assert_not_called()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_user_id_in_args(self, credit_service_factory, mock_credit_service):
|
async def test_decorator_user_id_in_args(self, credit_service_factory, mock_credit_service) -> None:
|
||||||
"""Test decorator extracting user_id from positional args."""
|
"""Test decorator extracting user_id from positional args."""
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -153,7 +161,7 @@ class TestRequiresCreditsDecorator:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_decorator_missing_user_id(self, credit_service_factory):
|
async def test_decorator_missing_user_id(self, credit_service_factory) -> None:
|
||||||
"""Test decorator when user_id cannot be extracted."""
|
"""Test decorator when user_id cannot be extracted."""
|
||||||
|
|
||||||
@requires_credits(
|
@requires_credits(
|
||||||
@@ -172,19 +180,19 @@ class TestValidateCreditsOnlyDecorator:
|
|||||||
"""Test validate_credits_only decorator."""
|
"""Test validate_credits_only decorator."""
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_credit_service(self):
|
def mock_credit_service(self) -> AsyncMock:
|
||||||
"""Create a mock credit service."""
|
"""Create a mock credit service."""
|
||||||
service = AsyncMock(spec=CreditService)
|
service = AsyncMock(spec=CreditService)
|
||||||
service.validate_and_reserve_credits = AsyncMock()
|
service.validate_and_reserve_credits = AsyncMock()
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def credit_service_factory(self, mock_credit_service):
|
def credit_service_factory(self, mock_credit_service: AsyncMock) -> Callable[[], AsyncMock]:
|
||||||
"""Create a credit service factory."""
|
"""Create a credit service factory."""
|
||||||
return lambda: mock_credit_service
|
return lambda: mock_credit_service
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_only_decorator(self, credit_service_factory, mock_credit_service):
|
async def test_validate_only_decorator(self, credit_service_factory, mock_credit_service) -> None:
|
||||||
"""Test validate_credits_only decorator."""
|
"""Test validate_credits_only decorator."""
|
||||||
|
|
||||||
@validate_credits_only(
|
@validate_credits_only(
|
||||||
@@ -209,7 +217,7 @@ class TestCreditManager:
|
|||||||
"""Test CreditManager context manager."""
|
"""Test CreditManager context manager."""
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_credit_service(self):
|
def mock_credit_service(self) -> AsyncMock:
|
||||||
"""Create a mock credit service."""
|
"""Create a mock credit service."""
|
||||||
service = AsyncMock(spec=CreditService)
|
service = AsyncMock(spec=CreditService)
|
||||||
service.validate_and_reserve_credits = AsyncMock()
|
service.validate_and_reserve_credits = AsyncMock()
|
||||||
@@ -217,7 +225,7 @@ class TestCreditManager:
|
|||||||
return service
|
return service
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_credit_manager_success(self, mock_credit_service):
|
async def test_credit_manager_success(self, mock_credit_service) -> None:
|
||||||
"""Test CreditManager with successful operation."""
|
"""Test CreditManager with successful operation."""
|
||||||
async with CreditManager(
|
async with CreditManager(
|
||||||
mock_credit_service,
|
mock_credit_service,
|
||||||
@@ -235,7 +243,7 @@ class TestCreditManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_credit_manager_failure(self, mock_credit_service):
|
async def test_credit_manager_failure(self, mock_credit_service) -> None:
|
||||||
"""Test CreditManager with failed operation."""
|
"""Test CreditManager with failed operation."""
|
||||||
async with CreditManager(
|
async with CreditManager(
|
||||||
mock_credit_service,
|
mock_credit_service,
|
||||||
@@ -250,7 +258,7 @@ class TestCreditManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_credit_manager_exception(self, mock_credit_service):
|
async def test_credit_manager_exception(self, mock_credit_service) -> Never:
|
||||||
"""Test CreditManager when exception occurs."""
|
"""Test CreditManager when exception occurs."""
|
||||||
with pytest.raises(ValueError, match="Test error"):
|
with pytest.raises(ValueError, match="Test error"):
|
||||||
async with CreditManager(
|
async with CreditManager(
|
||||||
@@ -258,14 +266,15 @@ class TestCreditManager:
|
|||||||
123,
|
123,
|
||||||
CreditActionType.VLC_PLAY_SOUND,
|
CreditActionType.VLC_PLAY_SOUND,
|
||||||
):
|
):
|
||||||
raise ValueError("Test error")
|
msg = "Test error"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
mock_credit_service.deduct_credits.assert_called_once_with(
|
mock_credit_service.deduct_credits.assert_called_once_with(
|
||||||
123, CreditActionType.VLC_PLAY_SOUND, success=False, metadata=None,
|
123, CreditActionType.VLC_PLAY_SOUND, success=False, metadata=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_credit_manager_validation_failure(self, mock_credit_service):
|
async def test_credit_manager_validation_failure(self, mock_credit_service) -> None:
|
||||||
"""Test CreditManager when validation fails."""
|
"""Test CreditManager when validation fails."""
|
||||||
mock_credit_service.validate_and_reserve_credits.side_effect = InsufficientCreditsError(1, 0)
|
mock_credit_service.validate_and_reserve_credits.side_effect = InsufficientCreditsError(1, 0)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user