Refactor test files for improved readability and consistency

- Removed unnecessary blank lines and adjusted formatting in test files.
- Ensured consistent use of commas in function calls and assertions across various test cases.
- Updated import statements for better organization and clarity.
- Enhanced mock setups in tests for better isolation and reliability.
- Improved assertions to follow a consistent style for better readability.
This commit is contained in:
JSC
2025-07-31 21:37:04 +02:00
parent e69098d633
commit 8847131f24
42 changed files with 602 additions and 616 deletions

View File

@@ -1,6 +1,6 @@
"""Player API endpoints."""
from typing import Annotated, Any
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
@@ -214,4 +214,4 @@ async def get_state(
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get player state",
) from e
) from e

View File

@@ -10,12 +10,12 @@ from app.core.dependencies import get_current_active_user_flexible
from app.models.credit_action import CreditActionType
from app.models.user import User
from app.repositories.sound import SoundRepository
from app.services.extraction import ExtractionInfo, ExtractionService
from app.services.credit import CreditService, InsufficientCreditsError
from app.services.extraction import ExtractionInfo, ExtractionService
from app.services.extraction_processor import extraction_processor
from app.services.sound_normalizer import NormalizationResults, SoundNormalizerService
from app.services.sound_scanner import ScanResults, SoundScannerService
from app.services.vlc_player import get_vlc_player_service, VLCPlayerService
from app.services.vlc_player import VLCPlayerService, get_vlc_player_service
router = APIRouter(prefix="/sounds", tags=["sounds"])
@@ -125,13 +125,13 @@ async def scan_custom_directory(
async def normalize_all_sounds(
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
normalizer_service: Annotated[
SoundNormalizerService, Depends(get_sound_normalizer_service)
SoundNormalizerService, Depends(get_sound_normalizer_service),
],
force: bool = Query(
False, description="Force normalization of already normalized sounds"
False, description="Force normalization of already normalized sounds",
),
one_pass: bool | None = Query(
None, description="Use one-pass normalization (overrides config)"
None, description="Use one-pass normalization (overrides config)",
),
) -> dict[str, NormalizationResults | str]:
"""Normalize all unnormalized sounds."""
@@ -163,13 +163,13 @@ async def normalize_sounds_by_type(
sound_type: str,
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
normalizer_service: Annotated[
SoundNormalizerService, Depends(get_sound_normalizer_service)
SoundNormalizerService, Depends(get_sound_normalizer_service),
],
force: bool = Query(
False, description="Force normalization of already normalized sounds"
False, description="Force normalization of already normalized sounds",
),
one_pass: bool | None = Query(
None, description="Use one-pass normalization (overrides config)"
None, description="Use one-pass normalization (overrides config)",
),
) -> dict[str, NormalizationResults | str]:
"""Normalize all sounds of a specific type (SDB, TTS, EXT)."""
@@ -210,13 +210,13 @@ async def normalize_sound_by_id(
sound_id: int,
current_user: Annotated[User, Depends(get_current_active_user_flexible)],
normalizer_service: Annotated[
SoundNormalizerService, Depends(get_sound_normalizer_service)
SoundNormalizerService, Depends(get_sound_normalizer_service),
],
force: bool = Query(
False, description="Force normalization of already normalized sound"
False, description="Force normalization of already normalized sound",
),
one_pass: bool | None = Query(
None, description="Use one-pass normalization (overrides config)"
None, description="Use one-pass normalization (overrides config)",
),
) -> dict[str, str]:
"""Normalize a specific sound by ID."""
@@ -283,7 +283,7 @@ async def create_extraction(
)
extraction_info = await extraction_service.create_extraction(
url, current_user.id
url, current_user.id,
)
# Queue the extraction for background processing
@@ -398,7 +398,7 @@ async def play_sound_with_vlc(
await credit_service.validate_and_reserve_credits(
current_user.id,
CreditActionType.VLC_PLAY_SOUND,
{"sound_id": sound_id, "sound_name": sound.name}
{"sound_id": sound_id, "sound_name": sound.name},
)
except InsufficientCreditsError as e:
raise HTTPException(
@@ -408,7 +408,7 @@ async def play_sound_with_vlc(
# Play the sound using VLC
success = await vlc_player.play_sound(sound)
# Deduct credits based on success
await credit_service.deduct_credits(
current_user.id,
@@ -416,7 +416,7 @@ async def play_sound_with_vlc(
success,
{"sound_id": sound_id, "sound_name": sound.name},
)
if not success:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,

View File

@@ -118,4 +118,4 @@ def get_all_credit_actions() -> dict[CreditActionType, CreditAction]:
Dictionary of all credit actions
"""
return CREDIT_ACTIONS.copy()
return CREDIT_ACTIONS.copy()

View File

@@ -26,4 +26,4 @@ class CreditTransaction(BaseModel, table=True):
metadata_json: str | None = Field(default=None)
# relationships
user: "User" = Relationship(back_populates="credit_transactions")
user: "User" = Relationship(back_populates="credit_transactions")

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING
from sqlmodel import Field, Relationship, UniqueConstraint
from sqlmodel import Field, Relationship
from app.models.base import BaseModel

View File

@@ -39,7 +39,7 @@ __all__ = [
"UserResponse",
# Common schemas
"HealthResponse",
"MessageResponse",
"MessageResponse",
"StatusResponse",
# Player schemas
"PlayerModeRequest",

View File

@@ -18,4 +18,4 @@ class StatusResponse(BaseModel):
class HealthResponse(BaseModel):
"""Health check response."""
status: str = Field(description="Health status")
status: str = Field(description="Health status")

View File

@@ -30,10 +30,10 @@ class PlayerStateResponse(BaseModel):
status: str = Field(description="Player status (playing, paused, stopped)")
current_sound: dict[str, Any] | None = Field(
None, description="Current sound information"
None, description="Current sound information",
)
playlist: dict[str, Any] | None = Field(
None, description="Current playlist information"
None, description="Current playlist information",
)
position: int = Field(description="Current position in milliseconds")
duration: int | None = Field(

View File

@@ -1,6 +1,6 @@
"""Playlist schemas."""
from pydantic import BaseModel, Field
from pydantic import BaseModel
from app.models.playlist import Playlist
from app.models.sound import Sound

View File

@@ -30,7 +30,7 @@ class InsufficientCreditsError(Exception):
self.required = required
self.available = available
super().__init__(
f"Insufficient credits: {required} required, {available} available"
f"Insufficient credits: {required} required, {available} available",
)
@@ -138,10 +138,10 @@ class CreditService:
"""
action = get_credit_action(action_type)
# Only deduct if action requires success and was successful, or doesn't require success
should_deduct = (action.requires_success and success) or not action.requires_success
if not should_deduct:
logger.info(
"Skipping credit deduction for user %s: action %s failed and requires success",
@@ -150,7 +150,7 @@ class CreditService:
)
# Still create a transaction record for auditing
return await self._create_transaction_record(
user_id, action, 0, success, metadata
user_id, action, 0, success, metadata,
)
session = self.db_session_factory()
@@ -380,4 +380,4 @@ class CreditService:
raise ValueError(msg)
return user.credits
finally:
await session.close()
await session.close()

View File

@@ -10,7 +10,6 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.config import settings
from app.core.logging import get_logger
from app.models.extraction import Extraction
from app.models.sound import Sound
from app.repositories.extraction import ExtractionRepository
from app.repositories.sound import SoundRepository
@@ -155,7 +154,7 @@ class ExtractionService:
# Check if extraction already exists for this service
existing = await self.extraction_repo.get_by_service_and_id(
service_info["service"], service_info["service_id"]
service_info["service"], service_info["service_id"],
)
if existing and existing.id != extraction_id:
error_msg = (
@@ -180,7 +179,7 @@ class ExtractionService:
# Extract audio and thumbnail
audio_file, thumbnail_file = await self._extract_media(
extraction_id, extraction_url
extraction_id, extraction_url,
)
# Move files to final locations
@@ -238,7 +237,7 @@ class ExtractionService:
except Exception as e:
error_msg = str(e)
logger.exception(
"Failed to process extraction %d: %s", extraction_id, error_msg
"Failed to process extraction %d: %s", extraction_id, error_msg,
)
# Update extraction with error
@@ -262,14 +261,14 @@ class ExtractionService:
}
async def _extract_media(
self, extraction_id: int, extraction_url: str
self, extraction_id: int, extraction_url: str,
) -> tuple[Path, Path | None]:
"""Extract audio and thumbnail using yt-dlp."""
temp_dir = Path(settings.EXTRACTION_TEMP_DIR)
# Create unique filename based on extraction ID
output_template = str(
temp_dir / f"extraction_{extraction_id}_%(title)s.%(ext)s"
temp_dir / f"extraction_{extraction_id}_%(title)s.%(ext)s",
)
# Configure yt-dlp options
@@ -304,8 +303,8 @@ class ExtractionService:
# Find the extracted files
audio_files = list(
temp_dir.glob(
f"extraction_{extraction_id}_*.{settings.EXTRACTION_AUDIO_FORMAT}"
)
f"extraction_{extraction_id}_*.{settings.EXTRACTION_AUDIO_FORMAT}",
),
)
thumbnail_files = (
list(temp_dir.glob(f"extraction_{extraction_id}_*.webp"))
@@ -342,7 +341,7 @@ class ExtractionService:
"""Move extracted files to their final locations."""
# Generate clean filename based on title and service
safe_title = self._sanitize_filename(
title or f"{service or 'unknown'}_{service_id or 'unknown'}"
title or f"{service or 'unknown'}_{service_id or 'unknown'}",
)
# Move audio file

View File

@@ -46,9 +46,9 @@ class ExtractionProcessor:
if self.processor_task and not self.processor_task.done():
try:
await asyncio.wait_for(self.processor_task, timeout=30.0)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning(
"Extraction processor did not stop gracefully, cancelling..."
"Extraction processor did not stop gracefully, cancelling...",
)
self.processor_task.cancel()
try:
@@ -66,7 +66,7 @@ class ExtractionProcessor:
# The processor will pick it up on the next cycle
else:
logger.warning(
"Extraction %d is already being processed", extraction_id
"Extraction %d is already being processed", extraction_id,
)
async def _process_queue(self) -> None:
@@ -81,7 +81,7 @@ class ExtractionProcessor:
try:
await asyncio.wait_for(self.shutdown_event.wait(), timeout=5.0)
break # Shutdown requested
except asyncio.TimeoutError:
except TimeoutError:
continue # Continue processing
except Exception as e:
@@ -90,7 +90,7 @@ class ExtractionProcessor:
try:
await asyncio.wait_for(self.shutdown_event.wait(), timeout=10.0)
break # Shutdown requested
except asyncio.TimeoutError:
except TimeoutError:
continue
logger.info("Extraction queue processor stopped")
@@ -125,13 +125,13 @@ class ExtractionProcessor:
# Start processing this extraction in the background
task = asyncio.create_task(
self._process_single_extraction(extraction_id)
self._process_single_extraction(extraction_id),
)
task.add_done_callback(
lambda t, eid=extraction_id: self._on_extraction_completed(
eid,
t,
)
),
)
logger.info(

View File

@@ -49,7 +49,7 @@ class PlaylistService:
if not main_playlist:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Main playlist not found. Make sure to run database seeding."
detail="Main playlist not found. Make sure to run database seeding.",
)
return main_playlist
@@ -179,7 +179,7 @@ class PlaylistService:
return await self.playlist_repo.get_playlist_sounds(playlist_id)
async def add_sound_to_playlist(
self, playlist_id: int, sound_id: int, user_id: int, position: int | None = None
self, playlist_id: int, sound_id: int, user_id: int, position: int | None = None,
) -> None:
"""Add a sound to a playlist."""
# Verify playlist exists
@@ -202,11 +202,11 @@ class PlaylistService:
await self.playlist_repo.add_sound_to_playlist(playlist_id, sound_id, position)
logger.info(
"Added sound %s to playlist %s for user %s", sound_id, playlist_id, user_id
"Added sound %s to playlist %s for user %s", sound_id, playlist_id, user_id,
)
async def remove_sound_from_playlist(
self, playlist_id: int, sound_id: int, user_id: int
self, playlist_id: int, sound_id: int, user_id: int,
) -> None:
"""Remove a sound from a playlist."""
# Verify playlist exists
@@ -228,7 +228,7 @@ class PlaylistService:
)
async def reorder_playlist_sounds(
self, playlist_id: int, user_id: int, sound_positions: list[tuple[int, int]]
self, playlist_id: int, user_id: int, sound_positions: list[tuple[int, int]],
) -> None:
"""Reorder sounds in a playlist."""
# Verify playlist exists
@@ -262,7 +262,7 @@ class PlaylistService:
await self._unset_current_playlist(user_id)
await self._set_main_as_current(user_id)
logger.info(
"Unset current playlist and set main as current for user %s", user_id
"Unset current playlist and set main as current for user %s", user_id,
)
async def get_playlist_stats(self, playlist_id: int) -> dict[str, Any]:
@@ -290,7 +290,7 @@ class PlaylistService:
# Check if sound is already in main playlist
if not await self.playlist_repo.is_sound_in_playlist(
main_playlist.id, sound_id
main_playlist.id, sound_id,
):
await self.playlist_repo.add_sound_to_playlist(main_playlist.id, sound_id)
logger.info(

View File

@@ -141,7 +141,7 @@ class SoundNormalizerService:
ffmpeg.run(stream, quiet=True, overwrite_output=True)
logger.info("One-pass normalization completed: %s", output_path)
except Exception as e:
except Exception:
logger.exception("One-pass normalization failed for %s", input_path)
raise
@@ -153,7 +153,7 @@ class SoundNormalizerService:
"""Normalize audio using two-pass loudnorm for better quality."""
try:
logger.info(
"Starting two-pass normalization: %s -> %s", input_path, output_path
"Starting two-pass normalization: %s -> %s", input_path, output_path,
)
# First pass: analyze
@@ -193,7 +193,7 @@ class SoundNormalizerService:
json_match = re.search(r'\{[^{}]*"input_i"[^{}]*\}', analysis_output)
if not json_match:
logger.error(
"Could not find JSON in loudnorm output: %s", analysis_output
"Could not find JSON in loudnorm output: %s", analysis_output,
)
raise ValueError("Could not extract loudnorm analysis data")
@@ -260,7 +260,7 @@ class SoundNormalizerService:
)
raise
except Exception as e:
except Exception:
logger.exception("Two-pass normalization failed for %s", input_path)
raise
@@ -428,7 +428,7 @@ class SoundNormalizerService:
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
}
},
)
# Process each sound using captured data
@@ -476,7 +476,7 @@ class SoundNormalizerService:
"normalized_hash": None,
"id": sound_id,
"error": str(e),
}
},
)
logger.info("Normalization completed: %s", results)
@@ -517,7 +517,7 @@ class SoundNormalizerService:
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
}
},
)
# Process each sound using captured data
@@ -565,7 +565,7 @@ class SoundNormalizerService:
"normalized_hash": None,
"id": sound_id,
"error": str(e),
}
},
)
logger.info("Type normalization completed: %s", results)

