267 lines
9.9 KiB
Python
267 lines
9.9 KiB
Python
"""Tests for TasksRepository."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
|
|
from app.features.tasks.repository import TasksRepository
|
|
from app.library.sqlite_store import SqliteStore
|
|
from app.tests.helpers import make_in_memory_db_path
|
|
|
|
|
|
@pytest_asyncio.fixture
|
|
async def repo():
|
|
"""Provide a fresh repository instance with initialized database for each test."""
|
|
TasksRepository._reset_singleton()
|
|
SqliteStore._reset_singleton()
|
|
|
|
store = SqliteStore(db_path=make_in_memory_db_path("tasks-repository"))
|
|
await store.get_connection()
|
|
|
|
repository = TasksRepository.get_instance()
|
|
|
|
yield repository
|
|
|
|
await store.close()
|
|
|
|
TasksRepository._reset_singleton()
|
|
SqliteStore._reset_singleton()
|
|
|
|
|
|
class TestTasksRepository:
|
|
"""Test suite for TasksRepository database operations."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_task(self, repo):
|
|
"""Create task with valid data."""
|
|
data = {
|
|
"name": "Daily Download",
|
|
"url": "https://example.com/video",
|
|
"folder": "/downloads",
|
|
"preset": "audio",
|
|
"timer": "0 0 * * *",
|
|
"template": "%(title)s.%(ext)s",
|
|
"cli": "--format best",
|
|
"auto_start": True,
|
|
"handler_enabled": True,
|
|
"enabled": True,
|
|
}
|
|
|
|
model = await repo.create(data)
|
|
|
|
assert model.id is not None, "Should generate ID for new task"
|
|
assert model.name == "Daily Download", "Should store name correctly"
|
|
assert model.url == "https://example.com/video", "Should store URL correctly"
|
|
assert model.folder == "/downloads", "Should store folder correctly"
|
|
assert model.preset == "audio", "Should store preset correctly"
|
|
assert model.timer == "0 0 * * *", "Should store timer correctly"
|
|
assert model.template == "%(title)s.%(ext)s", "Should store template correctly"
|
|
assert model.cli == "--format best", "Should store CLI correctly"
|
|
assert model.auto_start is True, "Should store auto_start correctly"
|
|
assert model.handler_enabled is True, "Should store handler_enabled correctly"
|
|
assert model.enabled is True, "Should store enabled correctly"
|
|
assert model.created_at is not None, "Should have created_at timestamp"
|
|
assert model.updated_at is not None, "Should have updated_at timestamp"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_with_minimal_data(self, repo):
|
|
"""Create task with minimal required data."""
|
|
data = {
|
|
"name": "Simple Task",
|
|
"url": "https://example.com",
|
|
}
|
|
|
|
model = await repo.create(data)
|
|
|
|
assert model.name == "Simple Task", "Should store name"
|
|
assert model.url == "https://example.com", "Should store URL"
|
|
assert model.folder == "", "Should default folder to empty string"
|
|
assert model.preset == "", "Should default preset to empty string"
|
|
assert model.timer == "", "Should default timer to empty string"
|
|
assert model.template == "", "Should default template to empty string"
|
|
assert model.cli == "", "Should default CLI to empty string"
|
|
assert model.auto_start is True, "Should default auto_start to True"
|
|
assert model.handler_enabled is True, "Should default handler_enabled to True"
|
|
assert model.enabled is True, "Should default enabled to True"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_id(self, repo):
|
|
"""Get task by integer ID."""
|
|
created = await repo.create(
|
|
{
|
|
"name": "Get Test",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
retrieved = await repo.get(created.id)
|
|
|
|
assert retrieved is not None, "Should retrieve created task"
|
|
assert retrieved.id == created.id, "Should retrieve correct task by ID"
|
|
assert retrieved.name == "Get Test", "Should match created task name"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_string_id(self, repo):
|
|
"""Get task by string ID."""
|
|
created = await repo.create(
|
|
{
|
|
"name": "String ID Test",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
retrieved = await repo.get(str(created.id))
|
|
|
|
assert retrieved is not None, "Should retrieve by string ID"
|
|
assert retrieved.id == created.id, "Should match created task ID"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_nonexistent(self, repo):
|
|
"""Get nonexistent task returns None."""
|
|
result = await repo.get(99999)
|
|
assert result is None, "Should return None for nonexistent ID"
|
|
|
|
result = await repo.get("99999")
|
|
assert result is None, "Should return None for nonexistent string ID"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_task(self, repo):
|
|
"""Update existing task."""
|
|
created = await repo.create(
|
|
{
|
|
"name": "Update Test",
|
|
"url": "https://example.com",
|
|
"preset": "video",
|
|
"enabled": True,
|
|
}
|
|
)
|
|
|
|
updated = await repo.update(
|
|
created.id,
|
|
{
|
|
"name": "Updated Name",
|
|
"preset": "audio",
|
|
"enabled": False,
|
|
},
|
|
)
|
|
|
|
assert updated.name == "Updated Name", "Should update name"
|
|
assert updated.preset == "audio", "Should update preset"
|
|
assert updated.enabled is False, "Should update enabled"
|
|
assert updated.url == "https://example.com", "Should preserve unchanged URL"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_nonexistent_raises(self, repo):
|
|
"""Update nonexistent task raises KeyError."""
|
|
with pytest.raises(KeyError, match="not found"):
|
|
await repo.update(99999, {"name": "should_fail"})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_task(self, repo):
|
|
"""Delete existing task."""
|
|
created = await repo.create(
|
|
{
|
|
"name": "Delete Test",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
deleted = await repo.delete(created.id)
|
|
|
|
assert deleted.id == created.id, "Should return deleted task"
|
|
|
|
result = await repo.get(created.id)
|
|
assert result is None, "Deleted task should not be retrievable"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_nonexistent_raises(self, repo):
|
|
"""Delete nonexistent task raises KeyError."""
|
|
with pytest.raises(KeyError, match="not found"):
|
|
await repo.delete(99999)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_name(self, repo):
|
|
"""Get task by name."""
|
|
await repo.create(
|
|
{
|
|
"name": "Named Task",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
retrieved = await repo.get_by_name("Named Task")
|
|
|
|
assert retrieved is not None, "Should retrieve by name"
|
|
assert retrieved.name == "Named Task", "Should match task name"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_by_name_excludes_id(self, repo):
|
|
"""Get by name can exclude specific ID."""
|
|
first = await repo.create(
|
|
{
|
|
"name": "duplicate",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
result = await repo.get_by_name("duplicate", exclude_id=first.id)
|
|
assert result is None, "Should not find when excluding only match"
|
|
|
|
result = await repo.get_by_name("duplicate", exclude_id=None)
|
|
assert result is not None, "Should find without exclusion"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_all_enabled(self, repo):
|
|
"""Get all enabled tasks."""
|
|
await repo.create({"name": "Enabled 1", "url": "https://example.com", "enabled": True})
|
|
await repo.create({"name": "Disabled", "url": "https://example.com", "enabled": False})
|
|
await repo.create({"name": "Enabled 2", "url": "https://example.com", "enabled": True})
|
|
|
|
enabled = await repo.get_all_enabled()
|
|
|
|
assert len(enabled) == 2, "Should return only enabled tasks"
|
|
assert all(task.enabled for task in enabled), "All tasks should be enabled"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_all_with_timer(self, repo):
|
|
"""Get all tasks with timer configured."""
|
|
await repo.create({"name": "With Timer", "url": "https://example.com", "timer": "0 0 * * *"})
|
|
await repo.create({"name": "No Timer", "url": "https://example.com", "timer": ""})
|
|
await repo.create({"name": "Another Timer", "url": "https://example.com", "timer": "0 12 * * *"})
|
|
|
|
with_timer = await repo.get_all_with_timer()
|
|
|
|
assert len(with_timer) == 2, "Should return only tasks with timer"
|
|
assert all(task.timer for task in with_timer), "All tasks should have timer"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_paginated(self, repo):
|
|
"""List paginated returns correct subset."""
|
|
for i in range(5):
|
|
await repo.create(
|
|
{
|
|
"name": f"Task {i}",
|
|
"url": "https://example.com",
|
|
}
|
|
)
|
|
|
|
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 of 5"
|
|
assert page == 1, "Should be on page 1"
|
|
assert total_pages == 3, "Should have 3 pages total"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_list_ordering(self, repo):
|
|
"""List orders by name ascending."""
|
|
await repo.create({"name": "Charlie", "url": "https://example.com"})
|
|
await repo.create({"name": "Alice", "url": "https://example.com"})
|
|
await repo.create({"name": "Bob", "url": "https://example.com"})
|
|
|
|
items = await repo.all()
|
|
|
|
assert items[0].name == "Alice", "Should be alphabetically first"
|
|
assert items[1].name == "Bob", "Should be alphabetically second"
|
|
assert items[2].name == "Charlie", "Should be alphabetically third"
|