206 lines
5.0 KiB
Python
206 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from app.storage import StoredTask
|
|
from app.storage.repository import JsonFileTaskRepository, StorageTaskNotFoundError
|
|
|
|
|
|
def make_task(
|
|
*,
|
|
title: str = "Task",
|
|
started: bool = False,
|
|
done: bool = False,
|
|
) -> StoredTask:
|
|
now = datetime.now(UTC)
|
|
started_at = now + timedelta(seconds=1) if started else None
|
|
done_at = now + timedelta(seconds=2) if done else None
|
|
return StoredTask(
|
|
id=uuid4(),
|
|
title=title,
|
|
created_at=now,
|
|
started_at=started_at,
|
|
done_at=done_at,
|
|
)
|
|
|
|
|
|
def make_repo(tmp_path) -> JsonFileTaskRepository:
|
|
return JsonFileTaskRepository(tmp_path / "tasks.json")
|
|
|
|
|
|
def test_create_task_in_empty_storage(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task = make_task(title="First task")
|
|
|
|
created = repo.create_task(task)
|
|
tasks = repo.list_tasks()
|
|
|
|
assert created == task
|
|
assert len(tasks) == 1
|
|
assert tasks[0] == task
|
|
|
|
|
|
def test_create_task_sequentially_preserves_insertion_order(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task_1 = make_task(title="First")
|
|
task_2 = make_task(title="Second")
|
|
|
|
repo.create_task(task_1)
|
|
repo.create_task(task_2)
|
|
|
|
tasks = repo.list_tasks()
|
|
|
|
assert [task.id for task in tasks] == [task_1.id, task_2.id]
|
|
|
|
|
|
def test_create_task_invalid_model_rejected_before_repository_call(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
|
|
with pytest.raises(ValidationError):
|
|
StoredTask(
|
|
id=uuid4(),
|
|
title="",
|
|
created_at=datetime.now(UTC),
|
|
)
|
|
|
|
assert repo.list_tasks() == []
|
|
|
|
|
|
def test_list_tasks_returns_empty_list_for_fresh_storage(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
|
|
assert repo.list_tasks() == []
|
|
|
|
|
|
def test_list_tasks_returns_persisted_tasks(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task_1 = make_task(title="One")
|
|
task_2 = make_task(title="Two")
|
|
repo.create_task(task_1)
|
|
repo.create_task(task_2)
|
|
|
|
tasks = repo.list_tasks()
|
|
|
|
assert tasks == [task_1, task_2]
|
|
|
|
|
|
def test_get_task_returns_existing_task(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task = make_task(title="Lookup me")
|
|
repo.create_task(task)
|
|
|
|
found = repo.get_task(task.id)
|
|
|
|
assert found == task
|
|
|
|
|
|
def test_get_task_returns_none_for_missing_id(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
repo.create_task(make_task(title="Other task"))
|
|
|
|
found = repo.get_task(uuid4())
|
|
|
|
assert found is None
|
|
|
|
|
|
def test_get_task_with_wrong_runtime_type_returns_none_and_does_not_change_storage(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task = make_task(title="Type safety")
|
|
repo.create_task(task)
|
|
|
|
found = repo.get_task("not-a-uuid") # type: ignore[arg-type]
|
|
|
|
assert found is None
|
|
assert repo.list_tasks() == [task]
|
|
|
|
|
|
def test_update_task_replaces_existing_fields(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
original = make_task(title="Old title")
|
|
repo.create_task(original)
|
|
|
|
updated = StoredTask(
|
|
id=original.id,
|
|
title="New title",
|
|
created_at=original.created_at,
|
|
started_at=original.started_at,
|
|
done_at=original.done_at,
|
|
)
|
|
|
|
result = repo.update_task(updated)
|
|
|
|
assert result == updated
|
|
assert repo.get_task(original.id) == updated
|
|
|
|
|
|
def test_update_task_persists_transition_related_fields(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
original = make_task(title="Workflow")
|
|
repo.create_task(original)
|
|
|
|
now = datetime.now(UTC)
|
|
updated = StoredTask(
|
|
id=original.id,
|
|
title=original.title,
|
|
created_at=original.created_at,
|
|
started_at=now,
|
|
done_at=now + timedelta(minutes=5),
|
|
)
|
|
|
|
repo.update_task(updated)
|
|
reloaded = repo.get_task(original.id)
|
|
|
|
assert reloaded is not None
|
|
assert reloaded.started_at == updated.started_at
|
|
assert reloaded.done_at == updated.done_at
|
|
|
|
|
|
def test_update_task_raises_for_missing_task(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
missing = make_task(title="Missing")
|
|
|
|
with pytest.raises(StorageTaskNotFoundError):
|
|
repo.update_task(missing)
|
|
|
|
assert repo.list_tasks() == []
|
|
|
|
|
|
def test_delete_task_removes_existing_task(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task = make_task(title="Delete me")
|
|
repo.create_task(task)
|
|
|
|
deleted = repo.delete_task(task.id)
|
|
|
|
assert deleted is True
|
|
assert repo.list_tasks() == []
|
|
|
|
|
|
def test_delete_task_only_affects_requested_task(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task_1 = make_task(title="Keep me")
|
|
task_2 = make_task(title="Delete me")
|
|
repo.create_task(task_1)
|
|
repo.create_task(task_2)
|
|
|
|
deleted = repo.delete_task(task_2.id)
|
|
|
|
assert deleted is True
|
|
assert repo.list_tasks() == [task_1]
|
|
|
|
|
|
def test_delete_task_returns_false_for_missing_task(tmp_path):
|
|
repo = make_repo(tmp_path)
|
|
task = make_task(title="Existing")
|
|
repo.create_task(task)
|
|
|
|
deleted = repo.delete_task(uuid4())
|
|
|
|
assert deleted is False
|
|
assert repo.list_tasks() == [task]
|
|
|