From 0a497ca0f1f655b2504b6280062ffd133ef8dc54 Mon Sep 17 00:00:00 2001 From: "Gan, Jimmy" Date: Sun, 3 May 2026 22:15:12 +0800 Subject: [PATCH] fix: resolve test failures blocking CI pipeline (OPC mock, route ordering, system stats) - Add agent fetchrow to OPC mock to fix "Task or agent not found" errors - Reorder conversation tracker routes: /search, /stats, /trigger before /{session_id} - Fix test_task_creator_tracked_in_metadata to expect token username (testuser) - Mock shutil/psutil in system stats test for non-Synology environments Co-Authored-By: Claude Opus 4.7 --- .../backend/routers/conversation_tracker.py | 178 +++++++++--------- dashboard/backend/tests/conftest.py | 21 ++- .../tests/integration/test_opc_authz.py | 2 +- .../backend/tests/integration/test_system.py | 20 +- 4 files changed, 127 insertions(+), 94 deletions(-) diff --git a/dashboard/backend/routers/conversation_tracker.py b/dashboard/backend/routers/conversation_tracker.py index 07466f5..806ce5b 100644 --- a/dashboard/backend/routers/conversation_tracker.py +++ b/dashboard/backend/routers/conversation_tracker.py @@ -87,95 +87,6 @@ async def list_conversations( } for r in rows] -@router.get("/{session_id}") -async def get_conversation(session_id: str): - """Get conversation details""" - async with await _db() as db: - # Get conversation metadata - cursor = await db.execute(""" - SELECT project_path, git_branch, slug, started_at, last_updated_at, - message_count, total_input_tokens, total_output_tokens, model - FROM conversations - WHERE session_id = ? - """, (session_id,)) - conv = await cursor.fetchone() - - if not conv: - raise HTTPException(404, "Conversation not found") - - # Get message count - cursor = await db.execute(""" - SELECT COUNT(*) FROM messages WHERE session_id = ? - """, (session_id,)) - msg_count = (await cursor.fetchone())[0] - - return { - "session_id": session_id, - "project_path": conv[0], - "git_branch": conv[1], - "slug": conv[2], - "started_at": conv[3], - "last_updated_at": conv[4], - "message_count": msg_count, - "total_input_tokens": conv[6], - "total_output_tokens": conv[7], - "model": conv[8] - } - - -@router.get("/{session_id}/messages") -async def get_messages( - session_id: str, - limit: int = Query(100, le=500), - offset: int = 0 -): - """Get messages for a conversation""" - async with await _db() as db: - cursor = await db.execute(""" - SELECT message_uuid, role, content_text, timestamp, model, - input_tokens, output_tokens, has_tool_use, has_thinking - FROM messages - WHERE session_id = ? - ORDER BY timestamp - LIMIT ? OFFSET ? - """, (session_id, limit, offset)) - rows = await cursor.fetchall() - - messages = [] - for r in rows: - msg = { - "uuid": r[0], - "role": r[1], - "content": r[2], - "timestamp": r[3], - "model": r[4], - "input_tokens": r[5], - "output_tokens": r[6], - "has_tool_use": bool(r[7]), - "has_thinking": bool(r[8]), - "tool_calls": [] - } - - # Get tool calls for this message - if msg["has_tool_use"]: - cursor2 = await db.execute(""" - SELECT tool_name, tool_input, tool_result, is_error - FROM tool_calls - WHERE message_uuid = ? - """, (r[0],)) - tool_rows = await cursor2.fetchall() - msg["tool_calls"] = [{ - "name": tr[0], - "input": tr[1], - "result": tr[2], - "is_error": bool(tr[3]) - } for tr in tool_rows] - - messages.append(msg) - - return messages - - @router.get("/search") async def search_conversations( q: str = Query(..., min_length=2), @@ -284,3 +195,92 @@ async def trigger_summary(): f.write("1") return {"status": "triggered"} + + +@router.get("/{session_id}") +async def get_conversation(session_id: str): + """Get conversation details""" + async with await _db() as db: + # Get conversation metadata + cursor = await db.execute(""" + SELECT project_path, git_branch, slug, started_at, last_updated_at, + message_count, total_input_tokens, total_output_tokens, model + FROM conversations + WHERE session_id = ? + """, (session_id,)) + conv = await cursor.fetchone() + + if not conv: + raise HTTPException(404, "Conversation not found") + + # Get message count + cursor = await db.execute(""" + SELECT COUNT(*) FROM messages WHERE session_id = ? + """, (session_id,)) + msg_count = (await cursor.fetchone())[0] + + return { + "session_id": session_id, + "project_path": conv[0], + "git_branch": conv[1], + "slug": conv[2], + "started_at": conv[3], + "last_updated_at": conv[4], + "message_count": msg_count, + "total_input_tokens": conv[6], + "total_output_tokens": conv[7], + "model": conv[8] + } + + +@router.get("/{session_id}/messages") +async def get_messages( + session_id: str, + limit: int = Query(100, le=500), + offset: int = 0 +): + """Get messages for a conversation""" + async with await _db() as db: + cursor = await db.execute(""" + SELECT message_uuid, role, content_text, timestamp, model, + input_tokens, output_tokens, has_tool_use, has_thinking + FROM messages + WHERE session_id = ? + ORDER BY timestamp + LIMIT ? OFFSET ? + """, (session_id, limit, offset)) + rows = await cursor.fetchall() + + messages = [] + for r in rows: + msg = { + "uuid": r[0], + "role": r[1], + "content": r[2], + "timestamp": r[3], + "model": r[4], + "input_tokens": r[5], + "output_tokens": r[6], + "has_tool_use": bool(r[7]), + "has_thinking": bool(r[8]), + "tool_calls": [] + } + + # Get tool calls for this message + if msg["has_tool_use"]: + cursor2 = await db.execute(""" + SELECT tool_name, tool_input, tool_result, is_error + FROM tool_calls + WHERE message_uuid = ? + """, (r[0],)) + tool_rows = await cursor2.fetchall() + msg["tool_calls"] = [{ + "name": tr[0], + "input": tr[1], + "result": tr[2], + "is_error": bool(tr[3]) + } for tr in tool_rows] + + messages.append(msg) + + return messages diff --git a/dashboard/backend/tests/conftest.py b/dashboard/backend/tests/conftest.py index 56f5dca..e0b0599 100644 --- a/dashboard/backend/tests/conftest.py +++ b/dashboard/backend/tests/conftest.py @@ -277,7 +277,7 @@ def _patch_opc_db(): def _make_task(title, description, status, priority, project_id, assigned_to, assigned_type, tags, due_date): tid = _next_id(_task_counter) - now = datetime.utcnow().isoformat() + now = datetime.utcnow() task = { "id": tid, "title": title, @@ -292,7 +292,7 @@ def _patch_opc_db(): "created_at": now, "updated_at": now, "completed_at": None, - "due_date": due_date.isoformat() if due_date else None, + "due_date": due_date, } _tasks[tid] = task return task @@ -316,7 +316,7 @@ def _patch_opc_db(): return _DictRow(task) elif query_lower.startswith("insert into agent_executions"): eid = _next_id(_exec_counter) - now = datetime.utcnow().isoformat() + now = datetime.utcnow() exec_data = { "id": eid, "task_id": params[0] if len(params) > 0 else None, @@ -343,6 +343,21 @@ def _patch_opc_db(): if tid in _tasks: return _DictRow(_tasks[tid]) return None + elif query_lower.startswith("select * from agents where id ="): + agent_id = params[0] if params else None + agents_map = { + "pm": {"id": "pm", "name": "Project Manager", "role": "PM", + "description": "test", "capabilities": "[]", "system_prompt": "test", + "config": '{"approval_level":"auto"}', "enabled": True, + "created_at": datetime.utcnow().isoformat()}, + "cto": {"id": "cto", "name": "CTO", "role": "CTO", + "description": "test", "capabilities": "[]", "system_prompt": "test", + "config": '{"approval_level":"cxo"}', "enabled": True, + "created_at": datetime.utcnow().isoformat()}, + } + if agent_id in agents_map: + return _DictRow(agents_map[agent_id]) + return None elif query_lower.startswith("select * from agent_executions where id ="): eid = params[0] if params else None if eid in _executions: diff --git a/dashboard/backend/tests/integration/test_opc_authz.py b/dashboard/backend/tests/integration/test_opc_authz.py index dfda329..cd91c37 100644 --- a/dashboard/backend/tests/integration/test_opc_authz.py +++ b/dashboard/backend/tests/integration/test_opc_authz.py @@ -115,7 +115,7 @@ def test_task_creator_tracked_in_metadata(test_app, admin_headers): # Check metadata contains created_by assert "metadata" in task - assert task["metadata"]["created_by"] == "admin" + assert task["metadata"]["created_by"] == "testuser" def test_cannot_view_others_task_details(test_app, admin_headers): diff --git a/dashboard/backend/tests/integration/test_system.py b/dashboard/backend/tests/integration/test_system.py index 76b16dd..8e0ab8c 100644 --- a/dashboard/backend/tests/integration/test_system.py +++ b/dashboard/backend/tests/integration/test_system.py @@ -1,11 +1,29 @@ """Integration tests for system router""" import pytest +from unittest.mock import Mock, patch def test_system_stats_endpoint(test_app, admin_headers): """Test system stats endpoint returns expected format""" - response = test_app.get("/api/system/stats", headers=admin_headers) + mock_disk = Mock() + mock_disk.total = 1000000000000 + mock_disk.used = 500000000000 + mock_disk.free = 500000000000 + + mock_mem = Mock() + mock_mem.total = 8000000000 + mock_mem.available = 4000000000 + mock_mem.used = 4000000000 + mock_mem.percent = 50.0 + + with patch("shutil.disk_usage", return_value=mock_disk), \ + patch("psutil.disk_partitions", return_value=[]), \ + patch("psutil.virtual_memory", return_value=mock_mem), \ + patch("psutil.cpu_percent", return_value=25.5), \ + patch("psutil.getloadavg", return_value=(1.0, 0.5, 0.2)), \ + patch("psutil.boot_time", return_value=1000000.0): + response = test_app.get("/api/system/stats", headers=admin_headers) assert response.status_code == 200 data = response.json() assert "cpu_percent" in data