fix: Lint fixes of core and repositories tests
All checks were successful
Backend CI / lint (push) Successful in 9m26s
Backend CI / test (push) Successful in 4m24s

This commit is contained in:
JSC
2025-08-01 09:17:20 +02:00
parent 389cfe2d6a
commit dc29915fbc
8 changed files with 135 additions and 88 deletions

1
tests/core/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Tests for core module."""

View File

@@ -1,4 +1,5 @@
"""Tests for API token authentication dependencies."""
# ruff: noqa: S106
from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock
@@ -10,17 +11,20 @@ from app.core.dependencies import get_current_user_api_token, get_current_user_f
from app.models.user import User
from app.services.auth import AuthService
# Constants
HTTP_401_UNAUTHORIZED = 401
class TestApiTokenDependencies:
"""Test API token authentication dependencies."""
@pytest.fixture
def mock_auth_service(self):
def mock_auth_service(self) -> AsyncMock:
"""Create a mock auth service."""
return AsyncMock(spec=AuthService)
@pytest.fixture
def test_user(self):
def test_user(self) -> User:
"""Create a test user."""
return User(
id=1,
@@ -37,9 +41,9 @@ class TestApiTokenDependencies:
@pytest.mark.asyncio
async def test_get_current_user_api_token_success(
self,
mock_auth_service,
test_user,
):
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test successful API token authentication."""
mock_auth_service.get_user_by_api_token.return_value = test_user
@@ -53,38 +57,46 @@ class TestApiTokenDependencies:
)
@pytest.mark.asyncio
async def test_get_current_user_api_token_no_header(self, mock_auth_service):
async def test_get_current_user_api_token_no_header(
self, mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication without API-TOKEN header."""
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, None)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API-TOKEN header required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_empty_token(self, mock_auth_service):
async def test_get_current_user_api_token_empty_token(
self, mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with empty token."""
api_token_header = " "
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_whitespace_token(self, mock_auth_service):
async def test_get_current_user_api_token_whitespace_token(
self, mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with whitespace-only token."""
api_token_header = " "
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token required" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_invalid_token(self, mock_auth_service):
async def test_get_current_user_api_token_invalid_token(
self, mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with invalid token."""
mock_auth_service.get_user_by_api_token.return_value = None
@@ -93,15 +105,15 @@ class TestApiTokenDependencies:
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Invalid API token" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_expired_token(
self,
mock_auth_service,
test_user,
):
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token authentication with expired token."""
# Set expired token
test_user.api_token_expires_at = datetime.now(UTC) - timedelta(days=1)
@@ -112,15 +124,15 @@ class TestApiTokenDependencies:
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "API token has expired" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_inactive_user(
self,
mock_auth_service,
test_user,
):
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test API token authentication with inactive user."""
test_user.is_active = False
mock_auth_service.get_user_by_api_token.return_value = test_user
@@ -130,13 +142,13 @@ class TestApiTokenDependencies:
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Account is deactivated" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_api_token_service_exception(
self, mock_auth_service,
):
self, mock_auth_service: AsyncMock,
) -> None:
"""Test API token authentication with service exception."""
mock_auth_service.get_user_by_api_token.side_effect = Exception(
"Database error",
@@ -147,15 +159,15 @@ class TestApiTokenDependencies:
with pytest.raises(HTTPException) as exc_info:
await get_current_user_api_token(mock_auth_service, api_token_header)
assert exc_info.value.status_code == 401
assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Could not validate API token" in exc_info.value.detail
@pytest.mark.asyncio
async def test_get_current_user_flexible_uses_api_token(
self,
mock_auth_service,
test_user,
):
mock_auth_service: AsyncMock,
test_user: User,
) -> None:
"""Test flexible authentication uses API token when available."""
mock_auth_service.get_user_by_api_token.return_value = test_user
@@ -174,18 +186,20 @@ class TestApiTokenDependencies:
)
@pytest.mark.asyncio
async def test_get_current_user_flexible_falls_back_to_jwt(self, mock_auth_service):
async def test_get_current_user_flexible_falls_back_to_jwt(
self, mock_auth_service: AsyncMock,
) -> None:
"""Test flexible authentication falls back to JWT when no API token."""
# Mock the get_current_user function (normally imported)
with pytest.raises(Exception):
with pytest.raises(Exception, match="Database error|Could not validate"):
# This will fail because we can't easily mock the get_current_user import
# In a real test, you'd mock the import or use dependency injection
await get_current_user_flexible(mock_auth_service, "jwt_token", None)
@pytest.mark.asyncio
async def test_api_token_no_expiry_never_expires(
self, mock_auth_service, test_user,
):
self, mock_auth_service: AsyncMock, test_user: User,
) -> None:
"""Test API token with no expiry date never expires."""
test_user.api_token_expires_at = None
mock_auth_service.get_user_by_api_token.return_value = test_user
@@ -197,7 +211,9 @@ class TestApiTokenDependencies:
assert result == test_user
@pytest.mark.asyncio
async def test_api_token_with_whitespace(self, mock_auth_service, test_user):
async def test_api_token_with_whitespace(
self, mock_auth_service: AsyncMock, test_user: User,
) -> None:
"""Test API token with leading/trailing whitespace is handled correctly."""
mock_auth_service.get_user_by_api_token.return_value = test_user

