Refactor user endpoint tests to include pagination and response structure validation

- Updated tests for listing users to validate pagination and response format.
- Changed mock return values to include total count and pagination details.
- Refactored user creation mocks for clarity and consistency.
- Enhanced assertions to check for presence of pagination fields in responses.
- Adjusted test cases for user retrieval and updates to ensure proper handling of user data.
- Improved readability by restructuring mock definitions and assertions across various test files.
This commit is contained in:
JSC
2025-08-17 12:36:52 +02:00
parent e6f796a3c9
commit 6b55ff0e81
35 changed files with 863 additions and 503 deletions

View File

@@ -21,8 +21,6 @@ def mock_plan_repository():
return Mock()
@pytest.fixture
def regular_user():
"""Create regular user for testing."""
@@ -60,52 +58,78 @@ class TestAdminUserEndpoints:
test_plan: Plan,
) -> None:
"""Test listing users successfully."""
with patch("app.repositories.user.UserRepository.get_all_with_plan") as mock_get_all:
with patch(
"app.repositories.user.UserRepository.get_all_with_plan_paginated",
) as mock_get_all:
# Create mock user objects that don't trigger database saves
mock_admin = type("User", (), {
"id": admin_user.id,
"email": admin_user.email,
"name": admin_user.name,
"picture": None,
"role": admin_user.role,
"credits": admin_user.credits,
"is_active": admin_user.is_active,
"created_at": admin_user.created_at,
"updated_at": admin_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_admin = type(
"User",
(),
{
"id": admin_user.id,
"email": admin_user.email,
"name": admin_user.name,
"picture": None,
"role": admin_user.role,
"credits": admin_user.credits,
"is_active": admin_user.is_active,
"created_at": admin_user.created_at,
"updated_at": admin_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_regular = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_regular = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_get_all.return_value = [mock_admin, mock_regular]
# Mock returns tuple (users, total_count)
mock_get_all.return_value = ([mock_admin, mock_regular], 2)
response = await authenticated_admin_client.get("/api/v1/admin/users/")
assert response.status_code == 200
data = response.json()
assert len(data) == 2
assert data[0]["email"] == "admin@example.com"
assert data[1]["email"] == "user@example.com"
mock_get_all.assert_called_once_with(limit=100, offset=0)
assert "users" in data
assert "total" in data
assert "page" in data
assert "limit" in data
assert "total_pages" in data
assert len(data["users"]) == 2
assert data["users"][0]["email"] == "admin@example.com"
assert data["users"][1]["email"] == "user@example.com"
assert data["total"] == 2
assert data["page"] == 1
assert data["limit"] == 50
@pytest.mark.asyncio
async def test_list_users_with_pagination(
@@ -115,29 +139,55 @@ class TestAdminUserEndpoints:
test_plan: Plan,
) -> None:
"""Test listing users with pagination."""
with patch("app.repositories.user.UserRepository.get_all_with_plan") as mock_get_all:
mock_admin = type("User", (), {
"id": admin_user.id,
"email": admin_user.email,
"name": admin_user.name,
"picture": None,
"role": admin_user.role,
"credits": admin_user.credits,
"is_active": admin_user.is_active,
"created_at": admin_user.created_at,
"updated_at": admin_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_get_all.return_value = [mock_admin]
from app.repositories.user import SortOrder, UserSortField, UserStatus
response = await authenticated_admin_client.get("/api/v1/admin/users/?limit=10&offset=5")
with patch(
"app.repositories.user.UserRepository.get_all_with_plan_paginated",
) as mock_get_all:
mock_admin = type(
"User",
(),
{
"id": admin_user.id,
"email": admin_user.email,
"name": admin_user.name,
"picture": None,
"role": admin_user.role,
"credits": admin_user.credits,
"is_active": admin_user.is_active,
"created_at": admin_user.created_at,
"updated_at": admin_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
# Mock returns tuple (users, total_count)
mock_get_all.return_value = ([mock_admin], 1)
response = await authenticated_admin_client.get(
"/api/v1/admin/users/?page=2&limit=10",
)
assert response.status_code == 200
mock_get_all.assert_called_once_with(limit=10, offset=5)
data = response.json()
assert "users" in data
assert data["page"] == 2
assert data["limit"] == 10
mock_get_all.assert_called_once_with(
page=2,
limit=10,
search=None,
sort_by=UserSortField.NAME,
sort_order=SortOrder.ASC,
status_filter=UserStatus.ALL,
)
@pytest.mark.asyncio
async def test_list_users_unauthenticated(self, client: AsyncClient) -> None:
@@ -153,7 +203,9 @@ class TestAdminUserEndpoints:
regular_user: User,
) -> None:
"""Test listing users as non-admin user."""
with patch("app.core.dependencies.get_current_active_user", return_value=regular_user):
with patch(
"app.core.dependencies.get_current_active_user", return_value=regular_user,
):
response = await client.get("/api/v1/admin/users/")
assert response.status_code == 401
@@ -169,24 +221,34 @@ class TestAdminUserEndpoints:
"""Test getting specific user successfully."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan") as mock_get_by_id,
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
) as mock_get_by_id,
):
mock_user = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_user = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_get_by_id.return_value = mock_user
response = await authenticated_admin_client.get("/api/v1/admin/users/2")
@@ -207,7 +269,10 @@ class TestAdminUserEndpoints:
"""Test getting non-existent user."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan", return_value=None),
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
return_value=None,
),
):
response = await authenticated_admin_client.get("/api/v1/admin/users/999")
@@ -226,43 +291,63 @@ class TestAdminUserEndpoints:
"""Test updating user successfully."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan") as mock_get_by_id,
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
) as mock_get_by_id,
patch("app.repositories.user.UserRepository.update") as mock_update,
patch("app.repositories.plan.PlanRepository.get_by_id", return_value=test_plan),
patch(
"app.repositories.plan.PlanRepository.get_by_id", return_value=test_plan,
),
):
mock_user = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_user = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
updated_mock = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": "Updated Name",
"picture": None,
"role": regular_user.role,
"credits": 200,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
updated_mock = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": "Updated Name",
"picture": None,
"role": regular_user.role,
"credits": 200,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_get_by_id.return_value = mock_user
mock_update.return_value = updated_mock
@@ -271,7 +356,10 @@ class TestAdminUserEndpoints:
async def mock_refresh(instance, attributes=None):
pass
with patch("sqlmodel.ext.asyncio.session.AsyncSession.refresh", side_effect=mock_refresh):
with patch(
"sqlmodel.ext.asyncio.session.AsyncSession.refresh",
side_effect=mock_refresh,
):
response = await authenticated_admin_client.patch(
"/api/v1/admin/users/2",
json={
@@ -295,7 +383,10 @@ class TestAdminUserEndpoints:
"""Test updating non-existent user."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan", return_value=None),
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
return_value=None,
),
):
response = await authenticated_admin_client.patch(
"/api/v1/admin/users/999",
@@ -316,25 +407,35 @@ class TestAdminUserEndpoints:
"""Test updating user with invalid plan."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan") as mock_get_by_id,
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
) as mock_get_by_id,
patch("app.repositories.plan.PlanRepository.get_by_id", return_value=None),
):
mock_user = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": 1,
"name": "Basic",
"max_credits": 100,
})(),
})()
mock_user = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": 1,
"name": "Basic",
"max_credits": 100,
},
)(),
},
)()
mock_get_by_id.return_value = mock_user
response = await authenticated_admin_client.patch(
"/api/v1/admin/users/2",
@@ -356,29 +457,41 @@ class TestAdminUserEndpoints:
"""Test disabling user successfully."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan") as mock_get_by_id,
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
) as mock_get_by_id,
patch("app.repositories.user.UserRepository.update") as mock_update,
):
mock_user = type("User", (), {
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_user = type(
"User",
(),
{
"id": regular_user.id,
"email": regular_user.email,
"name": regular_user.name,
"picture": None,
"role": regular_user.role,
"credits": regular_user.credits,
"is_active": regular_user.is_active,
"created_at": regular_user.created_at,
"updated_at": regular_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_get_by_id.return_value = mock_user
mock_update.return_value = mock_user
response = await authenticated_admin_client.post("/api/v1/admin/users/2/disable")
response = await authenticated_admin_client.post(
"/api/v1/admin/users/2/disable",
)
assert response.status_code == 200
data = response.json()
@@ -393,9 +506,14 @@ class TestAdminUserEndpoints:
"""Test disabling non-existent user."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan", return_value=None),
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
return_value=None,
),
):
response = await authenticated_admin_client.post("/api/v1/admin/users/999/disable")
response = await authenticated_admin_client.post(
"/api/v1/admin/users/999/disable",
)
assert response.status_code == 404
data = response.json()
@@ -421,29 +539,41 @@ class TestAdminUserEndpoints:
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan") as mock_get_by_id,
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
) as mock_get_by_id,
patch("app.repositories.user.UserRepository.update") as mock_update,
):
mock_disabled_user = type("User", (), {
"id": disabled_user.id,
"email": disabled_user.email,
"name": disabled_user.name,
"picture": None,
"role": disabled_user.role,
"credits": disabled_user.credits,
"is_active": disabled_user.is_active,
"created_at": disabled_user.created_at,
"updated_at": disabled_user.updated_at,
"plan": type("Plan", (), {
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
})(),
})()
mock_disabled_user = type(
"User",
(),
{
"id": disabled_user.id,
"email": disabled_user.email,
"name": disabled_user.name,
"picture": None,
"role": disabled_user.role,
"credits": disabled_user.credits,
"is_active": disabled_user.is_active,
"created_at": disabled_user.created_at,
"updated_at": disabled_user.updated_at,
"plan": type(
"Plan",
(),
{
"id": test_plan.id,
"name": test_plan.name,
"max_credits": test_plan.max_credits,
},
)(),
},
)()
mock_get_by_id.return_value = mock_disabled_user
mock_update.return_value = mock_disabled_user
response = await authenticated_admin_client.post("/api/v1/admin/users/3/enable")
response = await authenticated_admin_client.post(
"/api/v1/admin/users/3/enable",
)
assert response.status_code == 200
data = response.json()
@@ -458,9 +588,14 @@ class TestAdminUserEndpoints:
"""Test enabling non-existent user."""
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.user.UserRepository.get_by_id_with_plan", return_value=None),
patch(
"app.repositories.user.UserRepository.get_by_id_with_plan",
return_value=None,
),
):
response = await authenticated_admin_client.post("/api/v1/admin/users/999/enable")
response = await authenticated_admin_client.post(
"/api/v1/admin/users/999/enable",
)
assert response.status_code == 404
data = response.json()
@@ -479,9 +614,14 @@ class TestAdminUserEndpoints:
with (
patch("app.core.dependencies.get_admin_user", return_value=admin_user),
patch("app.repositories.plan.PlanRepository.get_all", return_value=[basic_plan, premium_plan]),
patch(
"app.repositories.plan.PlanRepository.get_all",
return_value=[basic_plan, premium_plan],
),
):
response = await authenticated_admin_client.get("/api/v1/admin/users/plans/list")
response = await authenticated_admin_client.get(
"/api/v1/admin/users/plans/list",
)
assert response.status_code == 200
data = response.json()

View File

@@ -488,11 +488,17 @@ class TestAuthEndpoints:
test_plan: Plan,
) -> None:
"""Test refresh token success."""
with patch("app.services.auth.AuthService.refresh_access_token") as mock_refresh:
mock_refresh.return_value = type("TokenResponse", (), {
"access_token": "new_access_token",
"expires_in": 3600,
})()
with patch(
"app.services.auth.AuthService.refresh_access_token",
) as mock_refresh:
mock_refresh.return_value = type(
"TokenResponse",
(),
{
"access_token": "new_access_token",
"expires_in": 3600,
},
)()
response = await test_client.post(
"/api/v1/auth/refresh",
@@ -516,7 +522,9 @@ class TestAuthEndpoints:
@pytest.mark.asyncio
async def test_refresh_token_service_error(self, test_client: AsyncClient) -> None:
"""Test refresh token with service error."""
with patch("app.services.auth.AuthService.refresh_access_token") as mock_refresh:
with patch(
"app.services.auth.AuthService.refresh_access_token",
) as mock_refresh:
mock_refresh.side_effect = Exception("Database error")
response = await test_client.post(
@@ -528,7 +536,6 @@ class TestAuthEndpoints:
data = response.json()
assert "Token refresh failed" in data["detail"]
@pytest.mark.asyncio
async def test_exchange_oauth_token_invalid_code(
self,
@@ -554,7 +561,9 @@ class TestAuthEndpoints:
"""Test update profile success."""
with (
patch("app.services.auth.AuthService.update_user_profile") as mock_update,
patch("app.services.auth.AuthService.user_to_response") as mock_user_to_response,
patch(
"app.services.auth.AuthService.user_to_response",
) as mock_user_to_response,
):
updated_user = User(
id=test_user.id,
@@ -569,6 +578,7 @@ class TestAuthEndpoints:
# Mock the user_to_response to return UserResponse format
from app.schemas.auth import UserResponse
mock_user_to_response.return_value = UserResponse(
id=test_user.id,
email=test_user.email,
@@ -598,7 +608,9 @@ class TestAuthEndpoints:
assert data["name"] == "Updated Name"
@pytest.mark.asyncio
async def test_update_profile_unauthenticated(self, test_client: AsyncClient) -> None:
async def test_update_profile_unauthenticated(
self, test_client: AsyncClient,
) -> None:
"""Test update profile without authentication."""
response = await test_client.patch(
"/api/v1/auth/me",
@@ -632,7 +644,9 @@ class TestAuthEndpoints:
assert data["message"] == "Password changed successfully"
@pytest.mark.asyncio
async def test_change_password_unauthenticated(self, test_client: AsyncClient) -> None:
async def test_change_password_unauthenticated(
self, test_client: AsyncClient,
) -> None:
"""Test change password without authentication."""
response = await test_client.post(
"/api/v1/auth/change-password",
@@ -652,7 +666,9 @@ class TestAuthEndpoints:
auth_cookies: dict[str, str],
) -> None:
"""Test get user OAuth providers success."""
with patch("app.services.auth.AuthService.get_user_oauth_providers") as mock_providers:
with patch(
"app.services.auth.AuthService.get_user_oauth_providers",
) as mock_providers:
from datetime import datetime
from app.models.user_oauth import UserOauth
@@ -699,7 +715,9 @@ class TestAuthEndpoints:
assert data[2]["display_name"] == "GitHub"
@pytest.mark.asyncio
async def test_get_user_providers_unauthenticated(self, test_client: AsyncClient) -> None:
async def test_get_user_providers_unauthenticated(
self, test_client: AsyncClient,
) -> None:
"""Test get user OAuth providers without authentication."""
response = await test_client.get("/api/v1/auth/user-providers")

View File

@@ -109,9 +109,15 @@ class TestPlaylistEndpoints:
assert response.status_code == 200
data = response.json()
assert len(data) == 2
assert "playlists" in data
assert "total" in data
assert "page" in data
assert "limit" in data
assert "total_pages" in data
assert len(data["playlists"]) == 2
assert data["total"] == 2
playlist_names = {p["name"] for p in data}
playlist_names = {p["name"] for p in data["playlists"]}
assert "Test Playlist" in playlist_names
assert "Main Playlist" in playlist_names

View File

@@ -9,7 +9,7 @@ from httpx import AsyncClient
from app.models.user import User
if TYPE_CHECKING:
from app.services.extraction import ExtractionInfo
from app.services.extraction import ExtractionInfo, PaginatedExtractionsResponse
class TestSoundEndpoints:
@@ -32,6 +32,7 @@ class TestSoundEndpoints:
"error": None,
"sound_id": None,
"user_id": authenticated_user.id,
"user_name": authenticated_user.name,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
}
@@ -111,6 +112,7 @@ class TestSoundEndpoints:
"error": None,
"sound_id": 42,
"user_id": authenticated_user.id,
"user_name": authenticated_user.name,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
}
@@ -154,41 +156,49 @@ class TestSoundEndpoints:
authenticated_user: User,
) -> None:
"""Test getting user extractions."""
mock_extractions: list[ExtractionInfo] = [
{
"id": 1,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Never Gonna Give You Up",
"service": "youtube",
"service_id": "dQw4w9WgXcQ",
"status": "completed",
"error": None,
"sound_id": 42,
"user_id": authenticated_user.id,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
},
{
"id": 2,
"url": "https://soundcloud.com/example/track",
"title": "Example Track",
"service": "soundcloud",
"service_id": "example-track",
"status": "pending",
"error": None,
"sound_id": None,
"user_id": authenticated_user.id,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
},
]
mock_extractions: PaginatedExtractionsResponse = {
"extractions": [
{
"id": 1,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"title": "Never Gonna Give You Up",
"service": "youtube",
"service_id": "dQw4w9WgXcQ",
"status": "completed",
"error": None,
"sound_id": 42,
"user_id": authenticated_user.id,
"user_name": authenticated_user.name,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
},
{
"id": 2,
"url": "https://soundcloud.com/example/track",
"title": "Example Track",
"service": "soundcloud",
"service_id": "example-track",
"status": "pending",
"error": None,
"sound_id": None,
"user_id": authenticated_user.id,
"user_name": authenticated_user.name,
"created_at": "2025-08-03T12:00:00Z",
"updated_at": "2025-08-03T12:00:00Z",
},
],
"total": 2,
"page": 1,
"limit": 50,
"total_pages": 1,
}
with patch(
"app.services.extraction.ExtractionService.get_user_extractions",
) as mock_get:
mock_get.return_value = mock_extractions
response = await authenticated_client.get("/api/v1/extractions/")
response = await authenticated_client.get("/api/v1/extractions/user")
assert response.status_code == 200
data = response.json()
@@ -337,7 +347,9 @@ class TestSoundEndpoints:
"""Test getting sounds with authentication."""
from app.models.sound import Sound
with patch("app.repositories.sound.SoundRepository.search_and_sort") as mock_get:
with patch(
"app.repositories.sound.SoundRepository.search_and_sort",
) as mock_get:
# Create mock sounds with all required fields
mock_sound_1 = Sound(
id=1,
@@ -383,7 +395,9 @@ class TestSoundEndpoints:
"""Test getting sounds with type filtering."""
from app.models.sound import Sound
with patch("app.repositories.sound.SoundRepository.search_and_sort") as mock_get:
with patch(
"app.repositories.sound.SoundRepository.search_and_sort",
) as mock_get:
# Create mock sound with all required fields
mock_sound = Sound(
id=1,

View File

@@ -335,5 +335,3 @@ async def admin_cookies(admin_user: User) -> dict[str, str]:
access_token = JWTUtils.create_access_token(token_data)
return {"access_token": access_token}

View File

@@ -539,21 +539,35 @@ class TestPlaylistRepository:
sound_ids = [s.id for s in sounds]
# Add first two sounds sequentially (positions 0, 1)
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[0]) # position 0
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[1]) # position 1
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[0],
) # position 0
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[1],
) # position 1
# Now insert third sound at position 1 - should shift existing sound at position 1 to position 2
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[2], position=1)
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[2], position=1,
)
# Verify the final positions
playlist_sounds = await playlist_repository.get_playlist_sound_entries(playlist_id)
playlist_sounds = await playlist_repository.get_playlist_sound_entries(
playlist_id,
)
assert len(playlist_sounds) == 3
assert playlist_sounds[0].sound_id == sound_ids[0] # Original sound 0 stays at position 0
assert (
playlist_sounds[0].sound_id == sound_ids[0]
) # Original sound 0 stays at position 0
assert playlist_sounds[0].position == 0
assert playlist_sounds[1].sound_id == sound_ids[2] # New sound 2 inserted at position 1
assert (
playlist_sounds[1].sound_id == sound_ids[2]
) # New sound 2 inserted at position 1
assert playlist_sounds[1].position == 1
assert playlist_sounds[2].sound_id == sound_ids[1] # Original sound 1 shifted to position 2
assert (
playlist_sounds[2].sound_id == sound_ids[1]
) # Original sound 1 shifted to position 2
assert playlist_sounds[2].position == 2
@pytest.mark.asyncio
@@ -615,21 +629,35 @@ class TestPlaylistRepository:
sound_ids = [s.id for s in sounds]
# Add first two sounds sequentially (positions 0, 1)
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[0]) # position 0
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[1]) # position 1
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[0],
) # position 0
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[1],
) # position 1
# Now insert third sound at position 0 - should shift existing sounds to positions 1, 2
await playlist_repository.add_sound_to_playlist(playlist_id, sound_ids[2], position=0)
await playlist_repository.add_sound_to_playlist(
playlist_id, sound_ids[2], position=0,
)
# Verify the final positions
playlist_sounds = await playlist_repository.get_playlist_sound_entries(playlist_id)
playlist_sounds = await playlist_repository.get_playlist_sound_entries(
playlist_id,
)
assert len(playlist_sounds) == 3
assert playlist_sounds[0].sound_id == sound_ids[2] # New sound 2 inserted at position 0
assert (
playlist_sounds[0].sound_id == sound_ids[2]
) # New sound 2 inserted at position 0
assert playlist_sounds[0].position == 0
assert playlist_sounds[1].sound_id == sound_ids[0] # Original sound 0 shifted to position 1
assert (
playlist_sounds[1].sound_id == sound_ids[0]
) # Original sound 0 shifted to position 1
assert playlist_sounds[1].position == 1
assert playlist_sounds[2].sound_id == sound_ids[1] # Original sound 1 shifted to position 2
assert (
playlist_sounds[2].sound_id == sound_ids[1]
) # Original sound 1 shifted to position 2
assert playlist_sounds[2].position == 2
@pytest.mark.asyncio

View File

@@ -409,7 +409,9 @@ class TestCreditService:
@pytest.mark.asyncio
async def test_recharge_user_credits_success(
self, credit_service, sample_user,
self,
credit_service,
sample_user,
) -> None:
"""Test successful credit recharge for a user."""
mock_session = credit_service.db_session_factory()

View File

@@ -43,7 +43,9 @@ class TestDashboardService:
"total_duration": 75000,
"total_size": 1024000,
}
mock_sound_repository.get_soundboard_statistics = AsyncMock(return_value=mock_stats)
mock_sound_repository.get_soundboard_statistics = AsyncMock(
return_value=mock_stats,
)
result = await dashboard_service.get_soundboard_statistics()

View File

@@ -99,6 +99,11 @@ class TestExtractionService:
url = "https://www.youtube.com/watch?v=test123"
user_id = 1
# Mock user for user_name retrieval
mock_user = Mock()
mock_user.name = "Test User"
extraction_service.user_repo.get_by_id = AsyncMock(return_value=mock_user)
# Mock repository call - no service detection happens during creation
mock_extraction = Extraction(
id=1,
@@ -120,6 +125,7 @@ class TestExtractionService:
assert result["service_id"] is None # Not detected during creation
assert result["title"] is None # Not detected during creation
assert result["status"] == "pending"
assert result["user_name"] == "Test User"
@pytest.mark.asyncio
async def test_create_extraction_basic(self, extraction_service) -> None:
@@ -127,6 +133,11 @@ class TestExtractionService:
url = "https://www.youtube.com/watch?v=test123"
user_id = 1
# Mock user for user_name retrieval
mock_user = Mock()
mock_user.name = "Test User"
extraction_service.user_repo.get_by_id = AsyncMock(return_value=mock_user)
# Mock repository call - creation always succeeds now
mock_extraction = Extraction(
id=2,
@@ -146,6 +157,7 @@ class TestExtractionService:
assert result["id"] == 2
assert result["url"] == url
assert result["status"] == "pending"
assert result["user_name"] == "Test User"
@pytest.mark.asyncio
async def test_create_extraction_any_url(self, extraction_service) -> None:
@@ -153,6 +165,11 @@ class TestExtractionService:
url = "https://invalid.url"
user_id = 1
# Mock user for user_name retrieval
mock_user = Mock()
mock_user.name = "Test User"
extraction_service.user_repo.get_by_id = AsyncMock(return_value=mock_user)
# Mock repository call - even invalid URLs are accepted during creation
mock_extraction = Extraction(
id=3,
@@ -172,6 +189,7 @@ class TestExtractionService:
assert result["id"] == 3
assert result["url"] == url
assert result["status"] == "pending"
assert result["user_name"] == "Test User"
@pytest.mark.asyncio
async def test_process_extraction_with_service_detection(
@@ -408,9 +426,16 @@ class TestExtractionService:
sound_id=42,
)
# Mock user for user_name retrieval
mock_user = Mock()
mock_user.name = "Test User"
extraction_service.extraction_repo.get_by_id = AsyncMock(
return_value=extraction,
)
extraction_service.user_repo.get_by_id = AsyncMock(
return_value=mock_user,
)
result = await extraction_service.get_extraction_by_id(1)
@@ -421,6 +446,7 @@ class TestExtractionService:
assert result["title"] == "Test Video"
assert result["status"] == "completed"
assert result["sound_id"] == 42
assert result["user_name"] == "Test User"
@pytest.mark.asyncio
async def test_get_extraction_by_id_not_found(self, extraction_service) -> None:
@@ -434,52 +460,70 @@ class TestExtractionService:
@pytest.mark.asyncio
async def test_get_user_extractions(self, extraction_service) -> None:
"""Test getting user extractions."""
from app.models.user import User
user = User(id=1, name="Test User", email="test@example.com")
extractions = [
Extraction(
id=1,
service="youtube",
service_id="test123",
url="https://www.youtube.com/watch?v=test123",
user_id=1,
title="Test Video 1",
status="completed",
sound_id=42,
(
Extraction(
id=1,
service="youtube",
service_id="test123",
url="https://www.youtube.com/watch?v=test123",
user_id=1,
title="Test Video 1",
status="completed",
sound_id=42,
),
user,
),
Extraction(
id=2,
service="youtube",
service_id="test456",
url="https://www.youtube.com/watch?v=test456",
user_id=1,
title="Test Video 2",
status="pending",
(
Extraction(
id=2,
service="youtube",
service_id="test456",
url="https://www.youtube.com/watch?v=test456",
user_id=1,
title="Test Video 2",
status="pending",
),
user,
),
]
extraction_service.extraction_repo.get_by_user = AsyncMock(
return_value=extractions,
extraction_service.extraction_repo.get_user_extractions_filtered = AsyncMock(
return_value=(extractions, 2),
)
result = await extraction_service.get_user_extractions(1)
assert len(result) == 2
assert result[0]["id"] == 1
assert result[0]["title"] == "Test Video 1"
assert result[1]["id"] == 2
assert result[1]["title"] == "Test Video 2"
assert result["total"] == 2
assert len(result["extractions"]) == 2
assert result["extractions"][0]["id"] == 1
assert result["extractions"][0]["title"] == "Test Video 1"
assert result["extractions"][0]["user_name"] == "Test User"
assert result["extractions"][1]["id"] == 2
assert result["extractions"][1]["title"] == "Test Video 2"
assert result["extractions"][1]["user_name"] == "Test User"
@pytest.mark.asyncio
async def test_get_pending_extractions(self, extraction_service) -> None:
"""Test getting pending extractions."""
from app.models.user import User
user = User(id=1, name="Test User", email="test@example.com")
pending_extractions = [
Extraction(
id=1,
service="youtube",
service_id="test123",
url="https://www.youtube.com/watch?v=test123",
user_id=1,
title="Pending Video",
status="pending",
(
Extraction(
id=1,
service="youtube",
service_id="test123",
url="https://www.youtube.com/watch?v=test123",
user_id=1,
title="Pending Video",
status="pending",
),
user,
),
]
@@ -492,3 +536,4 @@ class TestExtractionService:
assert len(result) == 1
assert result[0]["id"] == 1
assert result[0]["status"] == "pending"
assert result[0]["user_name"] == "Test User"

View File

@@ -25,9 +25,10 @@ class TestSchedulerService:
@pytest.mark.asyncio
async def test_start_scheduler(self, scheduler_service) -> None:
"""Test starting the scheduler service."""
with patch.object(scheduler_service.scheduler, "add_job") as mock_add_job, \
patch.object(scheduler_service.scheduler, "start") as mock_start:
with (
patch.object(scheduler_service.scheduler, "add_job") as mock_add_job,
patch.object(scheduler_service.scheduler, "start") as mock_start,
):
await scheduler_service.start()
# Verify job was added
@@ -61,7 +62,9 @@ class TestSchedulerService:
"total_credits_added": 500,
}
with patch.object(scheduler_service.credit_service, "recharge_all_users_credits") as mock_recharge:
with patch.object(
scheduler_service.credit_service, "recharge_all_users_credits",
) as mock_recharge:
mock_recharge.return_value = mock_stats
await scheduler_service._daily_credit_recharge()
@@ -71,7 +74,9 @@ class TestSchedulerService:
@pytest.mark.asyncio
async def test_daily_credit_recharge_failure(self, scheduler_service) -> None:
"""Test daily credit recharge task with failure."""
with patch.object(scheduler_service.credit_service, "recharge_all_users_credits") as mock_recharge:
with patch.object(
scheduler_service.credit_service, "recharge_all_users_credits",
) as mock_recharge:
mock_recharge.side_effect = Exception("Database error")
# Should not raise exception, just log it