"""End-to-end tests for the todo MCP server. Each test runs the real (flattened) tool functions against a temp store via ``TODO_MCP_STORE`` so behavior is verified, not just imported. """ from __future__ import annotations import json import pytest import todo.server as s @pytest.fixture def store(tmp_path, monkeypatch): path = tmp_path / "todos.json" monkeypatch.setenv("TODO_MCP_STORE", str(path)) return path def _ids(items): return sorted(t["id"] for t in items) @pytest.mark.asyncio async def test_add_and_list(store): created = await s.todo_add(title="Buy milk", priority="high", tags="dairy, breakfast") first = json.loads(created) assert first["title"] == "Buy milk" assert first["priority"] == "high" assert first["status"] == "active" assert first["tags"] == ["dairy", "breakfast"] assert first["id"].startswith("t_") listing = json.loads(await s.todo_list()) assert listing["total"] == 1 assert listing["tasks"][0]["id"] == first["id"] @pytest.mark.asyncio async def test_get_update_toggle_delete(store): tid = json.loads(await s.todo_add(title="Task one"))["id"] got = json.loads(await s.todo_get(id=tid)) assert got["id"] == tid updated = json.loads(await s.todo_update(id=tid, status="done", priority="urgent")) assert updated["status"] == "done" assert updated["priority"] == "urgent" toggled = json.loads(await s.todo_toggle(id=tid)) assert toggled["status"] == "active" deleted = json.loads(await s.todo_delete(id=tid)) assert deleted["deleted"] == tid missing = await s.todo_get(id="t_nope") assert missing.startswith("Error") @pytest.mark.asyncio async def test_filters_and_stats(store): await s.todo_add(title="A", priority="high", project="work") await s.todo_add(title="B", priority="low", project="home") await s.todo_add(title="C", priority="high", project="work") only_work = json.loads(await s.todo_list(project="work")) assert only_work["total"] == 2 stats = json.loads(await s.todo_stats()) assert stats["total"] == 3 assert stats["by_priority"]["high"] == 2 assert set(stats["projects"]) == {"work", "home"} @pytest.mark.asyncio async def test_clear_completed(store): t1 = json.loads(await s.todo_add(title="keep")) t2 = json.loads(await s.todo_add(title="done one")) await s.todo_toggle(id=t2["id"]) cleared = json.loads(await s.todo_clear()) assert cleared["cleared"] == 1 assert cleared["remaining"] == 1 remaining = json.loads(await s.todo_list()) assert _ids(remaining["tasks"]) == [t1["id"]] @pytest.mark.asyncio async def test_markdown_format(store): await s.todo_add(title="First", priority="high", due="2026-09-30") md = await s.todo_list(format="markdown") assert "**First**" in md assert "📅 2026-09-30" in md @pytest.mark.asyncio async def test_bad_priority_rejected(store): with pytest.raises(Exception): await s.todo_add(title="x", priority="sideways") @pytest.mark.asyncio async def test_bad_due_rejected(store): with pytest.raises(Exception): await s.todo_add(title="x", due="not-a-date")