ytptube/app/features/presets/tests/test_presets_repository.py
2026-06-14 18:35:03 +03:00

179 lines
7.5 KiB
Python

from __future__ import annotations
import json
from unittest.mock import MagicMock
import pytest
import pytest_asyncio
from aiohttp import web
from aiohttp.web import Request
from app.features.presets.repository import PresetsRepository
from app.features.presets.router import presets_list
from app.library.encoder import Encoder
from app.library.sqlite_store import SqliteStore
from app.tests.helpers import make_in_memory_db_path
@pytest_asyncio.fixture
async def repo():
PresetsRepository._reset_singleton()
SqliteStore._reset_singleton()
store = SqliteStore(db_path=make_in_memory_db_path("presets-repository"))
await store.get_connection()
repository = PresetsRepository.get_instance()
yield repository
await store.close()
PresetsRepository._reset_singleton()
SqliteStore._reset_singleton()
class TestPresetsRepository:
@pytest.mark.asyncio
async def test_create_and_get(self, repo):
preset = await repo.create({"name": "Custom", "cli": "--format best"})
assert preset.id is not None, "Should generate ID for new preset"
assert preset.name == "custom", "Should normalize preset name"
assert preset.cli == "--format best", "Should store preset cli"
fetched = await repo.get(preset.id)
assert fetched is not None, "Should fetch preset by id"
assert fetched.name == "custom", "Should return matching preset"
@pytest.mark.asyncio
async def test_create_normalizes_spaces(self, repo):
preset = await repo.create({"name": "My Preset"})
assert preset.name == "my_preset", "Should normalize spaces to underscores"
@pytest.mark.asyncio
async def test_list_orders_by_priority_then_name(self, repo):
await repo.create({"name": "B", "priority": 1})
await repo.create({"name": "A", "priority": 1})
await repo.create({"name": "C", "priority": 2})
items = await repo.all()
assert items[0].name == "c", "Highest priority should be first"
assert items[1].name == "a", "Same priority should sort by name"
assert items[2].name == "b", "Same priority should sort by name"
@pytest.mark.asyncio
async def test_list_paginated(self, repo):
for i in range(5):
await repo.create({"name": f"Item {i}", "priority": i})
items, total, page, total_pages = await repo.list_paginated(page=1, per_page=2)
assert len(items) == 2, "Should return 2 items per page"
assert total == 5, "Should report total count"
assert page == 1, "Should be on page 1"
assert total_pages == 3, "Should have 3 pages total"
assert [item.priority for item in items] == [4, 3], "Should keep default priority-desc order"
@pytest.mark.asyncio
async def test_list_paginated_sorts_by_name_desc(self, repo):
await repo.create({"name": "Alpha", "priority": 1})
await repo.create({"name": "Gamma", "priority": 3})
await repo.create({"name": "Beta", "priority": 2})
items, _, _, _ = await repo.list_paginated(page=1, per_page=10, sort="name", order="desc")
assert [item.name for item in items] == ["gamma", "beta", "alpha"], "Should sort by requested field"
@pytest.mark.asyncio
async def test_list_paginated_excludes_defaults(self, repo):
await repo.create({"name": "System Default", "default": True, "priority": 10})
await repo.create({"name": "Custom Preset", "priority": 1})
items, total, page, total_pages = await repo.list_paginated(page=1, per_page=10, exclude_defaults=True)
assert [item.name for item in items] == ["custom_preset"], "Should exclude default presets"
assert total == 1, "Should count only custom presets"
assert page == 1, "Should keep current page when filtered results exist"
assert total_pages == 1, "Should compute pages from the filtered total"
@pytest.mark.asyncio
async def test_list_paginated_multi_sort(self, repo):
await repo.create({"name": "Charlie", "priority": 2})
await repo.create({"name": "Alpha", "priority": 1})
await repo.create({"name": "Bravo", "priority": 1})
items, _, _, _ = await repo.list_paginated(page=1, per_page=10, sort="priority,name", order="asc,desc")
assert [(item.priority, item.name) for item in items] == [
(1, "bravo"),
(1, "alpha"),
(2, "charlie"),
], "Should support multiple sort fields and directions"
@pytest.mark.asyncio
async def test_list_paginated_bad_sort(self, repo):
with pytest.raises(ValueError, match="sort must use supported fields"):
await repo.list_paginated(page=1, per_page=10, sort="cli", order="asc")
@pytest.mark.asyncio
async def test_list_paginated_bad_order(self, repo):
with pytest.raises(ValueError, match="order must be 'asc' or 'desc'"):
await repo.list_paginated(page=1, per_page=10, sort="name", order="sideways")
@pytest.mark.asyncio
async def test_list_paginated_mismatched_order(self, repo):
with pytest.raises(ValueError, match="order must provide one direction or match the number of sort fields"):
await repo.list_paginated(page=1, per_page=10, sort="priority,name", order="asc,desc,asc")
@pytest.mark.asyncio
class TestPresetRoutes:
async def test_list_route_sort(self, repo):
await repo.create({"name": "Alpha", "priority": 1})
await repo.create({"name": "Bravo", "priority": 1})
await repo.create({"name": "Charlie", "priority": 2})
request = MagicMock(spec=Request)
request.query = {"page": "1", "per_page": "10", "sort": "priority,name", "order": "asc,desc"}
response = await presets_list(request, Encoder(), repo)
payload = json.loads(response.text)
assert response.status == web.HTTPOk.status_code, "Should return 200 for valid sorting"
assert [item["name"] for item in payload["items"]] == ["bravo", "alpha", "charlie"], "Should sort response"
async def test_list_route_bad_sort(self, repo):
request = MagicMock(spec=Request)
request.query = {"sort": "cli", "order": "asc"}
response = await presets_list(request, Encoder(), repo)
payload = json.loads(response.text)
assert response.status == web.HTTPBadRequest.status_code, "Should reject unsupported sort field"
assert "sort" in payload["error"], "Should explain invalid sort field"
async def test_list_route_bad_order(self, repo):
request = MagicMock(spec=Request)
request.query = {"sort": "name", "order": "sideways"}
response = await presets_list(request, Encoder(), repo)
payload = json.loads(response.text)
assert response.status == web.HTTPBadRequest.status_code, "Should reject unsupported sort direction"
assert "order" in payload["error"], "Should explain invalid sort direction"
async def test_list_route_exclude_defaults(self, repo):
await repo.create({"name": "System Default", "default": True, "priority": 10})
await repo.create({"name": "Custom Preset", "priority": 1})
request = MagicMock(spec=Request)
request.query = {"page": "1", "per_page": "10", "exclude_defaults": "true"}
response = await presets_list(request, Encoder(), repo)
payload = json.loads(response.text)
assert response.status == web.HTTPOk.status_code, "Should return 200 for valid default exclusion"
assert [item["name"] for item in payload["items"]] == ["custom_preset"], "Should exclude default presets"
assert payload["pagination"]["total"] == 1, "Should report filtered total"