View File

@@ -132,7 +132,7 @@ class SoundScannerService:
"id": None,
"error": str(e),
"changes": None,
}
},
)
# Delete sounds that no longer exist in directory
@@ -153,7 +153,7 @@ class SoundScannerService:
"id": sound.id,
"error": None,
"changes": None,
}
},
)
except Exception as e:
logger.exception("Error deleting sound %s", filename)
@@ -169,7 +169,7 @@ class SoundScannerService:
"id": sound.id,
"error": str(e),
"changes": None,
}
},
)
logger.info("Sync completed: %s", results)
@@ -219,7 +219,7 @@ class SoundScannerService:
"id": sound.id,
"error": None,
"changes": None,
}
},
)
elif existing_sound.hash != file_hash:
@@ -246,7 +246,7 @@ class SoundScannerService:
"id": existing_sound.id,
"error": None,
"changes": ["hash", "duration", "size", "name"],
}
},
)
else:
@@ -264,7 +264,7 @@ class SoundScannerService:
"id": existing_sound.id,
"error": None,
"changes": None,
}
},
)
async def scan_soundboard_directory(self) -> ScanResults:

View File

@@ -6,7 +6,6 @@ from collections.abc import Callable
from pathlib import Path
from typing import Any
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.logging import get_logger

View File