View File

@@ -1,4 +1,5 @@
"""Tests for credit transaction repository."""
# ruff: noqa: ARG002, E501
import json
from collections.abc import AsyncGenerator
@@ -11,6 +12,18 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.credit_transaction import CreditTransaction
from app.models.user import User
from app.repositories.credit_transaction import CreditTransactionRepository
from app.repositories.user import UserRepository
# Constants
EXPECTED_TRANSACTION_COUNT = 4
PAGE_SIZE = 2
MIN_VLC_TRANSACTIONS = 2
MIN_SUCCESSFUL_TRANSACTIONS = 3
SUCCESSFUL_TRANSACTION_COUNT = 3
MIN_ALL_TRANSACTIONS = 5
TEST_AMOUNT = -10
TEST_BALANCE_BEFORE = 100
TEST_BALANCE_AFTER = 90
class TestCreditTransactionRepository:
@@ -102,11 +115,9 @@ class TestCreditTransactionRepository:
async def other_user_transaction(
self,
test_session: AsyncSession,
ensure_plans: tuple[Any, ...], # noqa: ARG002
ensure_plans: tuple[Any, ...],
) -> AsyncGenerator[CreditTransaction, None]:
"""Create a transaction for a different user."""
from app.repositories.user import UserRepository
# Create another user
user_repo = UserRepository(test_session)
other_user_data = {
@@ -174,7 +185,7 @@ class TestCreditTransactionRepository:
transactions = await credit_transaction_repository.get_by_user_id(test_user_id)
# Should return all transactions for test_user
assert len(transactions) == 4
assert len(transactions) == EXPECTED_TRANSACTION_COUNT
# Should be ordered by created_at desc (newest first)
assert all(t.user_id == test_user_id for t in transactions)
@@ -194,13 +205,13 @@ class TestCreditTransactionRepository:
first_page = await credit_transaction_repository.get_by_user_id(
test_user_id, limit=2, offset=0,
)
assert len(first_page) == 2
assert len(first_page) == PAGE_SIZE
# Get next 2 transactions
second_page = await credit_transaction_repository.get_by_user_id(
test_user_id, limit=2, offset=2,
)
assert len(second_page) == 2
assert len(second_page) == PAGE_SIZE
# Should not overlap
first_page_ids = {t.id for t in first_page}
@@ -219,12 +230,14 @@ class TestCreditTransactionRepository:
)
# Should return 2 VLC transactions (1 successful, 1 failed)
assert len(vlc_transactions) >= 2
assert len(vlc_transactions) >= MIN_VLC_TRANSACTIONS
assert all(t.action_type == "vlc_play_sound" for t in vlc_transactions)
extraction_transactions = await credit_transaction_repository.get_by_action_type(
extraction_transactions = (
await credit_transaction_repository.get_by_action_type(
"audio_extraction",
)
)
# Should return 1 extraction transaction
assert len(extraction_transactions) >= 1
@@ -262,7 +275,7 @@ class TestCreditTransactionRepository:
# Should only return successful transactions
assert all(t.success is True for t in successful_transactions)
# Should be at least 3 (vlc_play_sound, audio_extraction, credit_addition)
assert len(successful_transactions) >= 3
assert len(successful_transactions) >= MIN_SUCCESSFUL_TRANSACTIONS
@pytest.mark.asyncio
async def test_get_successful_transactions_by_user(
@@ -281,7 +294,7 @@ class TestCreditTransactionRepository:
assert all(t.success is True for t in successful_transactions)
assert all(t.user_id == test_user_id for t in successful_transactions)
# Should be 3 successful transactions for test_user
assert len(successful_transactions) == 3
assert len(successful_transactions) == SUCCESSFUL_TRANSACTION_COUNT
@pytest.mark.asyncio
async def test_get_successful_transactions_with_pagination(
@@ -295,7 +308,7 @@ class TestCreditTransactionRepository:
first_page = await credit_transaction_repository.get_successful_transactions(
user_id=test_user_id, limit=2, offset=0,
)
assert len(first_page) == 2
assert len(first_page) == PAGE_SIZE
assert all(t.success is True for t in first_page)
# Get next successful transaction
@@ -316,7 +329,7 @@ class TestCreditTransactionRepository:
all_transactions = await credit_transaction_repository.get_all()
# Should return all transactions
assert len(all_transactions) >= 5 # 4 from test_transactions + 1 other_user_transaction
assert len(all_transactions) >= MIN_ALL_TRANSACTIONS # 4 from test_transactions + 1 other_user_transaction
@pytest.mark.asyncio
async def test_create_transaction(
@@ -341,9 +354,9 @@ class TestCreditTransactionRepository:
assert transaction.id is not None
assert transaction.user_id == test_user_id
assert transaction.action_type == "test_action"
assert transaction.amount == -10
assert transaction.balance_before == 100
assert transaction.balance_after == 90
assert transaction.amount == TEST_AMOUNT
assert transaction.balance_before == TEST_BALANCE_BEFORE
assert transaction.balance_after == TEST_BALANCE_AFTER
assert transaction.success is True
assert transaction.metadata_json is not None
assert json.loads(transaction.metadata_json) == {"test": "data"}

View File

@@ -1,4 +1,5 @@
"""Tests for extraction repository."""
# ruff: noqa: ANN001, ANN201
from unittest.mock import AsyncMock, Mock
@@ -8,6 +9,9 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.extraction import Extraction
from app.repositories.extraction import ExtractionRepository
# Constants
TEST_SOUND_ID = 42
class TestExtractionRepository:
"""Test extraction repository."""
@@ -123,6 +127,6 @@ class TestExtractionRepository:
result = await extraction_repo.update(extraction, update_data)
assert result.status == "completed"
assert result.sound_id == 42
assert result.sound_id == TEST_SOUND_ID
extraction_repo.session.commit.assert_called_once()
extraction_repo.session.refresh.assert_called_once_with(extraction)

View File

@@ -1,6 +1,8 @@
"""Tests for playlist repository."""
# ruff: noqa: PLR2004, ANN401
from collections.abc import AsyncGenerator
from typing import Any
import pytest
import pytest_asyncio
@@ -10,6 +12,16 @@ from app.models.playlist import Playlist
from app.models.sound import Sound
from app.models.user import User
from app.repositories.playlist import PlaylistRepository
from app.utils.auth import PasswordUtils
# Constants
TEST_POSITION = 5
TEST_TOTAL_SOUNDS = 3
ONE_PLAYLIST = 1
ZERO_SOUNDS = 0
ONE_SOUND = 1
TWO_SOUNDS = 2
DEFAULT_POSITION = 0
class TestPlaylistRepository:
@@ -134,11 +146,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test getting playlists by user ID."""
# Create test user within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -172,7 +183,7 @@ class TestPlaylistRepository:
playlists = await playlist_repository.get_by_user_id(user_id)
# Should only return user's playlists, not the main playlist (user_id=None)
assert len(playlists) == 1
assert len(playlists) == ONE_PLAYLIST
assert playlists[0].name == "Test Playlist"
@pytest.mark.asyncio
@@ -210,11 +221,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test getting current playlist when none is set."""
# Create test user within this test
from app.utils.auth import PasswordUtils
user = User(
email="test2@example.com",
name="Test User 2",
@@ -302,11 +312,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test searching playlists by name."""
# Create test user within this test
from app.utils.auth import PasswordUtils
user = User(
email="test3@example.com",
name="Test User 3",
@@ -353,11 +362,12 @@ class TestPlaylistRepository:
# Search with user filter
user_results = await playlist_repository.search_by_name("playlist", user_id)
assert len(user_results) == 1 # Only user's playlists, not main playlist
# Only user's playlists, not main playlist
assert len(user_results) == ONE_PLAYLIST
# Search for specific playlist
test_results = await playlist_repository.search_by_name("test", user_id)
assert len(test_results) == 1
assert len(test_results) == ONE_PLAYLIST
assert test_results[0].name == "Test Playlist"
@pytest.mark.asyncio
@@ -365,11 +375,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test adding a sound to a playlist."""
# Create test user within this test
from app.utils.auth import PasswordUtils
user = User(
email="test4@example.com",
name="Test User 4",
@@ -421,18 +430,17 @@ class TestPlaylistRepository:
assert playlist_sound.playlist_id == playlist_id
assert playlist_sound.sound_id == sound_id
assert playlist_sound.position == 0
assert playlist_sound.position == DEFAULT_POSITION
@pytest.mark.asyncio
async def test_add_sound_to_playlist_with_position(
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test adding a sound to a playlist with specific position."""
# Create test user within this test
from app.utils.auth import PasswordUtils
user = User(
email="test5@example.com",
name="Test User 5",
@@ -485,18 +493,17 @@ class TestPlaylistRepository:
playlist_id, sound_id, position=5,
)
assert playlist_sound.position == 5
assert playlist_sound.position == TEST_POSITION
@pytest.mark.asyncio
async def test_remove_sound_from_playlist(
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test removing a sound from a playlist."""
# Create objects within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -564,11 +571,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test getting sounds in a playlist."""
# Create objects within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -615,14 +621,14 @@ class TestPlaylistRepository:
# Initially empty
sounds = await playlist_repository.get_playlist_sounds(playlist_id)
assert len(sounds) == 0
assert len(sounds) == ZERO_SOUNDS
# Add sound
await playlist_repository.add_sound_to_playlist(playlist_id, sound_id)
# Check sounds
sounds = await playlist_repository.get_playlist_sounds(playlist_id)
assert len(sounds) == 1
assert len(sounds) == ONE_SOUND
assert sounds[0].id == sound_id
@pytest.mark.asyncio
@@ -630,11 +636,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test getting sound count in a playlist."""
# Create objects within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -695,11 +700,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test checking if sound is in playlist."""
# Create objects within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -762,11 +766,10 @@ class TestPlaylistRepository:
self,
playlist_repository: PlaylistRepository,
test_session: AsyncSession,
ensure_plans,
ensure_plans: Any,
) -> None:
"""Test reordering sounds in a playlist."""
# Create objects within this test
from app.utils.auth import PasswordUtils
user = User(
email="test@example.com",
name="Test User",
@@ -823,6 +826,6 @@ class TestPlaylistRepository:
# Verify new order
sounds = await playlist_repository.get_playlist_sounds(playlist_id)
assert len(sounds) == 2
assert len(sounds) == TWO_SOUNDS
assert sounds[0].id == sound2_id # sound2 now at position 5
assert sounds[1].id == sound1_id # sound1 now at position 10

View File

@@ -1,14 +1,19 @@
"""Tests for sound repository."""
# ruff: noqa: ARG002, PLR2004
from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy.exc import IntegrityError
from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.sound import Sound
from app.repositories.sound import SoundRepository
# Constants
MIN_POPULAR_SOUNDS = 3
class TestSoundRepository:
"""Test sound repository operations."""
@@ -306,7 +311,7 @@ class TestSoundRepository:
# Get popular sounds
popular_sounds = await sound_repository.get_popular_sounds(limit=10)
assert len(popular_sounds) >= 3
assert len(popular_sounds) >= MIN_POPULAR_SOUNDS
# Should be ordered by play_count desc
assert popular_sounds[0].play_count >= popular_sounds[1].play_count
# The highest play count sound should be first
@@ -372,5 +377,5 @@ class TestSoundRepository:
}
# Should fail due to unique constraint on hash
with pytest.raises(Exception): # SQLAlchemy IntegrityError or similar
with pytest.raises(IntegrityError, match="UNIQUE constraint failed"):
await sound_repository.create(duplicate_sound_data)

View File

@@ -1,4 +1,5 @@
"""Tests for user repository."""
# ruff: noqa: ARG002
from collections.abc import AsyncGenerator
@@ -174,24 +175,24 @@ class TestUserRepository:
test_user: User,
) -> None:
"""Test updating a user."""
UPDATED_CREDITS = 200
updated_credits = 200
update_data = {
"name": "Updated Name",
"credits": UPDATED_CREDITS,
"credits": updated_credits,
}
updated_user = await user_repository.update(test_user, update_data)
assert updated_user.id == test_user.id
assert updated_user.name == "Updated Name"
assert updated_user.credits == UPDATED_CREDITS
assert updated_user.credits == updated_credits
assert updated_user.email == test_user.email # Unchanged
@pytest.mark.asyncio
async def test_delete_user(
self,
user_repository: UserRepository,
ensure_plans: tuple[Plan, Plan], # noqa: ARG002
ensure_plans: tuple[Plan, Plan],
test_session: AsyncSession,
) -> None:
"""Test deleting a user."""

View File

@@ -1,9 +1,11 @@
"""Tests for user OAuth repository."""
# ruff: noqa: ARG002
from collections.abc import AsyncGenerator
import pytest
import pytest_asyncio
from sqlalchemy.exc import IntegrityError
from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.user import User
@@ -156,7 +158,9 @@ class TestUserOauthRepository:
assert updated_oauth.name == "Updated User Name"
assert updated_oauth.picture == "https://example.com/photo.jpg"
assert updated_oauth.provider == test_oauth.provider # Unchanged
assert updated_oauth.provider_user_id == test_oauth.provider_user_id # Unchanged
assert (
updated_oauth.provider_user_id == test_oauth.provider_user_id
) # Unchanged
@pytest.mark.asyncio
async def test_delete_oauth(
@@ -176,7 +180,7 @@ class TestUserOauthRepository:
"picture": None,
}
oauth = await user_oauth_repository.create(oauth_data)
oauth_id = oauth.id
_ = oauth.id # Store ID but don't use it
# Delete the OAuth record
await user_oauth_repository.delete(oauth)
@@ -206,7 +210,7 @@ class TestUserOauthRepository:
}
# This should fail due to unique constraint
with pytest.raises(Exception): # SQLAlchemy IntegrityError or similar
with pytest.raises(IntegrityError, match="UNIQUE constraint failed"):
await user_oauth_repository.create(duplicate_oauth_data)
@pytest.mark.asyncio
@@ -225,7 +229,7 @@ class TestUserOauthRepository:
"name": "Test User Google",
"picture": None,
}
google_oauth = await user_oauth_repository.create(google_oauth_data)
_ = await user_oauth_repository.create(google_oauth_data)
# Create GitHub OAuth for the same user
github_oauth_data = {
@@ -236,7 +240,7 @@ class TestUserOauthRepository:
"name": "Test User GitHub",
"picture": None,
}
github_oauth = await user_oauth_repository.create(github_oauth_data)
_ = await user_oauth_repository.create(github_oauth_data)
# Verify both exist by querying back from database
found_google = await user_oauth_repository.get_by_user_id_and_provider(