Compare commits

...

3 Commits

Author SHA1 Message Date
JSC
43be92c8f9 fix: Update linter command in CI
All checks were successful
Backend CI / lint (push) Successful in 9m31s
Backend CI / test (push) Successful in 4m7s
2025-08-01 09:44:53 +02:00
JSC
f68f4d9046 refactor: Compiled ignored ruff rules in pyproject 2025-08-01 09:40:15 +02:00
JSC
fceff92ca1 fix: Lint fixes of last tests 2025-08-01 09:30:15 +02:00
27 changed files with 329 additions and 325 deletions

View File

@@ -30,7 +30,7 @@ jobs:
run: uv sync --locked --all-extras --dev run: uv sync --locked --all-extras --dev
- name: Run linter - name: Run linter
run: uv run ruff check --exclude tests run: uv run ruff check
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -43,8 +43,28 @@ exclude = ["alembic"]
select = ["ALL"] select = ["ALL"]
ignore = ["D100", "D103", "TRY301"] ignore = ["D100", "D103", "TRY301"]
[tool.ruff.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = ["S101", "S105"] "tests/**/*.py" = [
"S101", # Use of assert detected
"S105", # Possible hardcoded password
"S106", # Possible hardcoded password
"ANN001", # Missing type annotation for function argument
"ANN003", # Missing type annotation for **kwargs
"ANN201", # Missing return type annotation for public function
"ANN202", # Missing return type annotation for private function
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed
"ARG001", # Unused function argument
"ARG002", # Unused method argument
"ARG005", # Unused lambda argument
"BLE001", # Do not catch blind exception
"E501", # Line too long
"PLR2004", # Magic value used in comparison
"PLC0415", # `import` should be at top-level
"SLF001", # Private member accessed
"SIM117", # Use a single `if` statement
"PT011", # `pytest.raises()` is too broad
"PT012", # `pytest.raises()` block should contain a single simple statement
]
[tool.pytest.ini_options] [tool.pytest.ini_options]
filterwarnings = [ filterwarnings = [

View File

@@ -17,7 +17,7 @@ class TestApiTokenEndpoints:
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test successful API token generation.""" """Test successful API token generation."""
request_data = {"expires_days": 30} request_data = {"expires_days": 30}
@@ -45,7 +45,7 @@ class TestApiTokenEndpoints:
async def test_generate_api_token_default_expiry( async def test_generate_api_token_default_expiry(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test API token generation with default expiry.""" """Test API token generation with default expiry."""
response = await authenticated_client.post("/api/v1/auth/api-token", json={}) response = await authenticated_client.post("/api/v1/auth/api-token", json={})
@@ -54,9 +54,7 @@ class TestApiTokenEndpoints:
expires_at_str = data["expires_at"] expires_at_str = data["expires_at"]
# Handle both ISO format with/without timezone info # Handle both ISO format with/without timezone info
if expires_at_str.endswith("Z"): if expires_at_str.endswith("Z") or "+" in expires_at_str or expires_at_str.count("-") > 2:
expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))
elif "+" in expires_at_str or expires_at_str.count("-") > 2:
expires_at = datetime.fromisoformat(expires_at_str) expires_at = datetime.fromisoformat(expires_at_str)
else: else:
# Naive datetime, assume UTC # Naive datetime, assume UTC
@@ -71,7 +69,7 @@ class TestApiTokenEndpoints:
async def test_generate_api_token_custom_expiry( async def test_generate_api_token_custom_expiry(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test API token generation with custom expiry.""" """Test API token generation with custom expiry."""
expires_days = 90 expires_days = 90
request_data = {"expires_days": expires_days} request_data = {"expires_days": expires_days}
@@ -86,9 +84,7 @@ class TestApiTokenEndpoints:
expires_at_str = data["expires_at"] expires_at_str = data["expires_at"]
# Handle both ISO format with/without timezone info # Handle both ISO format with/without timezone info
if expires_at_str.endswith("Z"): if expires_at_str.endswith("Z") or "+" in expires_at_str or expires_at_str.count("-") > 2:
expires_at = datetime.fromisoformat(expires_at_str.replace("Z", "+00:00"))
elif "+" in expires_at_str or expires_at_str.count("-") > 2:
expires_at = datetime.fromisoformat(expires_at_str) expires_at = datetime.fromisoformat(expires_at_str)
else: else:
# Naive datetime, assume UTC # Naive datetime, assume UTC
@@ -103,7 +99,7 @@ class TestApiTokenEndpoints:
async def test_generate_api_token_validation_errors( async def test_generate_api_token_validation_errors(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test API token generation with validation errors.""" """Test API token generation with validation errors."""
# Test minimum validation # Test minimum validation
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -120,7 +116,7 @@ class TestApiTokenEndpoints:
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_generate_api_token_unauthenticated(self, client: AsyncClient): async def test_generate_api_token_unauthenticated(self, client: AsyncClient) -> None:
"""Test API token generation without authentication.""" """Test API token generation without authentication."""
response = await client.post( response = await client.post(
"/api/v1/auth/api-token", "/api/v1/auth/api-token",
@@ -132,7 +128,7 @@ class TestApiTokenEndpoints:
async def test_get_api_token_status_no_token( async def test_get_api_token_status_no_token(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test getting API token status when user has no token.""" """Test getting API token status when user has no token."""
response = await authenticated_client.get("/api/v1/auth/api-token/status") response = await authenticated_client.get("/api/v1/auth/api-token/status")
@@ -147,7 +143,7 @@ class TestApiTokenEndpoints:
async def test_get_api_token_status_with_token( async def test_get_api_token_status_with_token(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test getting API token status when user has a token.""" """Test getting API token status when user has a token."""
# First generate a token # First generate a token
await authenticated_client.post( await authenticated_client.post(
@@ -170,7 +166,7 @@ class TestApiTokenEndpoints:
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test getting API token status with expired token.""" """Test getting API token status with expired token."""
# Mock expired token # Mock expired token
with patch("app.utils.auth.TokenUtils.is_token_expired", return_value=True): with patch("app.utils.auth.TokenUtils.is_token_expired", return_value=True):
@@ -190,7 +186,7 @@ class TestApiTokenEndpoints:
assert data["is_expired"] is True assert data["is_expired"] is True
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_api_token_status_unauthenticated(self, client: AsyncClient): async def test_get_api_token_status_unauthenticated(self, client: AsyncClient) -> None:
"""Test getting API token status without authentication.""" """Test getting API token status without authentication."""
response = await client.get("/api/v1/auth/api-token/status") response = await client.get("/api/v1/auth/api-token/status")
assert response.status_code == 401 assert response.status_code == 401
@@ -199,7 +195,7 @@ class TestApiTokenEndpoints:
async def test_revoke_api_token_success( async def test_revoke_api_token_success(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test successful API token revocation.""" """Test successful API token revocation."""
# First generate a token # First generate a token
await authenticated_client.post( await authenticated_client.post(
@@ -230,7 +226,7 @@ class TestApiTokenEndpoints:
async def test_revoke_api_token_no_token( async def test_revoke_api_token_no_token(
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test revoking API token when user has no token.""" """Test revoking API token when user has no token."""
response = await authenticated_client.delete("/api/v1/auth/api-token") response = await authenticated_client.delete("/api/v1/auth/api-token")
@@ -239,7 +235,7 @@ class TestApiTokenEndpoints:
assert data["message"] == "API token revoked successfully" assert data["message"] == "API token revoked successfully"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_revoke_api_token_unauthenticated(self, client: AsyncClient): async def test_revoke_api_token_unauthenticated(self, client: AsyncClient) -> None:
"""Test revoking API token without authentication.""" """Test revoking API token without authentication."""
response = await client.delete("/api/v1/auth/api-token") response = await client.delete("/api/v1/auth/api-token")
assert response.status_code == 401 assert response.status_code == 401
@@ -249,7 +245,7 @@ class TestApiTokenEndpoints:
self, self,
client: AsyncClient, client: AsyncClient,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test successful authentication using API token.""" """Test successful authentication using API token."""
# Generate API token # Generate API token
token_response = await authenticated_client.post( token_response = await authenticated_client.post(
@@ -268,7 +264,7 @@ class TestApiTokenEndpoints:
assert "email" in data assert "email" in data
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_api_token_authentication_invalid_token(self, client: AsyncClient): async def test_api_token_authentication_invalid_token(self, client: AsyncClient) -> None:
"""Test authentication with invalid API token.""" """Test authentication with invalid API token."""
headers = {"API-TOKEN": "invalid_token"} headers = {"API-TOKEN": "invalid_token"}
response = await client.get("/api/v1/auth/me", headers=headers) response = await client.get("/api/v1/auth/me", headers=headers)
@@ -282,7 +278,7 @@ class TestApiTokenEndpoints:
self, self,
client: AsyncClient, client: AsyncClient,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
): ) -> None:
"""Test authentication with expired API token.""" """Test authentication with expired API token."""
# Generate API token # Generate API token
token_response = await authenticated_client.post( token_response = await authenticated_client.post(
@@ -301,7 +297,7 @@ class TestApiTokenEndpoints:
assert "API token has expired" in data["detail"] assert "API token has expired" in data["detail"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_api_token_authentication_empty_token(self, client: AsyncClient): async def test_api_token_authentication_empty_token(self, client: AsyncClient) -> None:
"""Test authentication with empty API-TOKEN header.""" """Test authentication with empty API-TOKEN header."""
# Empty token # Empty token
headers = {"API-TOKEN": ""} headers = {"API-TOKEN": ""}
@@ -325,7 +321,7 @@ class TestApiTokenEndpoints:
client: AsyncClient, client: AsyncClient,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test authentication with API token for inactive user.""" """Test authentication with API token for inactive user."""
# Generate API token # Generate API token
token_response = await authenticated_client.post( token_response = await authenticated_client.post(
@@ -351,7 +347,7 @@ class TestApiTokenEndpoints:
client: AsyncClient, client: AsyncClient,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
auth_cookies: dict[str, str], auth_cookies: dict[str, str],
): ) -> None:
"""Test that flexible authentication prefers API token over cookie.""" """Test that flexible authentication prefers API token over cookie."""
# Generate API token # Generate API token
token_response = await authenticated_client.post( token_response = await authenticated_client.post(

View File

@@ -11,7 +11,7 @@ class TestExtractionEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_extraction_success( async def test_create_extraction_success(
self, test_client: AsyncClient, auth_cookies: dict[str, str], self, test_client: AsyncClient, auth_cookies: dict[str, str],
): ) -> None:
"""Test successful extraction creation.""" """Test successful extraction creation."""
# Set cookies on client instance to avoid deprecation warning # Set cookies on client instance to avoid deprecation warning
test_client.cookies.update(auth_cookies) test_client.cookies.update(auth_cookies)
@@ -26,7 +26,7 @@ class TestExtractionEndpoints:
assert response.status_code in [200, 400, 500] # Allow any non-auth error assert response.status_code in [200, 400, 500] # Allow any non-auth error
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_extraction_unauthenticated(self, test_client: AsyncClient): async def test_create_extraction_unauthenticated(self, test_client: AsyncClient) -> None:
"""Test extraction creation without authentication.""" """Test extraction creation without authentication."""
response = await test_client.post( response = await test_client.post(
"/api/v1/sounds/extract", "/api/v1/sounds/extract",
@@ -37,7 +37,7 @@ class TestExtractionEndpoints:
assert response.status_code == 401 assert response.status_code == 401
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_extraction_unauthenticated(self, test_client: AsyncClient): async def test_get_extraction_unauthenticated(self, test_client: AsyncClient) -> None:
"""Test extraction retrieval without authentication.""" """Test extraction retrieval without authentication."""
response = await test_client.get("/api/v1/sounds/extract/1") response = await test_client.get("/api/v1/sounds/extract/1")
@@ -47,7 +47,7 @@ class TestExtractionEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_processor_status_admin( async def test_get_processor_status_admin(
self, test_client: AsyncClient, admin_cookies: dict[str, str], self, test_client: AsyncClient, admin_cookies: dict[str, str],
): ) -> None:
"""Test getting processor status as admin.""" """Test getting processor status as admin."""
# Set cookies on client instance to avoid deprecation warning # Set cookies on client instance to avoid deprecation warning
test_client.cookies.update(admin_cookies) test_client.cookies.update(admin_cookies)
@@ -63,7 +63,7 @@ class TestExtractionEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_processor_status_non_admin( async def test_get_processor_status_non_admin(
self, test_client: AsyncClient, auth_cookies: dict[str, str], self, test_client: AsyncClient, auth_cookies: dict[str, str],
): ) -> None:
"""Test getting processor status as non-admin user.""" """Test getting processor status as non-admin user."""
# Set cookies on client instance to avoid deprecation warning # Set cookies on client instance to avoid deprecation warning
test_client.cookies.update(auth_cookies) test_client.cookies.update(auth_cookies)
@@ -77,7 +77,7 @@ class TestExtractionEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_user_extractions( async def test_get_user_extractions(
self, test_client: AsyncClient, auth_cookies: dict[str, str], self, test_client: AsyncClient, auth_cookies: dict[str, str],
): ) -> None:
"""Test getting user extractions.""" """Test getting user extractions."""
# Set cookies on client instance to avoid deprecation warning # Set cookies on client instance to avoid deprecation warning
test_client.cookies.update(auth_cookies) test_client.cookies.update(auth_cookies)

View File

@@ -37,7 +37,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test starting playback successfully.""" """Test starting playback successfully."""
response = await authenticated_client.post("/api/v1/player/play") response = await authenticated_client.post("/api/v1/player/play")
@@ -48,7 +48,7 @@ class TestPlayerEndpoints:
mock_player_service.play.assert_called_once_with() mock_player_service.play.assert_called_once_with()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_play_unauthenticated(self, client: AsyncClient): async def test_play_unauthenticated(self, client: AsyncClient) -> None:
"""Test starting playback without authentication.""" """Test starting playback without authentication."""
response = await client.post("/api/v1/player/play") response = await client.post("/api/v1/player/play")
assert response.status_code == 401 assert response.status_code == 401
@@ -59,7 +59,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test starting playback with service error.""" """Test starting playback with service error."""
mock_player_service.play.side_effect = Exception("Service error") mock_player_service.play.side_effect = Exception("Service error")
@@ -75,7 +75,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test playing sound at specific index successfully.""" """Test playing sound at specific index successfully."""
index = 2 index = 2
response = await authenticated_client.post(f"/api/v1/player/play/{index}") response = await authenticated_client.post(f"/api/v1/player/play/{index}")
@@ -92,7 +92,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test playing sound with invalid index.""" """Test playing sound with invalid index."""
mock_player_service.play.side_effect = ValueError("Invalid sound index") mock_player_service.play.side_effect = ValueError("Invalid sound index")
@@ -108,7 +108,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test playing sound at index with service error.""" """Test playing sound at index with service error."""
mock_player_service.play.side_effect = Exception("Service error") mock_player_service.play.side_effect = Exception("Service error")
@@ -124,7 +124,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test pausing playback successfully.""" """Test pausing playback successfully."""
response = await authenticated_client.post("/api/v1/player/pause") response = await authenticated_client.post("/api/v1/player/pause")
@@ -135,7 +135,7 @@ class TestPlayerEndpoints:
mock_player_service.pause.assert_called_once() mock_player_service.pause.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_pause_unauthenticated(self, client: AsyncClient): async def test_pause_unauthenticated(self, client: AsyncClient) -> None:
"""Test pausing playback without authentication.""" """Test pausing playback without authentication."""
response = await client.post("/api/v1/player/pause") response = await client.post("/api/v1/player/pause")
assert response.status_code == 401 assert response.status_code == 401
@@ -146,7 +146,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test pausing playback with service error.""" """Test pausing playback with service error."""
mock_player_service.pause.side_effect = Exception("Service error") mock_player_service.pause.side_effect = Exception("Service error")
@@ -162,7 +162,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test stopping playback successfully.""" """Test stopping playback successfully."""
response = await authenticated_client.post("/api/v1/player/stop") response = await authenticated_client.post("/api/v1/player/stop")
@@ -173,7 +173,7 @@ class TestPlayerEndpoints:
mock_player_service.stop_playback.assert_called_once() mock_player_service.stop_playback.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_stop_unauthenticated(self, client: AsyncClient): async def test_stop_unauthenticated(self, client: AsyncClient) -> None:
"""Test stopping playback without authentication.""" """Test stopping playback without authentication."""
response = await client.post("/api/v1/player/stop") response = await client.post("/api/v1/player/stop")
assert response.status_code == 401 assert response.status_code == 401
@@ -184,7 +184,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test stopping playback with service error.""" """Test stopping playback with service error."""
mock_player_service.stop_playback.side_effect = Exception("Service error") mock_player_service.stop_playback.side_effect = Exception("Service error")
@@ -200,7 +200,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test skipping to next track successfully.""" """Test skipping to next track successfully."""
response = await authenticated_client.post("/api/v1/player/next") response = await authenticated_client.post("/api/v1/player/next")
@@ -211,7 +211,7 @@ class TestPlayerEndpoints:
mock_player_service.next.assert_called_once() mock_player_service.next.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_next_track_unauthenticated(self, client: AsyncClient): async def test_next_track_unauthenticated(self, client: AsyncClient) -> None:
"""Test skipping to next track without authentication.""" """Test skipping to next track without authentication."""
response = await client.post("/api/v1/player/next") response = await client.post("/api/v1/player/next")
assert response.status_code == 401 assert response.status_code == 401
@@ -222,7 +222,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test skipping to next track with service error.""" """Test skipping to next track with service error."""
mock_player_service.next.side_effect = Exception("Service error") mock_player_service.next.side_effect = Exception("Service error")
@@ -238,7 +238,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test going to previous track successfully.""" """Test going to previous track successfully."""
response = await authenticated_client.post("/api/v1/player/previous") response = await authenticated_client.post("/api/v1/player/previous")
@@ -249,7 +249,7 @@ class TestPlayerEndpoints:
mock_player_service.previous.assert_called_once() mock_player_service.previous.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_previous_track_unauthenticated(self, client: AsyncClient): async def test_previous_track_unauthenticated(self, client: AsyncClient) -> None:
"""Test going to previous track without authentication.""" """Test going to previous track without authentication."""
response = await client.post("/api/v1/player/previous") response = await client.post("/api/v1/player/previous")
assert response.status_code == 401 assert response.status_code == 401
@@ -260,7 +260,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test going to previous track with service error.""" """Test going to previous track with service error."""
mock_player_service.previous.side_effect = Exception("Service error") mock_player_service.previous.side_effect = Exception("Service error")
@@ -276,7 +276,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test seeking to position successfully.""" """Test seeking to position successfully."""
position = 5000 position = 5000
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -296,7 +296,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test seeking with invalid position.""" """Test seeking with invalid position."""
response = await authenticated_client.post( response = await authenticated_client.post(
"/api/v1/player/seek", "/api/v1/player/seek",
@@ -306,7 +306,7 @@ class TestPlayerEndpoints:
assert response.status_code == 422 # Validation error assert response.status_code == 422 # Validation error
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_seek_unauthenticated(self, client: AsyncClient): async def test_seek_unauthenticated(self, client: AsyncClient) -> None:
"""Test seeking without authentication.""" """Test seeking without authentication."""
response = await client.post( response = await client.post(
"/api/v1/player/seek", "/api/v1/player/seek",
@@ -320,7 +320,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test seeking with service error.""" """Test seeking with service error."""
mock_player_service.seek.side_effect = Exception("Service error") mock_player_service.seek.side_effect = Exception("Service error")
@@ -339,7 +339,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting volume successfully.""" """Test setting volume successfully."""
volume = 75 volume = 75
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -359,7 +359,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting volume with invalid range.""" """Test setting volume with invalid range."""
# Test volume too high # Test volume too high
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -376,7 +376,7 @@ class TestPlayerEndpoints:
assert response.status_code == 422 assert response.status_code == 422
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_set_volume_unauthenticated(self, client: AsyncClient): async def test_set_volume_unauthenticated(self, client: AsyncClient) -> None:
"""Test setting volume without authentication.""" """Test setting volume without authentication."""
response = await client.post( response = await client.post(
"/api/v1/player/volume", "/api/v1/player/volume",
@@ -390,7 +390,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting volume with service error.""" """Test setting volume with service error."""
mock_player_service.set_volume.side_effect = Exception("Service error") mock_player_service.set_volume.side_effect = Exception("Service error")
@@ -409,7 +409,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting playback mode successfully.""" """Test setting playback mode successfully."""
mode = PlayerMode.LOOP mode = PlayerMode.LOOP
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -429,7 +429,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting invalid playback mode.""" """Test setting invalid playback mode."""
response = await authenticated_client.post( response = await authenticated_client.post(
"/api/v1/player/mode", "/api/v1/player/mode",
@@ -439,7 +439,7 @@ class TestPlayerEndpoints:
assert response.status_code == 422 # Validation error assert response.status_code == 422 # Validation error
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_set_mode_unauthenticated(self, client: AsyncClient): async def test_set_mode_unauthenticated(self, client: AsyncClient) -> None:
"""Test setting mode without authentication.""" """Test setting mode without authentication."""
response = await client.post( response = await client.post(
"/api/v1/player/mode", "/api/v1/player/mode",
@@ -453,7 +453,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting mode with service error.""" """Test setting mode with service error."""
mock_player_service.set_mode.side_effect = Exception("Service error") mock_player_service.set_mode.side_effect = Exception("Service error")
@@ -472,7 +472,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test reloading playlist successfully.""" """Test reloading playlist successfully."""
response = await authenticated_client.post("/api/v1/player/reload-playlist") response = await authenticated_client.post("/api/v1/player/reload-playlist")
@@ -483,7 +483,7 @@ class TestPlayerEndpoints:
mock_player_service.reload_playlist.assert_called_once() mock_player_service.reload_playlist.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_reload_playlist_unauthenticated(self, client: AsyncClient): async def test_reload_playlist_unauthenticated(self, client: AsyncClient) -> None:
"""Test reloading playlist without authentication.""" """Test reloading playlist without authentication."""
response = await client.post("/api/v1/player/reload-playlist") response = await client.post("/api/v1/player/reload-playlist")
assert response.status_code == 401 assert response.status_code == 401
@@ -494,7 +494,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test reloading playlist with service error.""" """Test reloading playlist with service error."""
mock_player_service.reload_playlist.side_effect = Exception("Service error") mock_player_service.reload_playlist.side_effect = Exception("Service error")
@@ -510,7 +510,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test getting player state successfully.""" """Test getting player state successfully."""
mock_state = { mock_state = {
"status": PlayerStatus.PLAYING.value, "status": PlayerStatus.PLAYING.value,
@@ -548,7 +548,7 @@ class TestPlayerEndpoints:
mock_player_service.get_state.assert_called_once() mock_player_service.get_state.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_state_unauthenticated(self, client: AsyncClient): async def test_get_state_unauthenticated(self, client: AsyncClient) -> None:
"""Test getting player state without authentication.""" """Test getting player state without authentication."""
response = await client.get("/api/v1/player/state") response = await client.get("/api/v1/player/state")
assert response.status_code == 401 assert response.status_code == 401
@@ -559,7 +559,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test getting player state with service error.""" """Test getting player state with service error."""
mock_player_service.get_state.side_effect = Exception("Service error") mock_player_service.get_state.side_effect = Exception("Service error")
@@ -574,7 +574,7 @@ class TestPlayerEndpoints:
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test seeking without request body.""" """Test seeking without request body."""
response = await authenticated_client.post("/api/v1/player/seek") response = await authenticated_client.post("/api/v1/player/seek")
assert response.status_code == 422 assert response.status_code == 422
@@ -584,7 +584,7 @@ class TestPlayerEndpoints:
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test setting volume without request body.""" """Test setting volume without request body."""
response = await authenticated_client.post("/api/v1/player/volume") response = await authenticated_client.post("/api/v1/player/volume")
assert response.status_code == 422 assert response.status_code == 422
@@ -594,7 +594,7 @@ class TestPlayerEndpoints:
self, self,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test setting mode without request body.""" """Test setting mode without request body."""
response = await authenticated_client.post("/api/v1/player/mode") response = await authenticated_client.post("/api/v1/player/mode")
assert response.status_code == 422 assert response.status_code == 422
@@ -605,7 +605,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test playing sound with negative index.""" """Test playing sound with negative index."""
mock_player_service.play.side_effect = ValueError("Invalid sound index") mock_player_service.play.side_effect = ValueError("Invalid sound index")
@@ -621,7 +621,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test seeking to position zero.""" """Test seeking to position zero."""
response = await authenticated_client.post( response = await authenticated_client.post(
"/api/v1/player/seek", "/api/v1/player/seek",
@@ -640,7 +640,7 @@ class TestPlayerEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_player_service, mock_player_service,
): ) -> None:
"""Test setting volume with boundary values.""" """Test setting volume with boundary values."""
# Test minimum volume # Test minimum volume
response = await authenticated_client.post( response = await authenticated_client.post(

View File

@@ -27,7 +27,7 @@ class TestSocketEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_socket_manager, mock_socket_manager,
): ) -> None:
"""Test getting socket status for authenticated user.""" """Test getting socket status for authenticated user."""
response = await authenticated_client.get("/api/v1/socket/status") response = await authenticated_client.get("/api/v1/socket/status")
@@ -42,7 +42,7 @@ class TestSocketEndpoints:
assert isinstance(data["connected"], bool) assert isinstance(data["connected"], bool)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_socket_status_unauthenticated(self, client: AsyncClient): async def test_get_socket_status_unauthenticated(self, client: AsyncClient) -> None:
"""Test getting socket status without authentication.""" """Test getting socket status without authentication."""
response = await client.get("/api/v1/socket/status") response = await client.get("/api/v1/socket/status")
assert response.status_code == 401 assert response.status_code == 401
@@ -53,7 +53,7 @@ class TestSocketEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_socket_manager, mock_socket_manager,
): ) -> None:
"""Test sending message to specific user successfully.""" """Test sending message to specific user successfully."""
target_user_id = 2 target_user_id = 2
message = "Hello there!" message = "Hello there!"
@@ -87,7 +87,7 @@ class TestSocketEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_socket_manager, mock_socket_manager,
): ) -> None:
"""Test sending message to user who is not connected.""" """Test sending message to user who is not connected."""
target_user_id = 999 target_user_id = 999
message = "Hello there!" message = "Hello there!"
@@ -108,7 +108,7 @@ class TestSocketEndpoints:
assert data["message"] == "User not connected" assert data["message"] == "User not connected"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_message_unauthenticated(self, client: AsyncClient): async def test_send_message_unauthenticated(self, client: AsyncClient) -> None:
"""Test sending message without authentication.""" """Test sending message without authentication."""
response = await client.post( response = await client.post(
"/api/v1/socket/send-message", "/api/v1/socket/send-message",
@@ -122,7 +122,7 @@ class TestSocketEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_socket_manager, mock_socket_manager,
): ) -> None:
"""Test broadcasting message to all users successfully.""" """Test broadcasting message to all users successfully."""
message = "Important announcement!" message = "Important announcement!"
@@ -148,7 +148,7 @@ class TestSocketEndpoints:
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_broadcast_message_unauthenticated(self, client: AsyncClient): async def test_broadcast_message_unauthenticated(self, client: AsyncClient) -> None:
"""Test broadcasting message without authentication.""" """Test broadcasting message without authentication."""
response = await client.post( response = await client.post(
"/api/v1/socket/broadcast", "/api/v1/socket/broadcast",
@@ -159,7 +159,7 @@ class TestSocketEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_message_missing_parameters( async def test_send_message_missing_parameters(
self, authenticated_client: AsyncClient, authenticated_user: User, self, authenticated_client: AsyncClient, authenticated_user: User,
): ) -> None:
"""Test sending message with missing parameters.""" """Test sending message with missing parameters."""
# Missing target_user_id # Missing target_user_id
response = await authenticated_client.post( response = await authenticated_client.post(
@@ -178,7 +178,7 @@ class TestSocketEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_broadcast_message_missing_parameters( async def test_broadcast_message_missing_parameters(
self, authenticated_client: AsyncClient, authenticated_user: User, self, authenticated_client: AsyncClient, authenticated_user: User,
): ) -> None:
"""Test broadcasting message with missing parameters.""" """Test broadcasting message with missing parameters."""
response = await authenticated_client.post("/api/v1/socket/broadcast") response = await authenticated_client.post("/api/v1/socket/broadcast")
assert response.status_code == 422 assert response.status_code == 422
@@ -186,7 +186,7 @@ class TestSocketEndpoints:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_message_invalid_user_id( async def test_send_message_invalid_user_id(
self, authenticated_client: AsyncClient, authenticated_user: User, self, authenticated_client: AsyncClient, authenticated_user: User,
): ) -> None:
"""Test sending message with invalid user ID.""" """Test sending message with invalid user ID."""
response = await authenticated_client.post( response = await authenticated_client.post(
"/api/v1/socket/send-message", "/api/v1/socket/send-message",
@@ -200,7 +200,7 @@ class TestSocketEndpoints:
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
mock_socket_manager, mock_socket_manager,
): ) -> None:
"""Test that socket status correctly shows if user is connected.""" """Test that socket status correctly shows if user is connected."""
# Test when user is connected # Test when user is connected
mock_socket_manager.get_connected_users.return_value = [ mock_socket_manager.get_connected_users.return_value = [

View File

@@ -1,13 +1,16 @@
"""Tests for sound API endpoints.""" """Tests for sound API endpoints."""
from typing import TYPE_CHECKING
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from httpx import ASGITransport, AsyncClient from httpx import ASGITransport, AsyncClient
from app.models.user import User from app.models.user import User
from app.services.sound_normalizer import NormalizationResults
from app.services.sound_scanner import ScanResults if TYPE_CHECKING:
from app.services.sound_normalizer import NormalizationResults
from app.services.sound_scanner import ScanResults
class TestSoundEndpoints: class TestSoundEndpoints:
@@ -18,7 +21,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test successful sound scanning.""" """Test successful sound scanning."""
# Mock the scanner service to return successful results # Mock the scanner service to return successful results
mock_results: ScanResults = { mock_results: ScanResults = {
@@ -95,7 +98,7 @@ class TestSoundEndpoints:
assert results["files"][2]["status"] == "deleted" assert results["files"][2]["status"] == "deleted"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_scan_sounds_unauthenticated(self, client: AsyncClient): async def test_scan_sounds_unauthenticated(self, client: AsyncClient) -> None:
"""Test scanning sounds without authentication.""" """Test scanning sounds without authentication."""
response = await client.post("/api/v1/sounds/scan") response = await client.post("/api/v1/sounds/scan")
@@ -108,7 +111,7 @@ class TestSoundEndpoints:
self, self,
test_app, test_app,
test_user: User, test_user: User,
): ) -> None:
"""Test scanning sounds with non-admin user.""" """Test scanning sounds with non-admin user."""
from app.core.dependencies import get_current_active_user_flexible from app.core.dependencies import get_current_active_user_flexible
@@ -142,7 +145,7 @@ class TestSoundEndpoints:
self, self,
test_app, test_app,
admin_user: User, admin_user: User,
): ) -> None:
"""Test scanning sounds with admin user.""" """Test scanning sounds with admin user."""
from app.core.dependencies import get_current_active_user_flexible from app.core.dependencies import get_current_active_user_flexible
@@ -189,7 +192,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test scanning sounds when service raises an error.""" """Test scanning sounds when service raises an error."""
with patch( with patch(
"app.services.sound_scanner.SoundScannerService.scan_soundboard_directory", "app.services.sound_scanner.SoundScannerService.scan_soundboard_directory",
@@ -208,7 +211,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test successful custom directory scanning.""" """Test successful custom directory scanning."""
mock_results: ScanResults = { mock_results: ScanResults = {
"scanned": 2, "scanned": 2,
@@ -272,7 +275,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test custom directory scanning with default sound type.""" """Test custom directory scanning with default sound type."""
mock_results: ScanResults = { mock_results: ScanResults = {
"scanned": 1, "scanned": 1,
@@ -304,7 +307,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test custom directory scanning with invalid path.""" """Test custom directory scanning with invalid path."""
with patch( with patch(
"app.services.sound_scanner.SoundScannerService.scan_directory", "app.services.sound_scanner.SoundScannerService.scan_directory",
@@ -322,7 +325,7 @@ class TestSoundEndpoints:
assert "Directory does not exist" in data["detail"] assert "Directory does not exist" in data["detail"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_scan_custom_directory_unauthenticated(self, client: AsyncClient): async def test_scan_custom_directory_unauthenticated(self, client: AsyncClient) -> None:
"""Test custom directory scanning without authentication.""" """Test custom directory scanning without authentication."""
response = await client.post( response = await client.post(
"/api/v1/sounds/scan/custom", params={"directory": "/some/path"}, "/api/v1/sounds/scan/custom", params={"directory": "/some/path"},
@@ -337,7 +340,7 @@ class TestSoundEndpoints:
self, self,
test_app, test_app,
test_user: User, test_user: User,
): ) -> None:
"""Test custom directory scanning with non-admin user.""" """Test custom directory scanning with non-admin user."""
from app.core.dependencies import get_current_active_user_flexible from app.core.dependencies import get_current_active_user_flexible
@@ -374,7 +377,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test custom directory scanning when service raises an error.""" """Test custom directory scanning when service raises an error."""
with patch( with patch(
"app.services.sound_scanner.SoundScannerService.scan_directory", "app.services.sound_scanner.SoundScannerService.scan_directory",
@@ -395,7 +398,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test scanning with some errors in results.""" """Test scanning with some errors in results."""
mock_results: ScanResults = { mock_results: ScanResults = {
"scanned": 3, "scanned": 3,
@@ -467,7 +470,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test that endpoint response has correct structure.""" """Test that endpoint response has correct structure."""
mock_results: ScanResults = { mock_results: ScanResults = {
"scanned": 0, "scanned": 0,
@@ -519,7 +522,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test successful normalization of all sounds.""" """Test successful normalization of all sounds."""
mock_results: NormalizationResults = { mock_results: NormalizationResults = {
"processed": 3, "processed": 3,
@@ -597,7 +600,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization with force parameter.""" """Test normalization with force parameter."""
mock_results: NormalizationResults = { mock_results: NormalizationResults = {
"processed": 1, "processed": 1,
@@ -626,7 +629,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization with one_pass parameter.""" """Test normalization with one_pass parameter."""
mock_results: NormalizationResults = { mock_results: NormalizationResults = {
"processed": 1, "processed": 1,
@@ -651,7 +654,7 @@ class TestSoundEndpoints:
mock_normalize.assert_called_once_with(force=False, one_pass=True) mock_normalize.assert_called_once_with(force=False, one_pass=True)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_all_sounds_unauthenticated(self, client: AsyncClient): async def test_normalize_all_sounds_unauthenticated(self, client: AsyncClient) -> None:
"""Test normalizing sounds without authentication.""" """Test normalizing sounds without authentication."""
response = await client.post("/api/v1/sounds/normalize/all") response = await client.post("/api/v1/sounds/normalize/all")
@@ -664,7 +667,7 @@ class TestSoundEndpoints:
self, self,
test_app, test_app,
test_user: User, test_user: User,
): ) -> None:
"""Test normalizing sounds with non-admin user.""" """Test normalizing sounds with non-admin user."""
from app.core.dependencies import get_current_active_user_flexible from app.core.dependencies import get_current_active_user_flexible
@@ -699,7 +702,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization when service raises an error.""" """Test normalization when service raises an error."""
with patch( with patch(
"app.services.sound_normalizer.SoundNormalizerService.normalize_all_sounds", "app.services.sound_normalizer.SoundNormalizerService.normalize_all_sounds",
@@ -720,7 +723,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test successful normalization by sound type.""" """Test successful normalization by sound type."""
mock_results: NormalizationResults = { mock_results: NormalizationResults = {
"processed": 2, "processed": 2,
@@ -787,7 +790,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization with invalid sound type.""" """Test normalization with invalid sound type."""
response = await authenticated_admin_client.post( response = await authenticated_admin_client.post(
"/api/v1/sounds/normalize/type/INVALID", "/api/v1/sounds/normalize/type/INVALID",
@@ -803,7 +806,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization by type with force and one_pass parameters.""" """Test normalization by type with force and one_pass parameters."""
mock_results: NormalizationResults = { mock_results: NormalizationResults = {
"processed": 1, "processed": 1,
@@ -835,7 +838,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test successful normalization of a specific sound.""" """Test successful normalization of a specific sound."""
# Mock the sound # Mock the sound
mock_sound = type( mock_sound = type(
@@ -894,7 +897,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization of non-existent sound.""" """Test normalization of non-existent sound."""
with patch( with patch(
"app.repositories.sound.SoundRepository.get_by_id", "app.repositories.sound.SoundRepository.get_by_id",
@@ -914,7 +917,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization when the sound normalization fails.""" """Test normalization when the sound normalization fails."""
# Mock the sound # Mock the sound
mock_sound = type( mock_sound = type(
@@ -966,7 +969,7 @@ class TestSoundEndpoints:
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test sound normalization with force and one_pass parameters.""" """Test sound normalization with force and one_pass parameters."""
# Mock the sound # Mock the sound
mock_sound = type( mock_sound = type(
@@ -1013,15 +1016,15 @@ class TestSoundEndpoints:
# Verify parameters were passed to normalize_sound # Verify parameters were passed to normalize_sound
call_args = mock_normalize_sound.call_args call_args = mock_normalize_sound.call_args
assert call_args[1]["force"] == True assert call_args[1]["force"]
assert call_args[1]["one_pass"] == True assert call_args[1]["one_pass"]
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_by_id_skipped( async def test_normalize_sound_by_id_skipped(
self, self,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test normalization when sound is already normalized and not forced.""" """Test normalization when sound is already normalized and not forced."""
# Mock the sound # Mock the sound
mock_sound = type( mock_sound = type(
@@ -1071,7 +1074,7 @@ class TestSoundEndpoints:
assert data["reason"] == "already normalized" assert data["reason"] == "already normalized"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_by_id_unauthenticated(self, client: AsyncClient): async def test_normalize_sound_by_id_unauthenticated(self, client: AsyncClient) -> None:
"""Test normalizing a specific sound without authentication.""" """Test normalizing a specific sound without authentication."""
response = await client.post("/api/v1/sounds/normalize/42") response = await client.post("/api/v1/sounds/normalize/42")
@@ -1084,7 +1087,7 @@ class TestSoundEndpoints:
self, self,
test_app, test_app,
test_user: User, test_user: User,
): ) -> None:
"""Test normalizing a specific sound with non-admin user.""" """Test normalizing a specific sound with non-admin user."""
from app.core.dependencies import get_current_active_user_flexible from app.core.dependencies import get_current_active_user_flexible

View File

@@ -20,7 +20,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test successful sound playback via VLC.""" """Test successful sound playback via VLC."""
# Set up mocks # Set up mocks
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -73,7 +73,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test VLC playback when sound is not found.""" """Test VLC playback when sound is not found."""
# Set up mocks # Set up mocks
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -106,7 +106,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test VLC playback when VLC launch fails.""" """Test VLC playback when VLC launch fails."""
# Set up mocks # Set up mocks
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -153,7 +153,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test VLC playback when service raises an exception.""" """Test VLC playback when service raises an exception."""
# Set up mocks # Set up mocks
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -184,7 +184,7 @@ class TestVLCEndpoints:
async def test_play_sound_with_vlc_unauthenticated( async def test_play_sound_with_vlc_unauthenticated(
self, self,
client: AsyncClient, client: AsyncClient,
): ) -> None:
"""Test VLC playback without authentication.""" """Test VLC playback without authentication."""
response = await client.post("/api/v1/sounds/vlc/play/1") response = await client.post("/api/v1/sounds/vlc/play/1")
assert response.status_code == 401 assert response.status_code == 401
@@ -195,7 +195,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test successful stopping of all VLC instances.""" """Test successful stopping of all VLC instances."""
# Set up mock # Set up mock
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -234,7 +234,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test stopping VLC instances when none are running.""" """Test stopping VLC instances when none are running."""
# Set up mock # Set up mock
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -268,7 +268,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test stopping VLC instances with partial success.""" """Test stopping VLC instances with partial success."""
# Set up mock # Set up mock
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -303,7 +303,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test stopping VLC instances when service fails.""" """Test stopping VLC instances when service fails."""
# Set up mock # Set up mock
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -337,7 +337,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_client: AsyncClient, authenticated_client: AsyncClient,
authenticated_user: User, authenticated_user: User,
): ) -> None:
"""Test stopping VLC instances when service raises an exception.""" """Test stopping VLC instances when service raises an exception."""
# Set up mock to raise an exception # Set up mock to raise an exception
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()
@@ -360,7 +360,7 @@ class TestVLCEndpoints:
async def test_stop_all_vlc_instances_unauthenticated( async def test_stop_all_vlc_instances_unauthenticated(
self, self,
client: AsyncClient, client: AsyncClient,
): ) -> None:
"""Test stopping VLC instances without authentication.""" """Test stopping VLC instances without authentication."""
response = await client.post("/api/v1/sounds/vlc/stop-all") response = await client.post("/api/v1/sounds/vlc/stop-all")
assert response.status_code == 401 assert response.status_code == 401
@@ -371,7 +371,7 @@ class TestVLCEndpoints:
test_app: FastAPI, test_app: FastAPI,
authenticated_admin_client: AsyncClient, authenticated_admin_client: AsyncClient,
admin_user: User, admin_user: User,
): ) -> None:
"""Test VLC endpoints work with admin user.""" """Test VLC endpoints work with admin user."""
# Set up mocks # Set up mocks
mock_vlc_service = AsyncMock() mock_vlc_service = AsyncMock()

View File

@@ -7,12 +7,15 @@ from typing import Any
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from httpx import ASGITransport, AsyncClient from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.ext.asyncio import create_async_engine
from sqlmodel import SQLModel, select from sqlmodel import SQLModel, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.api import api_router
from app.core.database import get_db from app.core.database import get_db
from app.middleware.logging import LoggingMiddleware
from app.models.plan import Plan from app.models.plan import Plan
from app.models.user import User from app.models.user import User
from app.utils.auth import JWTUtils, PasswordUtils from app.utils.auth import JWTUtils, PasswordUtils
@@ -61,14 +64,8 @@ async def test_session(test_engine: Any) -> AsyncGenerator[AsyncSession, None]:
@pytest_asyncio.fixture @pytest_asyncio.fixture
async def test_app(test_session: AsyncSession): async def test_app(test_session: AsyncSession) -> FastAPI:
"""Create a test FastAPI application.""" """Create a test FastAPI application."""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api import api_router
from app.middleware.logging import LoggingMiddleware
# Create FastAPI app directly for testing (without Socket.IO) # Create FastAPI app directly for testing (without Socket.IO)
app = FastAPI() app = FastAPI()

View File

@@ -1,5 +1,4 @@
"""Tests for API token authentication dependencies.""" """Tests for API token authentication dependencies."""
# ruff: noqa: S106
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from unittest.mock import AsyncMock from unittest.mock import AsyncMock

View File

@@ -1,5 +1,4 @@
"""Tests for credit transaction repository.""" """Tests for credit transaction repository."""
# ruff: noqa: ARG002, E501
import json import json
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator

View File

@@ -1,5 +1,4 @@
"""Tests for extraction repository.""" """Tests for extraction repository."""
# ruff: noqa: ANN001, ANN201
from unittest.mock import AsyncMock, Mock from unittest.mock import AsyncMock, Mock

View File

@@ -1,5 +1,4 @@
"""Tests for playlist repository.""" """Tests for playlist repository."""
# ruff: noqa: PLR2004, ANN401
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator
from typing import Any from typing import Any

View File

@@ -1,5 +1,4 @@
"""Tests for sound repository.""" """Tests for sound repository."""
# ruff: noqa: ARG002, PLR2004
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator

View File

@@ -1,5 +1,4 @@
"""Tests for user repository.""" """Tests for user repository."""
# ruff: noqa: ARG002
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator

View File

@@ -1,5 +1,4 @@
"""Tests for user OAuth repository.""" """Tests for user OAuth repository."""
# ruff: noqa: ARG002
from collections.abc import AsyncGenerator from collections.abc import AsyncGenerator

View File

@@ -10,6 +10,11 @@ from app.models.user import User
from app.schemas.auth import UserLoginRequest, UserRegisterRequest from app.schemas.auth import UserLoginRequest, UserRegisterRequest
from app.services.auth import AuthService from app.services.auth import AuthService
# Constants
HTTP_400_BAD_REQUEST = 400
HTTP_401_UNAUTHORIZED = 401
HTTP_404_NOT_FOUND = 404
class TestAuthService: class TestAuthService:
"""Test authentication service operations.""" """Test authentication service operations."""
@@ -62,7 +67,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.register(request) await auth_service.register(request)
assert exc_info.value.status_code == 400 assert exc_info.value.status_code == HTTP_400_BAD_REQUEST
assert "Email address is already registered" in exc_info.value.detail assert "Email address is already registered" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -100,7 +105,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.login(request) await auth_service.login(request)
assert exc_info.value.status_code == 401 assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Invalid email or password" in exc_info.value.detail assert "Invalid email or password" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -115,7 +120,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.login(request) await auth_service.login(request)
assert exc_info.value.status_code == 401 assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Invalid email or password" in exc_info.value.detail assert "Invalid email or password" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -138,7 +143,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.login(request) await auth_service.login(request)
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 assert "Account is deactivated" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -161,7 +166,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.login(request) await auth_service.login(request)
assert exc_info.value.status_code == 401 assert exc_info.value.status_code == HTTP_401_UNAUTHORIZED
assert "Invalid email or password" in exc_info.value.detail assert "Invalid email or password" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -184,7 +189,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.get_current_user(99999) await auth_service.get_current_user(99999)
assert exc_info.value.status_code == 404 assert exc_info.value.status_code == HTTP_404_NOT_FOUND
assert "User not found" in exc_info.value.detail assert "User not found" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio
@@ -205,7 +210,7 @@ class TestAuthService:
with pytest.raises(HTTPException) as exc_info: with pytest.raises(HTTPException) as exc_info:
await auth_service.get_current_user(user_id) await auth_service.get_current_user(user_id)
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 assert "Account is deactivated" in exc_info.value.detail
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@@ -39,7 +39,7 @@ class TestCreditService:
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_check_credits_sufficient(self, credit_service, sample_user): async def test_check_credits_sufficient(self, credit_service, sample_user) -> None:
"""Test checking credits when user has sufficient credits.""" """Test checking credits when user has sufficient credits."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -55,7 +55,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_check_credits_insufficient(self, credit_service): async def test_check_credits_insufficient(self, credit_service) -> None:
"""Test checking credits when user has insufficient credits.""" """Test checking credits when user has insufficient credits."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
poor_user = User( poor_user = User(
@@ -78,7 +78,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_check_credits_user_not_found(self, credit_service): async def test_check_credits_user_not_found(self, credit_service) -> None:
"""Test checking credits when user is not found.""" """Test checking credits when user is not found."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -93,7 +93,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_validate_and_reserve_credits_success(self, credit_service, sample_user): async def test_validate_and_reserve_credits_success(self, credit_service, sample_user) -> None:
"""Test successful credit validation and reservation.""" """Test successful credit validation and reservation."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -112,7 +112,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_validate_and_reserve_credits_insufficient(self, credit_service): async def test_validate_and_reserve_credits_insufficient(self, credit_service) -> None:
"""Test credit validation with insufficient credits.""" """Test credit validation with insufficient credits."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
poor_user = User( poor_user = User(
@@ -139,7 +139,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_validate_and_reserve_credits_user_not_found(self, credit_service): async def test_validate_and_reserve_credits_user_not_found(self, credit_service) -> None:
"""Test credit validation when user is not found.""" """Test credit validation when user is not found."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -156,7 +156,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_deduct_credits_success(self, credit_service, sample_user): async def test_deduct_credits_success(self, credit_service, sample_user) -> None:
"""Test successful credit deduction.""" """Test successful credit deduction."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -167,7 +167,7 @@ class TestCreditService:
mock_repo.get_by_id.return_value = sample_user mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock() mock_socket_manager.send_to_user = AsyncMock()
transaction = await credit_service.deduct_credits( await credit_service.deduct_credits(
1, CreditActionType.VLC_PLAY_SOUND, success=True, metadata={"test": "data"}, 1, CreditActionType.VLC_PLAY_SOUND, success=True, metadata={"test": "data"},
) )
@@ -202,7 +202,7 @@ class TestCreditService:
assert json.loads(added_transaction.metadata_json) == {"test": "data"} assert json.loads(added_transaction.metadata_json) == {"test": "data"}
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_deduct_credits_failed_action_requires_success(self, credit_service, sample_user): async def test_deduct_credits_failed_action_requires_success(self, credit_service, sample_user) -> None:
"""Test credit deduction when action failed but requires success.""" """Test credit deduction when action failed but requires success."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -213,7 +213,7 @@ class TestCreditService:
mock_repo.get_by_id.return_value = sample_user mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock() mock_socket_manager.send_to_user = AsyncMock()
transaction = await credit_service.deduct_credits( await credit_service.deduct_credits(
1, CreditActionType.VLC_PLAY_SOUND, success=False, # Action failed 1, CreditActionType.VLC_PLAY_SOUND, success=False, # Action failed
) )
@@ -235,7 +235,7 @@ class TestCreditService:
assert added_transaction.success is False assert added_transaction.success is False
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_deduct_credits_insufficient(self, credit_service): async def test_deduct_credits_insufficient(self, credit_service) -> None:
"""Test credit deduction with insufficient credits.""" """Test credit deduction with insufficient credits."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
poor_user = User( poor_user = User(
@@ -266,7 +266,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_add_credits(self, credit_service, sample_user): async def test_add_credits(self, credit_service, sample_user) -> None:
"""Test adding credits to user account.""" """Test adding credits to user account."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -277,7 +277,7 @@ class TestCreditService:
mock_repo.get_by_id.return_value = sample_user mock_repo.get_by_id.return_value = sample_user
mock_socket_manager.send_to_user = AsyncMock() mock_socket_manager.send_to_user = AsyncMock()
transaction = await credit_service.add_credits( await credit_service.add_credits(
1, 5, "Bonus credits", {"reason": "signup"}, 1, 5, "Bonus credits", {"reason": "signup"},
) )
@@ -308,7 +308,7 @@ class TestCreditService:
assert added_transaction.description == "Bonus credits" assert added_transaction.description == "Bonus credits"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_add_credits_invalid_amount(self, credit_service): async def test_add_credits_invalid_amount(self, credit_service) -> None:
"""Test adding invalid amount of credits.""" """Test adding invalid amount of credits."""
with pytest.raises(ValueError, match="Amount must be positive"): with pytest.raises(ValueError, match="Amount must be positive"):
await credit_service.add_credits(1, 0, "Invalid") await credit_service.add_credits(1, 0, "Invalid")
@@ -317,7 +317,7 @@ class TestCreditService:
await credit_service.add_credits(1, -5, "Invalid") await credit_service.add_credits(1, -5, "Invalid")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_user_balance(self, credit_service, sample_user): async def test_get_user_balance(self, credit_service, sample_user) -> None:
"""Test getting user credit balance.""" """Test getting user credit balance."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -332,7 +332,7 @@ class TestCreditService:
mock_session.close.assert_called_once() mock_session.close.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_user_balance_user_not_found(self, credit_service): async def test_get_user_balance_user_not_found(self, credit_service) -> None:
"""Test getting balance for non-existent user.""" """Test getting balance for non-existent user."""
mock_session = credit_service.db_session_factory() mock_session = credit_service.db_session_factory()
@@ -350,7 +350,7 @@ class TestCreditService:
class TestInsufficientCreditsError: class TestInsufficientCreditsError:
"""Test InsufficientCreditsError exception.""" """Test InsufficientCreditsError exception."""
def test_insufficient_credits_error_creation(self): def test_insufficient_credits_error_creation(self) -> None:
"""Test creating InsufficientCreditsError.""" """Test creating InsufficientCreditsError."""
error = InsufficientCreditsError(5, 2) error = InsufficientCreditsError(5, 2)
assert error.required == 5 assert error.required == 5

View File

@@ -26,13 +26,13 @@ class TestExtractionService:
with patch("app.services.extraction.Path.mkdir"): with patch("app.services.extraction.Path.mkdir"):
return ExtractionService(mock_session) return ExtractionService(mock_session)
def test_init(self, extraction_service): def test_init(self, extraction_service) -> None:
"""Test service initialization.""" """Test service initialization."""
assert extraction_service.session is not None assert extraction_service.session is not None
assert extraction_service.extraction_repo is not None assert extraction_service.extraction_repo is not None
assert extraction_service.sound_repo is not None assert extraction_service.sound_repo is not None
def test_sanitize_filename(self, extraction_service): def test_sanitize_filename(self, extraction_service) -> None:
"""Test filename sanitization.""" """Test filename sanitization."""
test_cases = [ test_cases = [
("Hello World", "Hello World"), ("Hello World", "Hello World"),
@@ -54,7 +54,7 @@ class TestExtractionService:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_detect_service_info_youtube( async def test_detect_service_info_youtube(
self, mock_ydl_class, extraction_service, self, mock_ydl_class, extraction_service,
): ) -> None:
"""Test service detection for YouTube.""" """Test service detection for YouTube."""
mock_ydl = Mock() mock_ydl = Mock()
mock_ydl_class.return_value.__enter__.return_value = mock_ydl mock_ydl_class.return_value.__enter__.return_value = mock_ydl
@@ -79,7 +79,7 @@ class TestExtractionService:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_detect_service_info_failure( async def test_detect_service_info_failure(
self, mock_ydl_class, extraction_service, self, mock_ydl_class, extraction_service,
): ) -> None:
"""Test service detection failure.""" """Test service detection failure."""
mock_ydl = Mock() mock_ydl = Mock()
mock_ydl_class.return_value.__enter__.return_value = mock_ydl mock_ydl_class.return_value.__enter__.return_value = mock_ydl
@@ -90,7 +90,7 @@ class TestExtractionService:
assert result is None assert result is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_extraction_success(self, extraction_service): async def test_create_extraction_success(self, extraction_service) -> None:
"""Test successful extraction creation.""" """Test successful extraction creation."""
url = "https://www.youtube.com/watch?v=test123" url = "https://www.youtube.com/watch?v=test123"
user_id = 1 user_id = 1
@@ -118,7 +118,7 @@ class TestExtractionService:
assert result["status"] == "pending" assert result["status"] == "pending"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_extraction_basic(self, extraction_service): async def test_create_extraction_basic(self, extraction_service) -> None:
"""Test basic extraction creation without validation.""" """Test basic extraction creation without validation."""
url = "https://www.youtube.com/watch?v=test123" url = "https://www.youtube.com/watch?v=test123"
user_id = 1 user_id = 1
@@ -144,7 +144,7 @@ class TestExtractionService:
assert result["status"] == "pending" assert result["status"] == "pending"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_extraction_any_url(self, extraction_service): async def test_create_extraction_any_url(self, extraction_service) -> None:
"""Test extraction creation accepts any URL.""" """Test extraction creation accepts any URL."""
url = "https://invalid.url" url = "https://invalid.url"
user_id = 1 user_id = 1
@@ -170,7 +170,7 @@ class TestExtractionService:
assert result["status"] == "pending" assert result["status"] == "pending"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_extraction_with_service_detection(self, extraction_service): async def test_process_extraction_with_service_detection(self, extraction_service) -> None:
"""Test extraction processing with service detection.""" """Test extraction processing with service detection."""
extraction_id = 1 extraction_id = 1
@@ -211,8 +211,8 @@ class TestExtractionService:
patch.object( patch.object(
extraction_service, "_create_sound_record", extraction_service, "_create_sound_record",
) as mock_create_sound, ) as mock_create_sound,
patch.object(extraction_service, "_normalize_sound") as mock_normalize, patch.object(extraction_service, "_normalize_sound"),
patch.object(extraction_service, "_add_to_main_playlist") as mock_playlist, patch.object(extraction_service, "_add_to_main_playlist"),
): ):
mock_sound = Sound(id=42, type="EXT", name="Test", filename="test.mp3") mock_sound = Sound(id=42, type="EXT", name="Test", filename="test.mp3")
mock_extract.return_value = (Path("/fake/audio.mp3"), None) mock_extract.return_value = (Path("/fake/audio.mp3"), None)
@@ -234,7 +234,7 @@ class TestExtractionService:
assert result["service_id"] == "test123" assert result["service_id"] == "test123"
assert result["title"] == "Test Video" assert result["title"] == "Test Video"
def test_ensure_unique_filename(self, extraction_service): def test_ensure_unique_filename(self, extraction_service) -> None:
"""Test unique filename generation.""" """Test unique filename generation."""
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) temp_path = Path(temp_dir)
@@ -255,7 +255,7 @@ class TestExtractionService:
assert result == expected_2 assert result == expected_2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_create_sound_record(self, extraction_service): async def test_create_sound_record(self, extraction_service) -> None:
"""Test sound record creation.""" """Test sound record creation."""
# Create temporary audio file # Create temporary audio file
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
@@ -317,7 +317,7 @@ class TestExtractionService:
audio_path.unlink() audio_path.unlink()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_success(self, extraction_service): async def test_normalize_sound_success(self, extraction_service) -> None:
"""Test sound normalization.""" """Test sound normalization."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -349,7 +349,7 @@ class TestExtractionService:
mock_normalizer.normalize_sound.assert_called_once_with(sound) mock_normalizer.normalize_sound.assert_called_once_with(sound)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_failure(self, extraction_service): async def test_normalize_sound_failure(self, extraction_service) -> None:
"""Test sound normalization failure.""" """Test sound normalization failure."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -381,7 +381,7 @@ class TestExtractionService:
mock_normalizer.normalize_sound.assert_called_once_with(sound) mock_normalizer.normalize_sound.assert_called_once_with(sound)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_extraction_by_id(self, extraction_service): async def test_get_extraction_by_id(self, extraction_service) -> None:
"""Test getting extraction by ID.""" """Test getting extraction by ID."""
extraction = Extraction( extraction = Extraction(
id=1, id=1,
@@ -409,7 +409,7 @@ class TestExtractionService:
assert result["sound_id"] == 42 assert result["sound_id"] == 42
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_extraction_by_id_not_found(self, extraction_service): async def test_get_extraction_by_id_not_found(self, extraction_service) -> None:
"""Test getting extraction by ID when not found.""" """Test getting extraction by ID when not found."""
extraction_service.extraction_repo.get_by_id = AsyncMock(return_value=None) extraction_service.extraction_repo.get_by_id = AsyncMock(return_value=None)
@@ -418,7 +418,7 @@ class TestExtractionService:
assert result is None assert result is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_user_extractions(self, extraction_service): async def test_get_user_extractions(self, extraction_service) -> None:
"""Test getting user extractions.""" """Test getting user extractions."""
extractions = [ extractions = [
Extraction( Extraction(
@@ -455,7 +455,7 @@ class TestExtractionService:
assert result[1]["title"] == "Test Video 2" assert result[1]["title"] == "Test Video 2"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_pending_extractions(self, extraction_service): async def test_get_pending_extractions(self, extraction_service) -> None:
"""Test getting pending extractions.""" """Test getting pending extractions."""
pending_extractions = [ pending_extractions = [
Extraction( Extraction(

View File

@@ -16,7 +16,7 @@ class TestExtractionProcessor:
# Use a custom processor instance to avoid affecting the global one # Use a custom processor instance to avoid affecting the global one
return ExtractionProcessor() return ExtractionProcessor()
def test_init(self, processor): def test_init(self, processor) -> None:
"""Test processor initialization.""" """Test processor initialization."""
assert processor.max_concurrent > 0 assert processor.max_concurrent > 0
assert len(processor.running_extractions) == 0 assert len(processor.running_extractions) == 0
@@ -25,12 +25,12 @@ class TestExtractionProcessor:
assert processor.processor_task is None assert processor.processor_task is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_start_and_stop(self, processor): async def test_start_and_stop(self, processor) -> None:
"""Test starting and stopping the processor.""" """Test starting and stopping the processor."""
# Mock the _process_queue method to avoid actual processing # Mock the _process_queue method to avoid actual processing
with patch.object( with patch.object(
processor, "_process_queue", new_callable=AsyncMock, processor, "_process_queue", new_callable=AsyncMock,
) as mock_process: ):
# Start the processor # Start the processor
await processor.start() await processor.start()
assert processor.processor_task is not None assert processor.processor_task is not None
@@ -41,7 +41,7 @@ class TestExtractionProcessor:
assert processor.processor_task.done() assert processor.processor_task.done()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_start_already_running(self, processor): async def test_start_already_running(self, processor) -> None:
"""Test starting processor when already running.""" """Test starting processor when already running."""
with patch.object(processor, "_process_queue", new_callable=AsyncMock): with patch.object(processor, "_process_queue", new_callable=AsyncMock):
# Start first time # Start first time
@@ -56,7 +56,7 @@ class TestExtractionProcessor:
await processor.stop() await processor.stop()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_queue_extraction(self, processor): async def test_queue_extraction(self, processor) -> None:
"""Test queuing an extraction.""" """Test queuing an extraction."""
extraction_id = 123 extraction_id = 123
@@ -66,7 +66,7 @@ class TestExtractionProcessor:
assert extraction_id not in processor.running_extractions assert extraction_id not in processor.running_extractions
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_queue_extraction_already_running(self, processor): async def test_queue_extraction_already_running(self, processor) -> None:
"""Test queuing an extraction that's already running.""" """Test queuing an extraction that's already running."""
extraction_id = 123 extraction_id = 123
processor.running_extractions.add(extraction_id) processor.running_extractions.add(extraction_id)
@@ -75,7 +75,7 @@ class TestExtractionProcessor:
# Should still be in running extractions # Should still be in running extractions
assert extraction_id in processor.running_extractions assert extraction_id in processor.running_extractions
def test_get_status(self, processor): def test_get_status(self, processor) -> None:
"""Test getting processor status.""" """Test getting processor status."""
status = processor.get_status() status = processor.get_status()
@@ -89,7 +89,7 @@ class TestExtractionProcessor:
assert status["currently_processing"] == 0 assert status["currently_processing"] == 0
assert status["available_slots"] == processor.max_concurrent assert status["available_slots"] == processor.max_concurrent
def test_get_status_with_running_extractions(self, processor): def test_get_status_with_running_extractions(self, processor) -> None:
"""Test getting processor status with running extractions.""" """Test getting processor status with running extractions."""
processor.running_extractions.add(123) processor.running_extractions.add(123)
processor.running_extractions.add(456) processor.running_extractions.add(456)
@@ -101,7 +101,7 @@ class TestExtractionProcessor:
assert 123 in status["processing_ids"] assert 123 in status["processing_ids"]
assert 456 in status["processing_ids"] assert 456 in status["processing_ids"]
def test_on_extraction_completed(self, processor): def test_on_extraction_completed(self, processor) -> None:
"""Test extraction completion callback.""" """Test extraction completion callback."""
extraction_id = 123 extraction_id = 123
processor.running_extractions.add(extraction_id) processor.running_extractions.add(extraction_id)
@@ -115,7 +115,7 @@ class TestExtractionProcessor:
# Should be removed from running extractions # Should be removed from running extractions
assert extraction_id not in processor.running_extractions assert extraction_id not in processor.running_extractions
def test_on_extraction_completed_with_exception(self, processor): def test_on_extraction_completed_with_exception(self, processor) -> None:
"""Test extraction completion callback with exception.""" """Test extraction completion callback with exception."""
extraction_id = 123 extraction_id = 123
processor.running_extractions.add(extraction_id) processor.running_extractions.add(extraction_id)
@@ -130,7 +130,7 @@ class TestExtractionProcessor:
assert extraction_id not in processor.running_extractions assert extraction_id not in processor.running_extractions
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_single_extraction_success(self, processor): async def test_process_single_extraction_success(self, processor) -> None:
"""Test processing a single extraction successfully.""" """Test processing a single extraction successfully."""
extraction_id = 123 extraction_id = 123
@@ -157,7 +157,7 @@ class TestExtractionProcessor:
mock_service.process_extraction.assert_called_once_with(extraction_id) mock_service.process_extraction.assert_called_once_with(extraction_id)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_single_extraction_failure(self, processor): async def test_process_single_extraction_failure(self, processor) -> None:
"""Test processing a single extraction with failure.""" """Test processing a single extraction with failure."""
extraction_id = 123 extraction_id = 123
@@ -183,7 +183,7 @@ class TestExtractionProcessor:
mock_service.process_extraction.assert_called_once_with(extraction_id) mock_service.process_extraction.assert_called_once_with(extraction_id)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_pending_extractions_no_slots(self, processor): async def test_process_pending_extractions_no_slots(self, processor) -> None:
"""Test processing when no slots are available.""" """Test processing when no slots are available."""
# Fill all slots # Fill all slots
for i in range(processor.max_concurrent): for i in range(processor.max_concurrent):
@@ -213,7 +213,7 @@ class TestExtractionProcessor:
assert 100 not in processor.running_extractions assert 100 not in processor.running_extractions
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_pending_extractions_with_slots(self, processor): async def test_process_pending_extractions_with_slots(self, processor) -> None:
"""Test processing when slots are available.""" """Test processing when slots are available."""
# Mock extraction service # Mock extraction service
mock_service = Mock() mock_service = Mock()
@@ -230,7 +230,7 @@ class TestExtractionProcessor:
) as mock_session_class, ) as mock_session_class,
patch.object( patch.object(
processor, "_process_single_extraction", new_callable=AsyncMock, processor, "_process_single_extraction", new_callable=AsyncMock,
) as mock_process, ),
patch( patch(
"app.services.extraction_processor.ExtractionService", "app.services.extraction_processor.ExtractionService",
return_value=mock_service, return_value=mock_service,
@@ -254,7 +254,7 @@ class TestExtractionProcessor:
assert mock_create_task.call_count == 2 assert mock_create_task.call_count == 2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_process_pending_extractions_respect_limit(self, processor): async def test_process_pending_extractions_respect_limit(self, processor) -> None:
"""Test that processing respects concurrency limit.""" """Test that processing respects concurrency limit."""
# Set max concurrent to 1 for this test # Set max concurrent to 1 for this test
processor.max_concurrent = 1 processor.max_concurrent = 1
@@ -275,7 +275,7 @@ class TestExtractionProcessor:
) as mock_session_class, ) as mock_session_class,
patch.object( patch.object(
processor, "_process_single_extraction", new_callable=AsyncMock, processor, "_process_single_extraction", new_callable=AsyncMock,
) as mock_process, ),
patch( patch(
"app.services.extraction_processor.ExtractionService", "app.services.extraction_processor.ExtractionService",
return_value=mock_service, return_value=mock_service,

View File

@@ -24,7 +24,7 @@ from app.services.player import (
class TestPlayerState: class TestPlayerState:
"""Test player state data structure.""" """Test player state data structure."""
def test_init_creates_default_state(self): def test_init_creates_default_state(self) -> None:
"""Test that player state initializes with default values.""" """Test that player state initializes with default values."""
state = PlayerState() state = PlayerState()
@@ -42,7 +42,7 @@ class TestPlayerState:
assert state.playlist_duration == 0 assert state.playlist_duration == 0
assert state.playlist_sounds == [] assert state.playlist_sounds == []
def test_to_dict_serializes_correctly(self): def test_to_dict_serializes_correctly(self) -> None:
"""Test that player state serializes to dict correctly.""" """Test that player state serializes to dict correctly."""
state = PlayerState() state = PlayerState()
state.status = PlayerStatus.PLAYING state.status = PlayerStatus.PLAYING
@@ -70,7 +70,7 @@ class TestPlayerState:
assert result["playlist"]["length"] == 5 assert result["playlist"]["length"] == 5
assert result["playlist"]["duration"] == 150000 assert result["playlist"]["duration"] == 150000
def test_serialize_sound_with_sound_object(self): def test_serialize_sound_with_sound_object(self) -> None:
"""Test serializing a sound object.""" """Test serializing a sound object."""
state = PlayerState() state = PlayerState()
sound = Sound( sound = Sound(
@@ -95,7 +95,7 @@ class TestPlayerState:
assert result["thumbnail"] == "test.jpg" assert result["thumbnail"] == "test.jpg"
assert result["play_count"] == 5 assert result["play_count"] == 5
def test_serialize_sound_with_none(self): def test_serialize_sound_with_none(self) -> None:
"""Test serializing None sound.""" """Test serializing None sound."""
state = PlayerState() state = PlayerState()
result = state._serialize_sound(None) result = state._serialize_sound(None)
@@ -133,10 +133,9 @@ class TestPlayerService:
@pytest.fixture @pytest.fixture
def player_service(self, mock_db_session_factory, mock_vlc_instance, mock_socket_manager): def player_service(self, mock_db_session_factory, mock_vlc_instance, mock_socket_manager):
"""Create a player service instance for testing.""" """Create a player service instance for testing."""
service = PlayerService(mock_db_session_factory) return PlayerService(mock_db_session_factory)
return service
def test_init_creates_player_service(self, mock_db_session_factory, mock_vlc_instance): def test_init_creates_player_service(self, mock_db_session_factory, mock_vlc_instance) -> None:
"""Test that player service initializes correctly.""" """Test that player service initializes correctly."""
with patch("app.services.player.socket_manager"): with patch("app.services.player.socket_manager"):
service = PlayerService(mock_db_session_factory) service = PlayerService(mock_db_session_factory)
@@ -153,7 +152,7 @@ class TestPlayerService:
assert service._loop is None assert service._loop is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_start_initializes_service(self, player_service, mock_vlc_instance): async def test_start_initializes_service(self, player_service, mock_vlc_instance) -> None:
"""Test that start method initializes the service.""" """Test that start method initializes the service."""
with patch.object(player_service, "reload_playlist", new_callable=AsyncMock): with patch.object(player_service, "reload_playlist", new_callable=AsyncMock):
await player_service.start() await player_service.start()
@@ -165,7 +164,7 @@ class TestPlayerService:
player_service._player.audio_set_volume.assert_called_once_with(50) player_service._player.audio_set_volume.assert_called_once_with(50)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_stop_cleans_up_service(self, player_service): async def test_stop_cleans_up_service(self, player_service) -> None:
"""Test that stop method cleans up the service.""" """Test that stop method cleans up the service."""
# Setup initial state # Setup initial state
player_service._is_running = True player_service._is_running = True
@@ -180,7 +179,7 @@ class TestPlayerService:
player_service._player.release.assert_called_once() player_service._player.release.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_play_new_track(self, player_service, mock_vlc_instance): async def test_play_new_track(self, player_service, mock_vlc_instance) -> None:
"""Test playing a new track.""" """Test playing a new track."""
# Setup test sound # Setup test sound
sound = Sound( sound = Sound(
@@ -212,7 +211,7 @@ class TestPlayerService:
assert 1 in player_service._play_time_tracking assert 1 in player_service._play_time_tracking
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_play_resume_from_pause(self, player_service): async def test_play_resume_from_pause(self, player_service) -> None:
"""Test resuming playback from pause.""" """Test resuming playback from pause."""
# Setup paused state # Setup paused state
sound = Sound(id=1, name="Test Song", filename="test.mp3", duration=30000) sound = Sound(id=1, name="Test Song", filename="test.mp3", duration=30000)
@@ -230,7 +229,7 @@ class TestPlayerService:
player_service._player.play.assert_called_once() player_service._player.play.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_play_invalid_index(self, player_service): async def test_play_invalid_index(self, player_service) -> None:
"""Test playing with invalid index raises ValueError.""" """Test playing with invalid index raises ValueError."""
player_service.state.playlist_sounds = [] player_service.state.playlist_sounds = []
@@ -238,7 +237,7 @@ class TestPlayerService:
await player_service.play(0) await player_service.play(0)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_pause_when_playing(self, player_service): async def test_pause_when_playing(self, player_service) -> None:
"""Test pausing when currently playing.""" """Test pausing when currently playing."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
@@ -249,7 +248,7 @@ class TestPlayerService:
player_service._player.pause.assert_called_once() player_service._player.pause.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_pause_when_not_playing(self, player_service): async def test_pause_when_not_playing(self, player_service) -> None:
"""Test pausing when not playing does nothing.""" """Test pausing when not playing does nothing."""
player_service.state.status = PlayerStatus.STOPPED player_service.state.status = PlayerStatus.STOPPED
@@ -260,7 +259,7 @@ class TestPlayerService:
mock_broadcast.assert_not_called() mock_broadcast.assert_not_called()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_stop_playback(self, player_service): async def test_stop_playback(self, player_service) -> None:
"""Test stopping playback.""" """Test stopping playback."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
player_service.state.current_sound_position = 5000 player_service.state.current_sound_position = 5000
@@ -274,7 +273,7 @@ class TestPlayerService:
player_service._player.stop.assert_called_once() player_service._player.stop.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_next_track(self, player_service): async def test_next_track(self, player_service) -> None:
"""Test skipping to next track.""" """Test skipping to next track."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3") sound1 = Sound(id=1, name="Song 1", filename="song1.mp3")
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3") sound2 = Sound(id=2, name="Song 2", filename="song2.mp3")
@@ -286,7 +285,7 @@ class TestPlayerService:
mock_play.assert_called_once_with(1) mock_play.assert_called_once_with(1)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_previous_track(self, player_service): async def test_previous_track(self, player_service) -> None:
"""Test going to previous track.""" """Test going to previous track."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3") sound1 = Sound(id=1, name="Song 1", filename="song1.mp3")
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3") sound2 = Sound(id=2, name="Song 2", filename="song2.mp3")
@@ -298,7 +297,7 @@ class TestPlayerService:
mock_play.assert_called_once_with(0) mock_play.assert_called_once_with(0)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_seek_position(self, player_service): async def test_seek_position(self, player_service) -> None:
"""Test seeking to specific position.""" """Test seeking to specific position."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
player_service.state.current_sound_duration = 30000 player_service.state.current_sound_duration = 30000
@@ -311,7 +310,7 @@ class TestPlayerService:
assert player_service.state.current_sound_position == 15000 assert player_service.state.current_sound_position == 15000
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_seek_when_stopped(self, player_service): async def test_seek_when_stopped(self, player_service) -> None:
"""Test seeking when stopped does nothing.""" """Test seeking when stopped does nothing."""
player_service.state.status = PlayerStatus.STOPPED player_service.state.status = PlayerStatus.STOPPED
@@ -322,7 +321,7 @@ class TestPlayerService:
mock_broadcast.assert_not_called() mock_broadcast.assert_not_called()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_set_volume(self, player_service): async def test_set_volume(self, player_service) -> None:
"""Test setting volume.""" """Test setting volume."""
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock): with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
await player_service.set_volume(75) await player_service.set_volume(75)
@@ -331,7 +330,7 @@ class TestPlayerService:
player_service._player.audio_set_volume.assert_called_once_with(75) player_service._player.audio_set_volume.assert_called_once_with(75)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_set_volume_clamping(self, player_service): async def test_set_volume_clamping(self, player_service) -> None:
"""Test volume clamping to valid range.""" """Test volume clamping to valid range."""
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock): with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
# Test upper bound # Test upper bound
@@ -343,7 +342,7 @@ class TestPlayerService:
assert player_service.state.volume == 0 assert player_service.state.volume == 0
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_set_mode(self, player_service): async def test_set_mode(self, player_service) -> None:
"""Test setting playback mode.""" """Test setting playback mode."""
with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock): with patch.object(player_service, "_broadcast_state", new_callable=AsyncMock):
await player_service.set_mode(PlayerMode.LOOP) await player_service.set_mode(PlayerMode.LOOP)
@@ -351,7 +350,7 @@ class TestPlayerService:
assert player_service.state.mode == PlayerMode.LOOP assert player_service.state.mode == PlayerMode.LOOP
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_reload_playlist(self, player_service): async def test_reload_playlist(self, player_service) -> None:
"""Test reloading playlist from database.""" """Test reloading playlist from database."""
mock_session = AsyncMock() mock_session = AsyncMock()
player_service.db_session_factory = lambda: mock_session player_service.db_session_factory = lambda: mock_session
@@ -383,7 +382,7 @@ class TestPlayerService:
assert player_service.state.playlist_duration == 75000 assert player_service.state.playlist_duration == 75000
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_playlist_id_changed(self, player_service): async def test_handle_playlist_id_changed(self, player_service) -> None:
"""Test handling when playlist ID changes.""" """Test handling when playlist ID changes."""
# Setup initial state # Setup initial state
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
@@ -405,7 +404,7 @@ class TestPlayerService:
assert player_service.state.current_sound_id == 1 assert player_service.state.current_sound_id == 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_playlist_id_changed_empty_playlist(self, player_service): async def test_handle_playlist_id_changed_empty_playlist(self, player_service) -> None:
"""Test handling playlist ID change with empty playlist.""" """Test handling playlist ID change with empty playlist."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
@@ -418,7 +417,7 @@ class TestPlayerService:
assert player_service.state.current_sound_id is None assert player_service.state.current_sound_id is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_same_playlist_track_exists_same_index(self, player_service): async def test_handle_same_playlist_track_exists_same_index(self, player_service) -> None:
"""Test handling same playlist when track exists at same index.""" """Test handling same playlist when track exists at same index."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000) sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -431,7 +430,7 @@ class TestPlayerService:
assert player_service.state.current_sound == sound1 assert player_service.state.current_sound == sound1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_same_playlist_track_exists_different_index(self, player_service): async def test_handle_same_playlist_track_exists_different_index(self, player_service) -> None:
"""Test handling same playlist when track exists at different index.""" """Test handling same playlist when track exists at different index."""
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
sound2 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000) sound2 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
@@ -444,7 +443,7 @@ class TestPlayerService:
assert player_service.state.current_sound == sound2 assert player_service.state.current_sound == sound2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_same_playlist_track_not_found(self, player_service): async def test_handle_same_playlist_track_not_found(self, player_service) -> None:
"""Test handling same playlist when current track no longer exists.""" """Test handling same playlist when current track no longer exists."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -456,7 +455,7 @@ class TestPlayerService:
mock_removed.assert_called_once_with(1, sounds) mock_removed.assert_called_once_with(1, sounds)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_track_removed_with_sounds(self, player_service): async def test_handle_track_removed_with_sounds(self, player_service) -> None:
"""Test handling when current track is removed with sounds available.""" """Test handling when current track is removed with sounds available."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound1 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -471,7 +470,7 @@ class TestPlayerService:
assert player_service.state.current_sound_id == 2 assert player_service.state.current_sound_id == 2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_handle_track_removed_empty_playlist(self, player_service): async def test_handle_track_removed_empty_playlist(self, player_service) -> None:
"""Test handling when current track is removed with empty playlist.""" """Test handling when current track is removed with empty playlist."""
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
@@ -483,7 +482,7 @@ class TestPlayerService:
assert player_service.state.current_sound is None assert player_service.state.current_sound is None
assert player_service.state.current_sound_id is None assert player_service.state.current_sound_id is None
def test_update_playlist_state(self, player_service): def test_update_playlist_state(self, player_service) -> None:
"""Test updating playlist state information.""" """Test updating playlist state information."""
mock_playlist = Mock() mock_playlist = Mock()
mock_playlist.id = 5 mock_playlist.id = 5
@@ -501,7 +500,7 @@ class TestPlayerService:
assert player_service.state.playlist_length == 2 assert player_service.state.playlist_length == 2
assert player_service.state.playlist_duration == 75000 assert player_service.state.playlist_duration == 75000
def test_find_sound_index_found(self, player_service): def test_find_sound_index_found(self, player_service) -> None:
"""Test finding sound index when sound exists.""" """Test finding sound index when sound exists."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000) sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -510,7 +509,7 @@ class TestPlayerService:
index = player_service._find_sound_index(2, sounds) index = player_service._find_sound_index(2, sounds)
assert index == 1 assert index == 1
def test_find_sound_index_not_found(self, player_service): def test_find_sound_index_not_found(self, player_service) -> None:
"""Test finding sound index when sound doesn't exist.""" """Test finding sound index when sound doesn't exist."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000) sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
sounds = [sound1] sounds = [sound1]
@@ -518,7 +517,7 @@ class TestPlayerService:
index = player_service._find_sound_index(999, sounds) index = player_service._find_sound_index(999, sounds)
assert index is None assert index is None
def test_set_first_track_as_current(self, player_service): def test_set_first_track_as_current(self, player_service) -> None:
"""Test setting first track as current.""" """Test setting first track as current."""
sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000) sound1 = Sound(id=1, name="Song 1", filename="song1.mp3", duration=30000)
sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000) sound2 = Sound(id=2, name="Song 2", filename="song2.mp3", duration=45000)
@@ -530,7 +529,7 @@ class TestPlayerService:
assert player_service.state.current_sound == sound1 assert player_service.state.current_sound == sound1
assert player_service.state.current_sound_id == 1 assert player_service.state.current_sound_id == 1
def test_clear_current_track(self, player_service): def test_clear_current_track(self, player_service) -> None:
"""Test clearing current track state.""" """Test clearing current track state."""
# Set some initial state # Set some initial state
player_service.state.current_sound_index = 2 player_service.state.current_sound_index = 2
@@ -544,7 +543,7 @@ class TestPlayerService:
assert player_service.state.current_sound_id is None assert player_service.state.current_sound_id is None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_reload_playlist_different_id_scenario(self, player_service): async def test_reload_playlist_different_id_scenario(self, player_service) -> None:
"""Test complete reload scenario when playlist ID changes.""" """Test complete reload scenario when playlist ID changes."""
# Setup current state # Setup current state
player_service.state.playlist_id = 1 player_service.state.playlist_id = 1
@@ -580,7 +579,7 @@ class TestPlayerService:
assert player_service.state.current_sound_id == 1 assert player_service.state.current_sound_id == 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_reload_playlist_same_id_track_moved(self, player_service): async def test_reload_playlist_same_id_track_moved(self, player_service) -> None:
"""Test reload when playlist ID same but track moved to different index.""" """Test reload when playlist ID same but track moved to different index."""
# Setup current state # Setup current state
player_service.state.playlist_id = 1 player_service.state.playlist_id = 1
@@ -616,7 +615,7 @@ class TestPlayerService:
assert player_service.state.current_sound == sound1 assert player_service.state.current_sound == sound1
def test_get_next_index_continuous_mode(self, player_service): def test_get_next_index_continuous_mode(self, player_service) -> None:
"""Test getting next index in continuous mode.""" """Test getting next index in continuous mode."""
player_service.state.mode = PlayerMode.CONTINUOUS player_service.state.mode = PlayerMode.CONTINUOUS
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -628,7 +627,7 @@ class TestPlayerService:
# Test end of playlist # Test end of playlist
assert player_service._get_next_index(2) is None assert player_service._get_next_index(2) is None
def test_get_next_index_loop_mode(self, player_service): def test_get_next_index_loop_mode(self, player_service) -> None:
"""Test getting next index in loop mode.""" """Test getting next index in loop mode."""
player_service.state.mode = PlayerMode.LOOP player_service.state.mode = PlayerMode.LOOP
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -640,7 +639,7 @@ class TestPlayerService:
# Test wrapping to beginning # Test wrapping to beginning
assert player_service._get_next_index(2) == 0 assert player_service._get_next_index(2) == 0
def test_get_next_index_loop_one_mode(self, player_service): def test_get_next_index_loop_one_mode(self, player_service) -> None:
"""Test getting next index in loop one mode.""" """Test getting next index in loop one mode."""
player_service.state.mode = PlayerMode.LOOP_ONE player_service.state.mode = PlayerMode.LOOP_ONE
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -650,7 +649,7 @@ class TestPlayerService:
assert player_service._get_next_index(1) == 1 assert player_service._get_next_index(1) == 1
assert player_service._get_next_index(2) == 2 assert player_service._get_next_index(2) == 2
def test_get_next_index_single_mode(self, player_service): def test_get_next_index_single_mode(self, player_service) -> None:
"""Test getting next index in single mode.""" """Test getting next index in single mode."""
player_service.state.mode = PlayerMode.SINGLE player_service.state.mode = PlayerMode.SINGLE
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -660,7 +659,7 @@ class TestPlayerService:
assert player_service._get_next_index(1) is None assert player_service._get_next_index(1) is None
assert player_service._get_next_index(2) is None assert player_service._get_next_index(2) is None
def test_get_next_index_random_mode(self, player_service): def test_get_next_index_random_mode(self, player_service) -> None:
"""Test getting next index in random mode.""" """Test getting next index in random mode."""
player_service.state.mode = PlayerMode.RANDOM player_service.state.mode = PlayerMode.RANDOM
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -674,7 +673,7 @@ class TestPlayerService:
# Should exclude current index # Should exclude current index
mock_choice.assert_called_once_with([1, 2]) mock_choice.assert_called_once_with([1, 2])
def test_get_previous_index(self, player_service): def test_get_previous_index(self, player_service) -> None:
"""Test getting previous index.""" """Test getting previous index."""
player_service.state.playlist_sounds = [Mock(), Mock(), Mock()] player_service.state.playlist_sounds = [Mock(), Mock(), Mock()]
@@ -690,7 +689,7 @@ class TestPlayerService:
player_service.state.mode = PlayerMode.LOOP player_service.state.mode = PlayerMode.LOOP
assert player_service._get_previous_index(0) == 2 assert player_service._get_previous_index(0) == 2
def test_update_play_time(self, player_service): def test_update_play_time(self, player_service) -> None:
"""Test updating play time tracking.""" """Test updating play time tracking."""
# Setup state # Setup state
player_service.state.status = PlayerStatus.PLAYING player_service.state.status = PlayerStatus.PLAYING
@@ -716,7 +715,7 @@ class TestPlayerService:
assert tracking["last_update"] == current_time assert tracking["last_update"] == current_time
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_record_play_count(self, player_service): async def test_record_play_count(self, player_service) -> None:
"""Test recording play count for a sound.""" """Test recording play count for a sound."""
mock_session = AsyncMock() mock_session = AsyncMock()
player_service.db_session_factory = lambda: mock_session player_service.db_session_factory = lambda: mock_session
@@ -742,7 +741,7 @@ class TestPlayerService:
mock_session.add.assert_called_once() mock_session.add.assert_called_once()
mock_session.commit.assert_called_once() mock_session.commit.assert_called_once()
def test_get_state(self, player_service): def test_get_state(self, player_service) -> None:
"""Test getting current player state.""" """Test getting current player state."""
result = player_service.get_state() result = player_service.get_state()
assert isinstance(result, dict) assert isinstance(result, dict)
@@ -750,7 +749,7 @@ class TestPlayerService:
assert "mode" in result assert "mode" in result
assert "volume" in result assert "volume" in result
def test_uses_shared_sound_path_utility(self, player_service): def test_uses_shared_sound_path_utility(self, player_service) -> None:
"""Test that player service uses the shared sound path utility.""" """Test that player service uses the shared sound path utility."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -767,7 +766,7 @@ class TestPlayerService:
mock_path.return_value = mock_file_path mock_path.return_value = mock_file_path
# This should fail because file doesn't exist # This should fail because file doesn't exist
result = asyncio.run(player_service.play(0)) asyncio.run(player_service.play(0))
# Verify the utility was called # Verify the utility was called
mock_path.assert_called_once_with(sound) mock_path.assert_called_once_with(sound)
@@ -776,7 +775,7 @@ class TestPlayerServiceGlobalFunctions:
"""Test global player service functions.""" """Test global player service functions."""
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_initialize_player_service(self): async def test_initialize_player_service(self) -> None:
"""Test initializing global player service.""" """Test initializing global player service."""
mock_factory = Mock() mock_factory = Mock()
@@ -790,7 +789,7 @@ class TestPlayerServiceGlobalFunctions:
mock_service.start.assert_called_once() mock_service.start.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_shutdown_player_service(self): async def test_shutdown_player_service(self) -> None:
"""Test shutting down global player service.""" """Test shutting down global player service."""
# Mock global player service exists # Mock global player service exists
with patch("app.services.player.player_service") as mock_global: with patch("app.services.player.player_service") as mock_global:
@@ -802,7 +801,7 @@ class TestPlayerServiceGlobalFunctions:
await shutdown_player_service() await shutdown_player_service()
mock_service.stop.assert_called_once() mock_service.stop.assert_called_once()
def test_get_player_service_success(self): def test_get_player_service_success(self) -> None:
"""Test getting player service when initialized.""" """Test getting player service when initialized."""
mock_service = Mock() mock_service = Mock()
@@ -810,7 +809,7 @@ class TestPlayerServiceGlobalFunctions:
result = get_player_service() result = get_player_service()
assert result is mock_service assert result is mock_service
def test_get_player_service_not_initialized(self): def test_get_player_service_not_initialized(self) -> None:
"""Test getting player service when not initialized.""" """Test getting player service when not initialized."""
with patch("app.services.player.player_service", None): with patch("app.services.player.player_service", None):
with pytest.raises(RuntimeError, match="Player service not initialized"): with pytest.raises(RuntimeError, match="Player service not initialized"):

View File

@@ -22,7 +22,7 @@ class TestSocketManager:
socket_manager.sio = AsyncMock(spec=socketio.AsyncServer) socket_manager.sio = AsyncMock(spec=socketio.AsyncServer)
return socket_manager.sio return socket_manager.sio
def test_init_creates_socket_server(self): def test_init_creates_socket_server(self) -> None:
"""Test that socket manager initializes with proper configuration.""" """Test that socket manager initializes with proper configuration."""
manager = SocketManager() manager = SocketManager()
@@ -33,7 +33,7 @@ class TestSocketManager:
assert len(manager.socket_users) == 0 assert len(manager.socket_users) == 0
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_to_user_success(self, socket_manager, mock_sio): async def test_send_to_user_success(self, socket_manager, mock_sio) -> None:
"""Test sending message to connected user.""" """Test sending message to connected user."""
user_id = "123" user_id = "123"
room_id = "user_123" room_id = "user_123"
@@ -48,7 +48,7 @@ class TestSocketManager:
mock_sio.emit.assert_called_once_with(event, data, room=room_id) mock_sio.emit.assert_called_once_with(event, data, room=room_id)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_send_to_user_not_connected(self, socket_manager, mock_sio): async def test_send_to_user_not_connected(self, socket_manager, mock_sio) -> None:
"""Test sending message to user who is not connected.""" """Test sending message to user who is not connected."""
user_id = "999" user_id = "999"
event = "test_event" event = "test_event"
@@ -60,7 +60,7 @@ class TestSocketManager:
mock_sio.emit.assert_not_called() mock_sio.emit.assert_not_called()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_broadcast_to_all(self, socket_manager, mock_sio): async def test_broadcast_to_all(self, socket_manager, mock_sio) -> None:
"""Test broadcasting message to all users.""" """Test broadcasting message to all users."""
event = "broadcast_event" event = "broadcast_event"
data = {"message": "announcement"} data = {"message": "announcement"}
@@ -69,7 +69,7 @@ class TestSocketManager:
mock_sio.emit.assert_called_once_with(event, data) mock_sio.emit.assert_called_once_with(event, data)
def test_get_connected_users(self, socket_manager): def test_get_connected_users(self, socket_manager) -> None:
"""Test getting list of connected users.""" """Test getting list of connected users."""
# Add some users # Add some users
socket_manager.user_rooms["1"] = "user_1" socket_manager.user_rooms["1"] = "user_1"
@@ -83,7 +83,7 @@ class TestSocketManager:
assert "2" in connected_users assert "2" in connected_users
assert "3" in connected_users assert "3" in connected_users
def test_get_room_info(self, socket_manager): def test_get_room_info(self, socket_manager) -> None:
"""Test getting room information.""" """Test getting room information."""
# Add some users # Add some users
socket_manager.user_rooms["1"] = "user_1" socket_manager.user_rooms["1"] = "user_1"
@@ -99,7 +99,7 @@ class TestSocketManager:
@patch("app.services.socket.JWTUtils.decode_access_token") @patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_success( async def test_connect_handler_success(
self, mock_decode, mock_extract_token, socket_manager, mock_sio, self, mock_decode, mock_extract_token, socket_manager, mock_sio,
): ) -> None:
"""Test successful connection with valid token.""" """Test successful connection with valid token."""
# Setup mocks # Setup mocks
mock_extract_token.return_value = "valid_token" mock_extract_token.return_value = "valid_token"
@@ -110,7 +110,6 @@ class TestSocketManager:
# Access the connect handler directly # Access the connect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func
@@ -134,7 +133,7 @@ class TestSocketManager:
@patch("app.services.socket.extract_access_token_from_cookies") @patch("app.services.socket.extract_access_token_from_cookies")
async def test_connect_handler_no_token( async def test_connect_handler_no_token(
self, mock_extract_token, socket_manager, mock_sio, self, mock_extract_token, socket_manager, mock_sio,
): ) -> None:
"""Test connection with no access token.""" """Test connection with no access token."""
# Setup mocks # Setup mocks
mock_extract_token.return_value = None mock_extract_token.return_value = None
@@ -144,7 +143,6 @@ class TestSocketManager:
# Access the connect handler directly # Access the connect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func
@@ -168,7 +166,7 @@ class TestSocketManager:
@patch("app.services.socket.JWTUtils.decode_access_token") @patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_invalid_token( async def test_connect_handler_invalid_token(
self, mock_decode, mock_extract_token, socket_manager, mock_sio, self, mock_decode, mock_extract_token, socket_manager, mock_sio,
): ) -> None:
"""Test connection with invalid token.""" """Test connection with invalid token."""
# Setup mocks # Setup mocks
mock_extract_token.return_value = "invalid_token" mock_extract_token.return_value = "invalid_token"
@@ -179,7 +177,6 @@ class TestSocketManager:
# Access the connect handler directly # Access the connect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func
@@ -203,7 +200,7 @@ class TestSocketManager:
@patch("app.services.socket.JWTUtils.decode_access_token") @patch("app.services.socket.JWTUtils.decode_access_token")
async def test_connect_handler_missing_user_id( async def test_connect_handler_missing_user_id(
self, mock_decode, mock_extract_token, socket_manager, mock_sio, self, mock_decode, mock_extract_token, socket_manager, mock_sio,
): ) -> None:
"""Test connection with token missing user ID.""" """Test connection with token missing user ID."""
# Setup mocks # Setup mocks
mock_extract_token.return_value = "token_without_user_id" mock_extract_token.return_value = "token_without_user_id"
@@ -214,7 +211,6 @@ class TestSocketManager:
# Access the connect handler directly # Access the connect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func
@@ -234,7 +230,7 @@ class TestSocketManager:
assert len(socket_manager.user_rooms) == 0 assert len(socket_manager.user_rooms) == 0
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_disconnect_handler(self, socket_manager, mock_sio): async def test_disconnect_handler(self, socket_manager, mock_sio) -> None:
"""Test disconnect handler.""" """Test disconnect handler."""
# Setup initial state # Setup initial state
socket_manager.socket_users["test_sid"] = "123" socket_manager.socket_users["test_sid"] = "123"
@@ -242,7 +238,6 @@ class TestSocketManager:
# Access the disconnect handler directly # Access the disconnect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func
@@ -259,11 +254,10 @@ class TestSocketManager:
assert "123" not in socket_manager.user_rooms assert "123" not in socket_manager.user_rooms
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_disconnect_handler_unknown_socket(self, socket_manager, mock_sio): async def test_disconnect_handler_unknown_socket(self, socket_manager, mock_sio) -> None:
"""Test disconnect handler with unknown socket.""" """Test disconnect handler with unknown socket."""
# Access the disconnect handler directly # Access the disconnect handler directly
handlers = {} handlers = {}
original_event = socket_manager.sio.event
def mock_event(func): def mock_event(func):
handlers[func.__name__] = func handlers[func.__name__] = func

View File

@@ -28,7 +28,7 @@ class TestSoundNormalizerService:
mock_settings.NORMALIZED_AUDIO_PASSES = 2 mock_settings.NORMALIZED_AUDIO_PASSES = 2
return SoundNormalizerService(mock_session) return SoundNormalizerService(mock_session)
def test_init(self, normalizer_service): def test_init(self, normalizer_service) -> None:
"""Test normalizer service initialization.""" """Test normalizer service initialization."""
assert normalizer_service.session is not None assert normalizer_service.session is not None
assert normalizer_service.sound_repo is not None assert normalizer_service.sound_repo is not None
@@ -40,7 +40,7 @@ class TestSoundNormalizerService:
assert "TTS" in normalizer_service.type_directories assert "TTS" in normalizer_service.type_directories
assert "EXT" in normalizer_service.type_directories assert "EXT" in normalizer_service.type_directories
def test_get_normalized_path(self, normalizer_service): def test_get_normalized_path(self, normalizer_service) -> None:
"""Test normalized path generation.""" """Test normalized path generation."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -57,7 +57,7 @@ class TestSoundNormalizerService:
assert "sounds/normalized/soundboard" in str(normalized_path) assert "sounds/normalized/soundboard" in str(normalized_path)
assert normalized_path.name == "test_audio.mp3" assert normalized_path.name == "test_audio.mp3"
def test_get_original_path(self, normalizer_service): def test_get_original_path(self, normalizer_service) -> None:
"""Test original path generation.""" """Test original path generation."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -74,7 +74,7 @@ class TestSoundNormalizerService:
assert "sounds/originals/soundboard" in str(original_path) assert "sounds/originals/soundboard" in str(original_path)
assert original_path.name == "test_audio.wav" assert original_path.name == "test_audio.wav"
def test_get_file_hash(self, normalizer_service): def test_get_file_hash(self, normalizer_service) -> None:
"""Test file hash calculation.""" """Test file hash calculation."""
# Create a temporary file # Create a temporary file
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
@@ -90,7 +90,7 @@ class TestSoundNormalizerService:
finally: finally:
temp_path.unlink() temp_path.unlink()
def test_get_file_size(self, normalizer_service): def test_get_file_size(self, normalizer_service) -> None:
"""Test file size calculation.""" """Test file size calculation."""
# Create a temporary file # Create a temporary file
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
@@ -107,7 +107,7 @@ class TestSoundNormalizerService:
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, normalizer_service): def test_get_audio_duration_success(self, mock_probe, normalizer_service) -> None:
"""Test successful audio duration extraction.""" """Test successful audio duration extraction."""
mock_probe.return_value = {"format": {"duration": "123.456"}} mock_probe.return_value = {"format": {"duration": "123.456"}}
@@ -120,7 +120,7 @@ class TestSoundNormalizerService:
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_failure(self, mock_probe, normalizer_service): def test_get_audio_duration_failure(self, mock_probe, normalizer_service) -> None:
"""Test audio duration extraction failure.""" """Test audio duration extraction failure."""
mock_probe.side_effect = Exception("FFmpeg error") mock_probe.side_effect = Exception("FFmpeg error")
@@ -133,7 +133,7 @@ class TestSoundNormalizerService:
mock_probe.assert_called_once_with(str(temp_path)) mock_probe.assert_called_once_with(str(temp_path))
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_already_normalized(self, normalizer_service): async def test_normalize_sound_already_normalized(self, normalizer_service) -> None:
"""Test normalizing a sound that's already normalized.""" """Test normalizing a sound that's already normalized."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -154,7 +154,7 @@ class TestSoundNormalizerService:
assert result["id"] == 1 assert result["id"] == 1
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_force_already_normalized(self, normalizer_service): async def test_normalize_sound_force_already_normalized(self, normalizer_service) -> None:
"""Test force normalizing a sound that's already normalized.""" """Test force normalizing a sound that's already normalized."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -173,7 +173,7 @@ class TestSoundNormalizerService:
patch.object(normalizer_service, "_get_normalized_path") as mock_norm_path, patch.object(normalizer_service, "_get_normalized_path") as mock_norm_path,
patch.object( patch.object(
normalizer_service, "_normalize_audio_two_pass", normalizer_service, "_normalize_audio_two_pass",
) as mock_normalize, ),
patch( patch(
"app.services.sound_normalizer.get_audio_duration", return_value=6000, "app.services.sound_normalizer.get_audio_duration", return_value=6000,
), ),
@@ -203,7 +203,7 @@ class TestSoundNormalizerService:
normalizer_service.sound_repo.update.assert_called_once() normalizer_service.sound_repo.update.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_file_not_found(self, normalizer_service): async def test_normalize_sound_file_not_found(self, normalizer_service) -> None:
"""Test normalizing a sound where original file doesn't exist.""" """Test normalizing a sound where original file doesn't exist."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -228,7 +228,7 @@ class TestSoundNormalizerService:
assert result["filename"] == "missing.mp3" assert result["filename"] == "missing.mp3"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_one_pass(self, normalizer_service): async def test_normalize_sound_one_pass(self, normalizer_service) -> None:
"""Test normalizing a sound using one-pass method.""" """Test normalizing a sound using one-pass method."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -275,7 +275,7 @@ class TestSoundNormalizerService:
mock_normalize.assert_called_once() mock_normalize.assert_called_once()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sound_normalization_error(self, normalizer_service): async def test_normalize_sound_normalization_error(self, normalizer_service) -> None:
"""Test handling normalization errors.""" """Test handling normalization errors."""
sound = Sound( sound = Sound(
id=1, id=1,
@@ -312,7 +312,7 @@ class TestSoundNormalizerService:
assert result["filename"] == "test.mp3" assert result["filename"] == "test.mp3"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_all_sounds(self, normalizer_service): async def test_normalize_all_sounds(self, normalizer_service) -> None:
"""Test normalizing all unnormalized sounds.""" """Test normalizing all unnormalized sounds."""
sounds = [ sounds = [
Sound( Sound(
@@ -382,7 +382,7 @@ class TestSoundNormalizerService:
assert len(results["files"]) == 2 assert len(results["files"]) == 2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sounds_by_type(self, normalizer_service): async def test_normalize_sounds_by_type(self, normalizer_service) -> None:
"""Test normalizing sounds by type.""" """Test normalizing sounds by type."""
sdb_sounds = [ sdb_sounds = [
Sound( Sound(
@@ -432,7 +432,7 @@ class TestSoundNormalizerService:
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_normalize_sounds_with_errors(self, normalizer_service): async def test_normalize_sounds_with_errors(self, normalizer_service) -> None:
"""Test normalizing sounds with some errors.""" """Test normalizing sounds with some errors."""
sounds = [ sounds = [
Sound( Sound(
@@ -512,7 +512,7 @@ class TestSoundNormalizerService:
self, self,
mock_ffmpeg, mock_ffmpeg,
normalizer_service, normalizer_service,
): ) -> None:
"""Test one-pass audio normalization for MP3.""" """Test one-pass audio normalization for MP3."""
input_path = Path("/fake/input.wav") input_path = Path("/fake/input.wav")
output_path = Path("/fake/output.mp3") output_path = Path("/fake/output.mp3")
@@ -547,7 +547,7 @@ class TestSoundNormalizerService:
self, self,
mock_ffmpeg, mock_ffmpeg,
normalizer_service, normalizer_service,
): ) -> None:
"""Test two-pass audio normalization analysis phase.""" """Test two-pass audio normalization analysis phase."""
input_path = Path("/fake/input.wav") input_path = Path("/fake/input.wav")
output_path = Path("/fake/output.mp3") output_path = Path("/fake/output.mp3")
@@ -562,7 +562,7 @@ class TestSoundNormalizerService:
# Mock analysis output with valid JSON # Mock analysis output with valid JSON
analysis_json = """{ analysis_json = """{
"input_i": "-23.0", "input_i": "-23.0",
"input_lra": "11.0", "input_lra": "11.0",
"input_tp": "-2.0", "input_tp": "-2.0",
"input_thresh": "-33.0", "input_thresh": "-33.0",
"target_offset": "0.0" "target_offset": "0.0"

View File

@@ -24,7 +24,7 @@ class TestSoundScannerService:
"""Create a scanner service with mock session.""" """Create a scanner service with mock session."""
return SoundScannerService(mock_session) return SoundScannerService(mock_session)
def test_init(self, scanner_service): def test_init(self, scanner_service) -> None:
"""Test scanner service initialization.""" """Test scanner service initialization."""
assert scanner_service.session is not None assert scanner_service.session is not None
assert scanner_service.sound_repo is not None assert scanner_service.sound_repo is not None
@@ -32,7 +32,7 @@ class TestSoundScannerService:
assert ".mp3" in scanner_service.supported_extensions assert ".mp3" in scanner_service.supported_extensions
assert ".wav" in scanner_service.supported_extensions assert ".wav" in scanner_service.supported_extensions
def test_get_file_hash(self, scanner_service): def test_get_file_hash(self, scanner_service) -> None:
"""Test file hash calculation.""" """Test file hash calculation."""
# Create a temporary file # Create a temporary file
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
@@ -48,7 +48,7 @@ class TestSoundScannerService:
finally: finally:
temp_path.unlink() temp_path.unlink()
def test_get_file_size(self, scanner_service): def test_get_file_size(self, scanner_service) -> None:
"""Test file size calculation.""" """Test file size calculation."""
# Create a temporary file # Create a temporary file
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
@@ -64,7 +64,7 @@ class TestSoundScannerService:
finally: finally:
temp_path.unlink() temp_path.unlink()
def test_extract_name_from_filename(self, scanner_service): def test_extract_name_from_filename(self, scanner_service) -> None:
"""Test name extraction from filename.""" """Test name extraction from filename."""
test_cases = [ test_cases = [
("hello_world.mp3", "Hello World"), ("hello_world.mp3", "Hello World"),
@@ -79,7 +79,7 @@ class TestSoundScannerService:
assert result == expected_name assert result == expected_name
@patch("app.utils.audio.ffmpeg.probe") @patch("app.utils.audio.ffmpeg.probe")
def test_get_audio_duration_success(self, mock_probe, scanner_service): def test_get_audio_duration_success(self, mock_probe, scanner_service) -> None:
"""Test successful audio duration extraction.""" """Test successful audio duration extraction."""
mock_probe.return_value = {"format": {"duration": "123.456"}} mock_probe.return_value = {"format": {"duration": "123.456"}}
@@ -92,7 +92,7 @@ class TestSoundScannerService:
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_failure(self, mock_probe, scanner_service): def test_get_audio_duration_failure(self, mock_probe, scanner_service) -> None:
"""Test audio duration extraction failure.""" """Test audio duration extraction failure."""
mock_probe.side_effect = Exception("FFmpeg error") mock_probe.side_effect = Exception("FFmpeg error")
@@ -105,13 +105,13 @@ class TestSoundScannerService:
mock_probe.assert_called_once_with(str(temp_path)) mock_probe.assert_called_once_with(str(temp_path))
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_scan_directory_nonexistent(self, scanner_service): async def test_scan_directory_nonexistent(self, scanner_service) -> None:
"""Test scanning a non-existent directory.""" """Test scanning a non-existent directory."""
with pytest.raises(ValueError, match="Directory does not exist"): with pytest.raises(ValueError, match="Directory does not exist"):
await scanner_service.scan_directory("/non/existent/path") await scanner_service.scan_directory("/non/existent/path")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_scan_directory_not_directory(self, scanner_service): async def test_scan_directory_not_directory(self, scanner_service) -> None:
"""Test scanning a path that is not a directory.""" """Test scanning a path that is not a directory."""
# Create a temporary file # Create a temporary file
with tempfile.NamedTemporaryFile() as f: with tempfile.NamedTemporaryFile() as f:
@@ -119,7 +119,7 @@ class TestSoundScannerService:
await scanner_service.scan_directory(f.name) await scanner_service.scan_directory(f.name)
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_sync_audio_file_unchanged(self, scanner_service): async def test_sync_audio_file_unchanged(self, scanner_service) -> None:
"""Test syncing file that is unchanged.""" """Test syncing file that is unchanged."""
# Existing sound with same hash as file # Existing sound with same hash as file
existing_sound = Sound( existing_sound = Sound(
@@ -166,7 +166,7 @@ class TestSoundScannerService:
temp_path.unlink() temp_path.unlink()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_sync_audio_file_new(self, scanner_service): async def test_sync_audio_file_new(self, scanner_service) -> None:
"""Test syncing a new audio file.""" """Test syncing a new audio file."""
created_sound = Sound( created_sound = Sound(
id=1, id=1,
@@ -221,7 +221,7 @@ class TestSoundScannerService:
temp_path.unlink() temp_path.unlink()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_sync_audio_file_updated(self, scanner_service): async def test_sync_audio_file_updated(self, scanner_service) -> None:
"""Test syncing a file that was modified (different hash).""" """Test syncing a file that was modified (different hash)."""
# Existing sound with different hash than file # Existing sound with different hash than file
existing_sound = Sound( existing_sound = Sound(
@@ -280,7 +280,7 @@ class TestSoundScannerService:
temp_path.unlink() temp_path.unlink()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_sync_audio_file_custom_type(self, scanner_service): async def test_sync_audio_file_custom_type(self, scanner_service) -> None:
"""Test syncing file with custom type.""" """Test syncing file with custom type."""
created_sound = Sound( created_sound = Sound(
id=1, id=1,

View File

@@ -6,7 +6,6 @@ from unittest.mock import AsyncMock, Mock, patch
import pytest import pytest
from app.models.credit_transaction import CreditTransaction
from app.models.sound import Sound from app.models.sound import Sound
from app.models.user import User from app.models.user import User
from app.services.vlc_player import VLCPlayerService, get_vlc_player_service from app.services.vlc_player import VLCPlayerService, get_vlc_player_service
@@ -62,20 +61,20 @@ class TestVLCPlayerService:
normalized_filename="normalized.mp3", normalized_filename="normalized.mp3",
) )
def test_init(self, vlc_service): def test_init(self, vlc_service) -> None:
"""Test VLC service initialization.""" """Test VLC service initialization."""
assert vlc_service.vlc_executable is not None assert vlc_service.vlc_executable is not None
assert isinstance(vlc_service.vlc_executable, str) assert isinstance(vlc_service.vlc_executable, str)
@patch("app.services.vlc_player.subprocess.run") @patch("app.services.vlc_player.subprocess.run")
def test_find_vlc_executable_found_in_path(self, mock_run): def test_find_vlc_executable_found_in_path(self, mock_run) -> None:
"""Test VLC executable detection when found in PATH.""" """Test VLC executable detection when found in PATH."""
mock_run.return_value.returncode = 0 mock_run.return_value.returncode = 0
service = VLCPlayerService() service = VLCPlayerService()
assert service.vlc_executable == "vlc" assert service.vlc_executable == "vlc"
@patch("app.services.vlc_player.subprocess.run") @patch("app.services.vlc_player.subprocess.run")
def test_find_vlc_executable_found_by_path(self, mock_run): def test_find_vlc_executable_found_by_path(self, mock_run) -> None:
"""Test VLC executable detection when found by absolute path.""" """Test VLC executable detection when found by absolute path."""
mock_run.return_value.returncode = 1 # which command fails mock_run.return_value.returncode = 1 # which command fails
@@ -93,7 +92,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.subprocess.run") @patch("app.services.vlc_player.subprocess.run")
@patch("app.services.vlc_player.Path") @patch("app.services.vlc_player.Path")
def test_find_vlc_executable_fallback(self, mock_path, mock_run): def test_find_vlc_executable_fallback(self, mock_path, mock_run) -> None:
"""Test VLC executable detection fallback to default.""" """Test VLC executable detection fallback to default."""
# Mock all paths as non-existent # Mock all paths as non-existent
mock_path_instance = Mock() mock_path_instance = Mock()
@@ -111,7 +110,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_success( async def test_play_sound_success(
self, mock_subprocess, vlc_service, sample_sound, self, mock_subprocess, vlc_service, sample_sound,
): ) -> None:
"""Test successful sound playback.""" """Test successful sound playback."""
# Mock subprocess # Mock subprocess
mock_process = Mock() mock_process = Mock()
@@ -144,7 +143,7 @@ class TestVLCPlayerService:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_play_sound_file_not_found( async def test_play_sound_file_not_found(
self, vlc_service, sample_sound, self, vlc_service, sample_sound,
): ) -> None:
"""Test sound playback when file doesn't exist.""" """Test sound playback when file doesn't exist."""
# Mock the file path utility to return a non-existent path # Mock the file path utility to return a non-existent path
with patch("app.services.vlc_player.get_sound_file_path") as mock_get_path: with patch("app.services.vlc_player.get_sound_file_path") as mock_get_path:
@@ -160,7 +159,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_subprocess_error( async def test_play_sound_subprocess_error(
self, mock_subprocess, vlc_service, sample_sound, self, mock_subprocess, vlc_service, sample_sound,
): ) -> None:
"""Test sound playback when subprocess fails.""" """Test sound playback when subprocess fails."""
# Mock the file path utility to return an existing path # Mock the file path utility to return an existing path
with patch("app.services.vlc_player.get_sound_file_path") as mock_get_path: with patch("app.services.vlc_player.get_sound_file_path") as mock_get_path:
@@ -177,7 +176,7 @@ class TestVLCPlayerService:
@pytest.mark.asyncio @pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_success(self, mock_subprocess, vlc_service): async def test_stop_all_vlc_instances_success(self, mock_subprocess, vlc_service) -> None:
"""Test successful stopping of all VLC instances.""" """Test successful stopping of all VLC instances."""
# Mock pgrep process (find VLC processes) # Mock pgrep process (find VLC processes)
mock_find_process = Mock() mock_find_process = Mock()
@@ -214,7 +213,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_no_processes( async def test_stop_all_vlc_instances_no_processes(
self, mock_subprocess, vlc_service, self, mock_subprocess, vlc_service,
): ) -> None:
"""Test stopping VLC instances when none are running.""" """Test stopping VLC instances when none are running."""
# Mock pgrep process (no VLC processes found) # Mock pgrep process (no VLC processes found)
mock_find_process = Mock() mock_find_process = Mock()
@@ -234,7 +233,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_partial_kill( async def test_stop_all_vlc_instances_partial_kill(
self, mock_subprocess, vlc_service, self, mock_subprocess, vlc_service,
): ) -> None:
"""Test stopping VLC instances when some processes remain.""" """Test stopping VLC instances when some processes remain."""
# Mock pgrep process (find VLC processes) # Mock pgrep process (find VLC processes)
mock_find_process = Mock() mock_find_process = Mock()
@@ -267,7 +266,7 @@ class TestVLCPlayerService:
@pytest.mark.asyncio @pytest.mark.asyncio
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_stop_all_vlc_instances_error(self, mock_subprocess, vlc_service): async def test_stop_all_vlc_instances_error(self, mock_subprocess, vlc_service) -> None:
"""Test stopping VLC instances when an error occurs.""" """Test stopping VLC instances when an error occurs."""
# Mock subprocess exception # Mock subprocess exception
mock_subprocess.side_effect = Exception("Command failed") mock_subprocess.side_effect = Exception("Command failed")
@@ -280,7 +279,7 @@ class TestVLCPlayerService:
assert "error" in result assert "error" in result
assert result["message"] == "Failed to stop VLC processes" assert result["message"] == "Failed to stop VLC processes"
def test_get_vlc_player_service_singleton(self): def test_get_vlc_player_service_singleton(self) -> None:
"""Test that get_vlc_player_service returns the same instance.""" """Test that get_vlc_player_service returns the same instance."""
with patch("app.services.vlc_player.VLCPlayerService") as mock_service_class: with patch("app.services.vlc_player.VLCPlayerService") as mock_service_class:
mock_instance = Mock() mock_instance = Mock()
@@ -306,7 +305,7 @@ class TestVLCPlayerService:
@patch("app.services.vlc_player.asyncio.create_subprocess_exec") @patch("app.services.vlc_player.asyncio.create_subprocess_exec")
async def test_play_sound_with_play_count_tracking( async def test_play_sound_with_play_count_tracking(
self, mock_subprocess, vlc_service_with_db, sample_sound, self, mock_subprocess, vlc_service_with_db, sample_sound,
): ) -> None:
"""Test sound playback with play count tracking.""" """Test sound playback with play count tracking."""
# Mock subprocess # Mock subprocess
mock_process = Mock() mock_process = Mock()
@@ -371,7 +370,7 @@ class TestVLCPlayerService:
# mocking or using a real async test framework setup # mocking or using a real async test framework setup
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_record_play_count_success(self, vlc_service_with_db): async def test_record_play_count_success(self, vlc_service_with_db) -> None:
"""Test successful play count recording.""" """Test successful play count recording."""
# Mock session and repositories # Mock session and repositories
mock_session = AsyncMock() mock_session = AsyncMock()
@@ -436,14 +435,14 @@ class TestVLCPlayerService:
) )
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_record_play_count_no_session_factory(self, vlc_service): async def test_record_play_count_no_session_factory(self, vlc_service) -> None:
"""Test play count recording when no session factory is available.""" """Test play count recording when no session factory is available."""
# This should not raise an error and should log a warning # This should not raise an error and should log a warning
await vlc_service._record_play_count(1, "Test Sound") await vlc_service._record_play_count(1, "Test Sound")
# The method should return early without doing anything # The method should return early without doing anything
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_record_play_count_always_creates_record(self, vlc_service_with_db): async def test_record_play_count_always_creates_record(self, vlc_service_with_db) -> None:
"""Test play count recording always creates a new SoundPlayed record.""" """Test play count recording always creates a new SoundPlayed record."""
# Mock session and repositories # Mock session and repositories
mock_session = AsyncMock() mock_session = AsyncMock()
@@ -493,7 +492,7 @@ class TestVLCPlayerService:
# Verify commit happened # Verify commit happened
mock_session.commit.assert_called_once() mock_session.commit.assert_called_once()
def test_uses_shared_sound_path_utility(self, vlc_service, sample_sound): def test_uses_shared_sound_path_utility(self, vlc_service, sample_sound) -> None:
"""Test that VLC service uses the shared sound path utility.""" """Test that VLC service uses the shared sound path utility."""
with patch("app.services.vlc_player.get_sound_file_path") as mock_path: with patch("app.services.vlc_player.get_sound_file_path") as mock_path:
mock_file_path = Mock(spec=Path) mock_file_path = Mock(spec=Path)

View File

@@ -1,5 +1,4 @@
"""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

View File

@@ -1,5 +1,4 @@
"""Tests for credit decorators.""" """Tests for credit decorators."""
# ruff: noqa: ARG001, ANN001, E501, PT012
from collections.abc import Callable from collections.abc import Callable
from typing import Never from typing import Never