feat(taskflow): add core task API, storage persistence, csv export, stats page, and test coverage

This commit is contained in:
Alexander Kalinovsky
2026-04-01 17:56:03 +03:00
commit 19d659df6b
31 changed files with 4197 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from uuid import uuid4
from app.storage import DATA_FORMAT_VERSION, StoredTask
from app.storage.repository import JsonFileTaskRepository
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 test_replace_all_replaces_empty_state_with_provided_tasks(tmp_path):
repo = JsonFileTaskRepository(tmp_path / "tasks.json")
task_1 = make_task(title="One")
task_2 = make_task(title="Two")
repo.replace_all([task_1, task_2])
assert repo.list_tasks() == [task_1, task_2]
def test_replace_all_fully_overwrites_previous_content(tmp_path):
repo = JsonFileTaskRepository(tmp_path / "tasks.json")
old_task = make_task(title="Old")
new_task = make_task(title="New")
repo.create_task(old_task)
repo.replace_all([new_task])
assert repo.list_tasks() == [new_task]
def test_data_format_version_matches_constant(tmp_path):
repo = JsonFileTaskRepository(tmp_path / "tasks.json")
assert repo.data_format_version == DATA_FORMAT_VERSION
def test_data_format_version_is_stable_across_instances(tmp_path):
file_path = tmp_path / "tasks.json"
repo_1 = JsonFileTaskRepository(file_path)
repo_2 = JsonFileTaskRepository(file_path)
assert repo_1.data_format_version == repo_2.data_format_version == DATA_FORMAT_VERSION
def test_missing_file_is_created_automatically(tmp_path):
file_path = tmp_path / "nested" / "tasks.json"
repo = JsonFileTaskRepository(file_path)
assert repo.list_tasks() == []
assert file_path.exists()
def test_existing_valid_file_is_loaded_on_new_instance(tmp_path):
file_path = tmp_path / "tasks.json"
repo_1 = JsonFileTaskRepository(file_path)
task = make_task(title="Persisted")
repo_1.create_task(task)
repo_2 = JsonFileTaskRepository(file_path)
assert repo_2.list_tasks() == [task]
def test_parent_directory_is_created_if_missing(tmp_path):
file_path = tmp_path / "deep" / "nested" / "data" / "tasks.json"
repo = JsonFileTaskRepository(file_path)
assert file_path.parent.exists()
assert repo.list_tasks() == []
def test_current_version_payload_loads_as_is(tmp_path):
file_path = tmp_path / "tasks.json"
repo_1 = JsonFileTaskRepository(file_path)
task = make_task(title="Versioned")
repo_1.create_task(task)
repo_2 = JsonFileTaskRepository(file_path)
assert repo_2.data_format_version == DATA_FORMAT_VERSION
assert repo_2.list_tasks() == [task]
def test_empty_current_version_payload_loads_without_backup(tmp_path):
file_path = tmp_path / "tasks.json"
repo = JsonFileTaskRepository(file_path)
assert repo.list_tasks() == []
assert not list(tmp_path.glob("tasks.json.corrupted*"))
def test_persistence_between_repository_instances(tmp_path):
file_path = tmp_path / "tasks.json"
repo_1 = JsonFileTaskRepository(file_path)
task = make_task(title="Cross-session")
repo_1.create_task(task)
repo_2 = JsonFileTaskRepository(file_path)
assert repo_2.get_task(task.id) == task
def test_multiple_operations_survive_restart(tmp_path):
file_path = tmp_path / "tasks.json"
repo_1 = JsonFileTaskRepository(file_path)
task_1 = make_task(title="First")
task_2 = make_task(title="Second")
repo_1.create_task(task_1)
repo_1.create_task(task_2)
repo_1.delete_task(task_1.id)
updated_task_2 = StoredTask(
id=task_2.id,
title="Second updated",
created_at=task_2.created_at,
started_at=datetime.now(UTC),
done_at=None,
)
repo_1.update_task(updated_task_2)
repo_2 = JsonFileTaskRepository(file_path)
assert repo_2.list_tasks() == [updated_task_2]