Skip to content

Commit 1a2749c

Browse files
committed
test(task): cover mcp task APIs, lock release, and event listing
1 parent 08de2de commit 1a2749c

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

tests/test_server.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,3 +457,74 @@ async def test_list_kbs_empty(self, tmp_db):
457457
app = await create_app(db_path=tmp_db, anthropic_client=mock_client, embeddings=_mock_embedding_engine())
458458
result = await app.list_knowledge_bases()
459459
assert "No knowledge bases" in result
460+
461+
462+
class TestTaskStateMachineTools:
463+
@pytest.mark.asyncio
464+
async def test_create_and_get_task(self, tmp_db):
465+
mock_client = MagicMock()
466+
app = await create_app(db_path=tmp_db, anthropic_client=mock_client, embeddings=_mock_embedding_engine())
467+
468+
created = json.loads(await app.create_task(title="index docs", owner="agent-1"))
469+
assert created["title"] == "index docs"
470+
assert created["owner"] == "agent-1"
471+
assert created["status"] == "todo"
472+
473+
fetched = json.loads(await app.get_task(created["task_id"]))
474+
assert fetched["task_id"] == created["task_id"]
475+
assert fetched["version"] == 0
476+
477+
@pytest.mark.asyncio
478+
async def test_assign_and_release_task_locks(self, tmp_db):
479+
mock_client = MagicMock()
480+
app = await create_app(db_path=tmp_db, anthropic_client=mock_client, embeddings=_mock_embedding_engine())
481+
482+
task_a = json.loads(await app.create_task(title="task-a"))
483+
task_b = json.loads(await app.create_task(title="task-b"))
484+
485+
assigned = json.loads(await app.assign_task_locks(task_a["task_id"], ["src/a.py"]))
486+
assert assigned["count"] == 1
487+
488+
released = json.loads(await app.release_task_locks(task_a["task_id"]))
489+
assert released["released"] == 1
490+
491+
assigned_b = json.loads(await app.assign_task_locks(task_b["task_id"], ["src/a.py"]))
492+
assert assigned_b["count"] == 1
493+
494+
@pytest.mark.asyncio
495+
async def test_transition_task(self, tmp_db):
496+
mock_client = MagicMock()
497+
app = await create_app(db_path=tmp_db, anthropic_client=mock_client, embeddings=_mock_embedding_engine())
498+
499+
created = json.loads(await app.create_task(title="transition"))
500+
transitioned = json.loads(
501+
await app.transition_task(
502+
task_id=created["task_id"],
503+
from_state="todo",
504+
to_state="in_progress",
505+
actor="orchestrator",
506+
expected_version=0,
507+
)
508+
)
509+
assert transitioned["task"]["status"] == "in_progress"
510+
assert transitioned["task"]["version"] == 1
511+
512+
@pytest.mark.asyncio
513+
async def test_append_and_list_task_events(self, tmp_db):
514+
mock_client = MagicMock()
515+
app = await create_app(db_path=tmp_db, anthropic_client=mock_client, embeddings=_mock_embedding_engine())
516+
517+
created = json.loads(await app.create_task(title="events"))
518+
event = json.loads(
519+
await app.append_task_event(
520+
task_id=created["task_id"],
521+
event_type="worklog",
522+
actor="agent-1",
523+
payload_json=json.dumps({"action": "start"}),
524+
)
525+
)
526+
assert event["event_type"] == "worklog"
527+
528+
events = json.loads(await app.list_task_events(task_id=created["task_id"], limit=10))
529+
assert len(events) >= 1
530+
assert events[0]["task_id"] == created["task_id"]

tests/test_task_store.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,34 @@ async def test_lock_conflict_enforced_by_db(self, tmp_db):
4141
with pytest.raises(LockConflict):
4242
await task_store.assign_locks(t2.task_id, ["src/a.py"])
4343

44+
async def test_lock_prefix_conflict_enforced_by_db(self, tmp_db):
45+
store = InsightStore(tmp_db)
46+
await store.initialize()
47+
task_store = TaskStore(tmp_db)
48+
49+
t1 = await task_store.create_task("task 1")
50+
t2 = await task_store.create_task("task 2")
51+
52+
await task_store.assign_locks(t1.task_id, ["src"])
53+
54+
with pytest.raises(LockConflict):
55+
await task_store.assign_locks(t2.task_id, ["src/a.py"])
56+
57+
async def test_release_locks_allows_reacquire(self, tmp_db):
58+
store = InsightStore(tmp_db)
59+
await store.initialize()
60+
task_store = TaskStore(tmp_db)
61+
62+
t1 = await task_store.create_task("task 1")
63+
t2 = await task_store.create_task("task 2")
64+
65+
await task_store.assign_locks(t1.task_id, ["src/a.py"])
66+
released = await task_store.release_locks(t1.task_id)
67+
assert released == 1
68+
69+
lock_ids = await task_store.assign_locks(t2.task_id, ["src/a.py"])
70+
assert len(lock_ids) == 1
71+
4472
async def test_invalid_transition_rejected(self, tmp_db):
4573
store = InsightStore(tmp_db)
4674
await store.initialize()
@@ -146,6 +174,18 @@ async def test_task_events_append_only(self, tmp_db):
146174
await db.execute("UPDATE task_events SET event_type = 'changed' WHERE id = ?", (event.id,))
147175
await db.commit()
148176

177+
async def test_list_events_returns_latest_first(self, tmp_db):
178+
store = InsightStore(tmp_db)
179+
await store.initialize()
180+
task_store = TaskStore(tmp_db)
181+
182+
task = await task_store.create_task("events")
183+
await task_store.append_event(task.task_id, "first", "operator", {"i": 1})
184+
await task_store.append_event(task.task_id, "second", "operator", {"i": 2})
185+
186+
events = await task_store.list_events(task.task_id, limit=2)
187+
assert [event.event_type for event in events] == ["second", "first"]
188+
149189
async def test_migration_created_task_tables(self, tmp_db):
150190
store = InsightStore(tmp_db)
151191
await store.initialize()
@@ -159,3 +199,6 @@ async def test_migration_created_task_tables(self, tmp_db):
159199
"SELECT name FROM sqlite_master WHERE type='index' AND name='ux_task_locks_active_resource'"
160200
)
161201
assert await cursor.fetchone() is not None
202+
cursor = await db.execute("SELECT MAX(version) FROM schema_versions")
203+
row = await cursor.fetchone()
204+
assert row is not None and row[0] >= 7

0 commit comments

Comments
 (0)