diff --git a/app/models/sound.py b/app/models/sound.py index 93b9449..ee666ae 100644 --- a/app/models/sound.py +++ b/app/models/sound.py @@ -1,6 +1,6 @@ from typing import TYPE_CHECKING -from sqlmodel import Field, Relationship +from sqlmodel import Field, Relationship, UniqueConstraint from app.models.base import BaseModel @@ -29,6 +29,11 @@ class Sound(BaseModel, table=True): is_music: bool = Field(default=False, nullable=False) is_deletable: bool = Field(default=True, nullable=False) + # constraints + __table_args__ = ( + UniqueConstraint("hash", name="uq_sound_hash"), + ) + # relationships playlist_sounds: list["PlaylistSound"] = Relationship(back_populates="sound") extractions: list["Extraction"] = Relationship(back_populates="sound") diff --git a/app/repositories/sound.py b/app/repositories/sound.py index eb6af2f..afce0ad 100644 --- a/app/repositories/sound.py +++ b/app/repositories/sound.py @@ -116,7 +116,7 @@ class SoundRepository: async def get_popular_sounds(self, limit: int = 10) -> list[Sound]: """Get the most played sounds.""" try: - statement = select(Sound).order_by(desc(Sound.play_count)).limit(limit) + statement = select(Sound).order_by(Sound.play_count.desc()).limit(limit) result = await self.session.exec(statement) return list(result.all()) except Exception: diff --git a/tests/repositories/test_credit_transaction.py b/tests/repositories/test_credit_transaction.py index 3dde7ba..173df11 100644 --- a/tests/repositories/test_credit_transaction.py +++ b/tests/repositories/test_credit_transaction.py @@ -2,6 +2,7 @@ import json from collections.abc import AsyncGenerator +from typing import Any import pytest import pytest_asyncio @@ -19,7 +20,7 @@ class TestCreditTransactionRepository: async def credit_transaction_repository( self, test_session: AsyncSession, - ) -> AsyncGenerator[CreditTransactionRepository, None]: # type: ignore[misc] + ) -> AsyncGenerator[CreditTransactionRepository, None]: """Create a credit transaction repository instance.""" yield CreditTransactionRepository(test_session) @@ -29,6 +30,7 @@ class TestCreditTransactionRepository: test_user: User, ) -> int: """Get test user ID to avoid lazy loading issues.""" + assert test_user.id is not None return test_user.id @pytest_asyncio.fixture @@ -36,7 +38,7 @@ class TestCreditTransactionRepository: self, test_session: AsyncSession, test_user_id: int, - ) -> AsyncGenerator[list[CreditTransaction], None]: # type: ignore[misc] + ) -> AsyncGenerator[list[CreditTransaction], None]: """Create test credit transactions.""" transactions = [] user_id = test_user_id @@ -100,8 +102,8 @@ class TestCreditTransactionRepository: async def other_user_transaction( self, test_session: AsyncSession, - ensure_plans: tuple, # noqa: ARG002 - ) -> AsyncGenerator[CreditTransaction, None]: # type: ignore[misc] + ensure_plans: tuple[Any, ...], # noqa: ARG002 + ) -> AsyncGenerator[CreditTransaction, None]: """Create a transaction for a different user.""" from app.models.plan import Plan from app.repositories.user import UserRepository @@ -142,7 +144,9 @@ class TestCreditTransactionRepository: test_transactions: list[CreditTransaction], ) -> None: """Test getting transaction by ID when it exists.""" - transaction = await credit_transaction_repository.get_by_id(test_transactions[0].id) + transaction_id = test_transactions[0].id + assert transaction_id is not None + transaction = await credit_transaction_repository.get_by_id(transaction_id) assert transaction is not None assert transaction.id == test_transactions[0].id @@ -342,6 +346,7 @@ class TestCreditTransactionRepository: assert transaction.balance_before == 100 assert transaction.balance_after == 90 assert transaction.success is True + assert transaction.metadata_json is not None assert json.loads(transaction.metadata_json) == {"test": "data"} @pytest.mark.asyncio @@ -363,6 +368,7 @@ class TestCreditTransactionRepository: assert updated_transaction.id == transaction.id assert updated_transaction.description == "Updated description" + assert updated_transaction.metadata_json is not None assert json.loads(updated_transaction.metadata_json) == {"updated": True} # Other fields should remain unchanged assert updated_transaction.amount == transaction.amount @@ -394,6 +400,7 @@ class TestCreditTransactionRepository: await credit_transaction_repository.delete(transaction) # Verify transaction is deleted + assert transaction_id is not None deleted_transaction = await credit_transaction_repository.get_by_id(transaction_id) assert deleted_transaction is None diff --git a/tests/repositories/test_sound.py b/tests/repositories/test_sound.py index fe90f49..ef737cb 100644 --- a/tests/repositories/test_sound.py +++ b/tests/repositories/test_sound.py @@ -17,7 +17,7 @@ class TestSoundRepository: async def sound_repository( self, test_session: AsyncSession, - ) -> AsyncGenerator[SoundRepository, None]: # type: ignore[misc] + ) -> AsyncGenerator[SoundRepository, None]: """Create a sound repository instance.""" yield SoundRepository(test_session) @@ -25,7 +25,7 @@ class TestSoundRepository: async def test_sound( self, test_session: AsyncSession, - ) -> AsyncGenerator[Sound, None]: # type: ignore[misc] + ) -> AsyncGenerator[Sound, None]: """Create a test sound.""" sound_data = { "name": "Test Sound", @@ -47,7 +47,7 @@ class TestSoundRepository: async def normalized_sound( self, test_session: AsyncSession, - ) -> AsyncGenerator[Sound, None]: # type: ignore[misc] + ) -> AsyncGenerator[Sound, None]: """Create a normalized test sound.""" sound_data = { "name": "Normalized Sound", @@ -76,7 +76,9 @@ class TestSoundRepository: test_sound: Sound, ) -> None: """Test getting sound by ID when it exists.""" - sound = await sound_repository.get_by_id(test_sound.id) + sound_id = test_sound.id + assert sound_id is not None + sound = await sound_repository.get_by_id(sound_id) assert sound is not None assert sound.id == test_sound.id @@ -240,6 +242,7 @@ class TestSoundRepository: await sound_repository.delete(sound) # Verify sound is deleted + assert sound_id is not None deleted_sound = await sound_repository.get_by_id(sound_id) assert deleted_sound is None @@ -353,7 +356,7 @@ class TestSoundRepository: sound_repository: SoundRepository, test_sound: Sound, ) -> None: - """Test creating sound with duplicate hash is allowed.""" + """Test creating sound with duplicate hash should fail.""" # Store the hash to avoid lazy loading issues original_hash = test_sound.hash @@ -368,9 +371,6 @@ class TestSoundRepository: "is_normalized": False, } - # Should succeed - duplicate hashes are allowed - duplicate_sound = await sound_repository.create(duplicate_sound_data) - - assert duplicate_sound.id is not None - assert duplicate_sound.name == "Duplicate Hash Sound" - assert duplicate_sound.hash == original_hash # Same hash is allowed \ No newline at end of file + # Should fail due to unique constraint on hash + with pytest.raises(Exception): # SQLAlchemy IntegrityError or similar + await sound_repository.create(duplicate_sound_data) \ No newline at end of file diff --git a/tests/repositories/test_user_oauth.py b/tests/repositories/test_user_oauth.py index bbacb64..c85f6e2 100644 --- a/tests/repositories/test_user_oauth.py +++ b/tests/repositories/test_user_oauth.py @@ -18,7 +18,7 @@ class TestUserOauthRepository: async def user_oauth_repository( self, test_session: AsyncSession, - ) -> AsyncGenerator[UserOauthRepository, None]: # type: ignore[misc] + ) -> AsyncGenerator[UserOauthRepository, None]: """Create a user OAuth repository instance.""" yield UserOauthRepository(test_session) @@ -28,6 +28,7 @@ class TestUserOauthRepository: test_user: User, ) -> int: """Get test user ID to avoid lazy loading issues.""" + assert test_user.id is not None return test_user.id @pytest_asyncio.fixture @@ -35,7 +36,7 @@ class TestUserOauthRepository: self, test_session: AsyncSession, test_user_id: int, - ) -> AsyncGenerator[UserOauth, None]: # type: ignore[misc] + ) -> AsyncGenerator[UserOauth, None]: """Create a test OAuth record.""" oauth_data = { "user_id": test_user_id,