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,96 @@
from __future__ import annotations
from datetime import UTC, datetime, timedelta
from uuid import uuid4
import pytest
from app.domain import InvalidTransitionError, complete_task, start_task
from app.storage import StoredTask
def make_task(
*,
title: str = "Task",
started_at: datetime | None = None,
done_at: datetime | None = None,
) -> StoredTask:
return StoredTask(
id=uuid4(),
title=title,
created_at=datetime.now(UTC),
started_at=started_at,
done_at=done_at,
)
def test_start_task_from_backlog_sets_started_at():
task = make_task(started_at=None, done_at=None)
before_call = datetime.now(UTC)
result = start_task(task)
after_call = datetime.now(UTC)
assert result.id == task.id
assert result.title == task.title
assert result.created_at == task.created_at
assert result.done_at is None
assert result.started_at is not None
assert before_call <= result.started_at <= after_call
def test_start_task_is_idempotent_for_already_started_task():
started_at = datetime.now(UTC) - timedelta(minutes=5)
task = make_task(started_at=started_at, done_at=None)
result = start_task(task)
assert result == task
assert result.started_at == started_at
def test_start_task_raises_for_completed_task():
started_at = datetime.now(UTC) - timedelta(minutes=10)
done_at = datetime.now(UTC) - timedelta(minutes=1)
task = make_task(started_at=started_at, done_at=done_at)
with pytest.raises(InvalidTransitionError, match="invalid_transaction"):
start_task(task)
def test_complete_task_from_in_progress_sets_done_at():
started_at = datetime.now(UTC) - timedelta(minutes=10)
task = make_task(started_at=started_at, done_at=None)
before_call = datetime.now(UTC)
result = complete_task(task)
after_call = datetime.now(UTC)
assert result.id == task.id
assert result.title == task.title
assert result.created_at == task.created_at
assert result.started_at == started_at
assert result.done_at is not None
assert result.started_at <= result.done_at
assert before_call <= result.done_at <= after_call
def test_complete_task_is_idempotent_for_already_completed_task():
started_at = datetime.now(UTC) - timedelta(minutes=10)
done_at = datetime.now(UTC) - timedelta(minutes=1)
task = make_task(started_at=started_at, done_at=done_at)
result = complete_task(task)
assert result == task
assert result.done_at == done_at
def test_complete_task_raises_for_backlog_task():
task = make_task(started_at=None, done_at=None)
with pytest.raises(InvalidTransitionError, match="invalid_transaction"):
complete_task(task)