@@ -5,7 +5,7 @@ from collections.abc import Awaitable, Callable
from typing import Any, TypeVar
from app.models.credit_action import CreditActionType
from app.services.credit import CreditService, InsufficientCreditsError
from app.services.credit import CreditService
F = TypeVar("F", bound=Callable[..., Awaitable[Any]])
@@ -69,7 +69,7 @@ def requires_credits(
# Validate credits before execution
await credit_service.validate_and_reserve_credits(
user_id, action_type, metadata
user_id, action_type, metadata,
)
# Execute the function
@@ -85,7 +85,7 @@ def requires_credits(
finally:
# Deduct credits based on success
await credit_service.deduct_credits(
user_id, action_type, success, metadata
user_id, action_type, success, metadata,
)
return wrapper # type: ignore[return-value]
@@ -173,7 +173,7 @@ class CreditManager:
async def __aenter__(self) -> "CreditManager":
"""Enter context manager - validate credits."""
await self.credit_service.validate_and_reserve_credits(
self.user_id, self.action_type, self.metadata
self.user_id, self.action_type, self.metadata,
)
self.validated = True
return self
@@ -184,9 +184,9 @@ class CreditManager:
# If no exception occurred, consider it successful
success = exc_type is None and self.success
await self.credit_service.deduct_credits(
self.user_id, self.action_type, success, self.metadata
self.user_id, self.action_type, success, self.metadata,
)
def mark_success(self) -> None:
"""Mark the operation as successful."""
self.success = True
self.success = True