122 lines
4.0 KiB
Python
122 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from uuid import uuid4
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.tasks import get_task_repository
|
|
from app.main import app
|
|
from app.storage import JsonFileTaskRepository, StoredTask
|
|
|
|
|
|
def make_repo(tmp_path) -> JsonFileTaskRepository:
|
|
return JsonFileTaskRepository(tmp_path / "tasks.json")
|
|
|
|
|
|
def make_task(
|
|
*,
|
|
title: str = "Task",
|
|
started: bool = False,
|
|
done: bool = False,
|
|
) -> StoredTask:
|
|
now = datetime.now(UTC)
|
|
started_at = now - timedelta(minutes=10) if started else None
|
|
done_at = now - timedelta(minutes=1) if done else None
|
|
return StoredTask(
|
|
id=uuid4(),
|
|
title=title,
|
|
created_at=now - timedelta(hours=1),
|
|
started_at=started_at,
|
|
done_at=done_at,
|
|
)
|
|
|
|
|
|
def make_client(tmp_path) -> tuple[TestClient, JsonFileTaskRepository]:
|
|
repo = make_repo(tmp_path)
|
|
app.dependency_overrides[get_task_repository] = lambda: repo
|
|
client = TestClient(app)
|
|
return client, repo
|
|
|
|
|
|
def teardown_function() -> None:
|
|
app.dependency_overrides.clear()
|
|
|
|
|
|
def test_ui_data_board_returns_lightweight_grouped_lists(tmp_path):
|
|
client, repo = make_client(tmp_path)
|
|
backlog = make_task(title="Backlog title")
|
|
in_progress = make_task(title="In progress title", started=True)
|
|
done = make_task(title="Done title", started=True, done=True)
|
|
repo.create_task(backlog)
|
|
repo.create_task(in_progress)
|
|
repo.create_task(done)
|
|
|
|
response = client.get("/ui_data/stats/board")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert [item["title"] for item in data["backlog_tasks"]] == ["Backlog title"]
|
|
assert [item["title"] for item in data["in_progress_tasks"]] == ["In progress title"]
|
|
assert [item["title"] for item in data["done_tasks"]] == ["Done title"]
|
|
assert data["backlog_tasks"][0]["display_date_label"] == "Created"
|
|
assert data["in_progress_tasks"][0]["display_date_label"] == "Started"
|
|
assert data["done_tasks"][0]["display_date_label"] == "Done"
|
|
assert "created_at" not in data["backlog_tasks"][0]
|
|
assert "started_at" not in data["in_progress_tasks"][0]
|
|
assert "done_at" not in data["done_tasks"][0]
|
|
assert "cycle_time" not in data["done_tasks"][0]
|
|
|
|
|
|
def test_ui_data_task_details_returns_full_selected_task_data(tmp_path):
|
|
client, repo = make_client(tmp_path)
|
|
task = make_task(title="Detailed task", started=True, done=True)
|
|
repo.create_task(task)
|
|
|
|
response = client.get(f"/ui_data/tasks/{task.id}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["id"] == str(task.id)
|
|
assert data["title"] == "Detailed task"
|
|
assert data["status"] == "done"
|
|
assert datetime.fromisoformat(data["created_at"].replace("Z", "+00:00")) == task.created_at
|
|
assert datetime.fromisoformat(data["started_at"].replace("Z", "+00:00")) == task.started_at
|
|
assert datetime.fromisoformat(data["done_at"].replace("Z", "+00:00")) == task.done_at
|
|
assert data["cycle_time"].startswith("0h 9m")
|
|
|
|
|
|
def test_ui_data_task_details_rejects_invalid_uuid(tmp_path):
|
|
client, _repo = make_client(tmp_path)
|
|
|
|
response = client.get("/ui_data/tasks/bad-id")
|
|
|
|
assert response.status_code == 400
|
|
assert response.json() == {
|
|
"error": "invalid_id",
|
|
"message": "Task id must be a valid UUID",
|
|
}
|
|
|
|
|
|
def test_ui_data_task_details_returns_not_found_for_missing_task(tmp_path):
|
|
client, _repo = make_client(tmp_path)
|
|
|
|
response = client.get(f"/ui_data/tasks/{uuid4()}")
|
|
|
|
assert response.status_code == 404
|
|
assert response.json() == {
|
|
"error": "invalid_id",
|
|
"message": "Task was not found",
|
|
}
|
|
|
|
|
|
def test_ui_data_routes_are_hidden_from_openapi_schema(tmp_path):
|
|
client, _repo = make_client(tmp_path)
|
|
|
|
response = client.get("/openapi.json")
|
|
|
|
assert response.status_code == 200
|
|
paths = response.json()["paths"]
|
|
assert "/ui_data/stats/board" not in paths
|
|
assert "/ui_data/tasks/{task_id}" not in paths
|