Files
kadence/backend/tests/test_api.py
T
timka123 d280101bce
docker / image (push) Failing after 24s
lint / ruff (push) Failing after 28s
tests / backend (push) Successful in 37s
tests / desktop (push) Successful in 22s
tests / android-core (push) Successful in 49s
tests / web (push) Successful in 1m20s
android / apk (push) Successful in 8m49s
disallow to disable guard during focus
2026-10-28 09:50:58 +03:00

161 lines
6.6 KiB
Python

"""The HTTP surface, driven the way the clients drive it."""
from __future__ import annotations
def test_health_and_info(client):
assert client.get("/api/health").json()["status"] == "ok"
assert "auth_required" in client.get("/api/info").json()
def test_a_whole_pomodoro_through_the_api(client):
client.put("/api/settings", json={"pomodoro": {"daily_goal_pomodoros": 1}})
task = client.post("/api/tasks", json={"title": "Write the API"}).json()
state = client.post("/api/timer/start", json={"kind": "work", "task_id": task["id"]}).json()
assert state["current"]["kind"] == "work"
assert state["current"]["task"]["title"] == "Write the API"
assert client.post("/api/timer/pause").json()["current"]["status"] == "paused"
assert client.post("/api/timer/resume").json()["current"]["status"] == "running"
assert client.post("/api/timer/extend", json={"minutes": 5}).json()["current"]["planned_seconds"] > 0
done = client.post("/api/timer/complete", json={"auto_next": False}).json()
assert done["finished"]["status"] == "completed"
assert done["completed_today"] == 1
report = client.get("/api/reports/daily").json()
assert report["report"]["metrics"]["pomodoros"] == 1
assert "Daily report" in report["telegram_preview"]
def test_a_stale_complete_leaves_the_auto_started_break_alone(client):
client.put(
"/api/settings",
json={"pomodoro": {"auto_start_breaks": True, "auto_start_work": True}},
)
state = client.post("/api/timer/start", json={"kind": "work"}).json()
work_id = state["current"]["id"]
done = client.post(
"/api/timer/complete", json={"auto_next": True, "session_id": work_id}
).json()
assert done["current"]["kind"] in ("short_break", "long_break")
break_id = done["current"]["id"]
# The other client's countdown, one beat behind.
again = client.post(
"/api/timer/complete", json={"auto_next": True, "session_id": work_id}
).json()
assert again["finished"] is None
assert again["current"]["id"] == break_id
def test_timer_errors_are_400_not_500(client):
assert client.post("/api/timer/pause").status_code == 400
assert client.post("/api/timer/complete", json={}).status_code == 400
def test_tasks_cannot_be_ticked_off_here(client):
"""Completion belongs to Jira / Todoist / Google — we only notice it.
405 rather than 404 because the SPA catch-all claims unknown paths for GET;
either way there is no way to close a task through the API.
"""
task = client.post("/api/tasks", json={"title": "Not yours to close"}).json()
response = client.post(f"/api/tasks/{task['id']}/done", json={"done": True})
assert response.status_code in (404, 405)
assert client.get("/api/tasks").json()["tasks"][0]["is_done"] is False
def test_task_crud(client):
created = client.post("/api/tasks", json={"title": "Temporary", "priority": "p2"}).json()
listing = client.get("/api/tasks").json()
assert any(task["id"] == created["id"] for task in listing["tasks"])
patched = client.patch(f"/api/tasks/{created['id']}", json={"pinned": True}).json()
assert patched["pinned"] is True
client.delete(f"/api/tasks/{created['id']}")
assert all(task["id"] != created["id"] for task in client.get("/api/tasks").json()["tasks"])
def test_refresh_reports_per_source_failures_without_failing(client):
client.put(
"/api/settings",
json={"sources": {"todoist": {"enabled": True, "api_token": "definitely-invalid"}}},
)
result = client.post("/api/tasks/refresh").json()
assert result["sources"], "the enabled source should be reported"
assert result["sources"][0]["ok"] is False
assert client.get("/api/tasks").status_code == 200
def test_blocker_policy_and_events(client):
client.put(
"/api/settings",
json={"blocker": {"enabled": True, "blocked_domains": ["youtube.com"]}},
)
assert client.get("/api/blocker/policy").json()["active"] is False
client.post("/api/timer/start", json={"kind": "work"})
policy = client.get("/api/blocker/policy").json()
assert policy["active"] is True
assert "youtube.com" in policy["domains"]
client.post("/api/blocker/events", json={"kind": "domain", "target": "youtube.com"})
events = client.get("/api/blocker/events").json()
assert events["total"] == 1
def test_settings_round_trip_masks_secrets(client):
client.put("/api/settings", json={"sources": {"todoist": {"api_token": "s3cret"}}})
body = client.get("/api/settings").json()
assert body["settings"]["sources"]["todoist"]["api_token"] == "••••••••"
assert body["defaults"]["blocked_domains"]
# Posting the mask back must not wipe the token.
client.put("/api/settings", json=body["settings"])
again = client.get("/api/settings").json()
assert again["settings"]["sources"]["todoist"]["api_token"] == "••••••••"
def test_invalid_settings_are_rejected(client):
assert client.put("/api/settings", json={"pomodoro": {"work_minutes": 0}}).status_code == 422
assert client.put("/api/settings", json={"timezone": "Mars/Olympus"}).status_code == 422
def test_switching_the_guard_off_mid_interval_is_a_409(client):
assert client.put("/api/settings", json={"blocker": {"enabled": True}}).status_code == 200
client.post("/api/timer/start", json={"kind": "work"})
assert client.get("/api/blocker/policy").json()["locked"] is True
refused = client.put("/api/settings", json={"blocker": {"enabled": False}})
assert refused.status_code == 409
assert "focus guard" in refused.json()["detail"]
# Turning it on, and editing anything else, are both still fine.
assert client.put("/api/settings", json={"blocker": {"enabled": True}}).status_code == 200
lists = client.put("/api/settings", json={"blocker": {"blocked_apps": ["steam"]}})
assert lists.status_code == 200
assert lists.json()["settings"]["blocker"]["enabled"] is True
client.post("/api/timer/complete", json={"auto_next": False})
assert client.put("/api/settings", json={"blocker": {"enabled": False}}).status_code == 200
def test_resetting_the_settings_cannot_unguard_a_live_interval(client):
client.put("/api/settings", json={"blocker": {"enabled": True}})
client.post("/api/timer/start", json={"kind": "short_break"})
assert client.post("/api/settings/reset").status_code == 409
client.post("/api/timer/complete", json={"auto_next": False})
assert client.post("/api/settings/reset").status_code == 200
def test_notification_test_endpoint_falls_back_to_the_log(client):
body = client.post("/api/notifications/test", json={}).json()
assert body["deliveries"][0]["channel"] == "log"