fix: resolve test failures blocking CI pipeline (OPC mock, route ordering, system stats)
Run Tests / Secret Detection (pull_request) Failing after 42s
Run Tests / Backend Tests (pull_request) Successful in 32m26s
Run Tests / Frontend Tests (pull_request) Failing after 26s
Deploy Dashboard (Dev) / Backend Tests (push) Successful in 30m35s
Deploy Dashboard (Dev) / Frontend Tests (push) Failing after 16m37s
Deploy Dashboard (Dev) / Deploy to Dev (push) Has been skipped

- 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 <noreply@anthropic.com>
This commit is contained in:
Gan, Jimmy
2026-05-03 22:15:12 +08:00
parent 56a8ff28ad
commit 0a497ca0f1
4 changed files with 127 additions and 94 deletions
@@ -87,95 +87,6 @@ async def list_conversations(
} for r in rows] } 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") @router.get("/search")
async def search_conversations( async def search_conversations(
q: str = Query(..., min_length=2), q: str = Query(..., min_length=2),
@@ -284,3 +195,92 @@ async def trigger_summary():
f.write("1") f.write("1")
return {"status": "triggered"} 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
+18 -3
View File
@@ -277,7 +277,7 @@ def _patch_opc_db():
def _make_task(title, description, status, priority, project_id, def _make_task(title, description, status, priority, project_id,
assigned_to, assigned_type, tags, due_date): assigned_to, assigned_type, tags, due_date):
tid = _next_id(_task_counter) tid = _next_id(_task_counter)
now = datetime.utcnow().isoformat() now = datetime.utcnow()
task = { task = {
"id": tid, "id": tid,
"title": title, "title": title,
@@ -292,7 +292,7 @@ def _patch_opc_db():
"created_at": now, "created_at": now,
"updated_at": now, "updated_at": now,
"completed_at": None, "completed_at": None,
"due_date": due_date.isoformat() if due_date else None, "due_date": due_date,
} }
_tasks[tid] = task _tasks[tid] = task
return task return task
@@ -316,7 +316,7 @@ def _patch_opc_db():
return _DictRow(task) return _DictRow(task)
elif query_lower.startswith("insert into agent_executions"): elif query_lower.startswith("insert into agent_executions"):
eid = _next_id(_exec_counter) eid = _next_id(_exec_counter)
now = datetime.utcnow().isoformat() now = datetime.utcnow()
exec_data = { exec_data = {
"id": eid, "id": eid,
"task_id": params[0] if len(params) > 0 else None, "task_id": params[0] if len(params) > 0 else None,
@@ -343,6 +343,21 @@ def _patch_opc_db():
if tid in _tasks: if tid in _tasks:
return _DictRow(_tasks[tid]) return _DictRow(_tasks[tid])
return None 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 ="): elif query_lower.startswith("select * from agent_executions where id ="):
eid = params[0] if params else None eid = params[0] if params else None
if eid in _executions: if eid in _executions:
@@ -115,7 +115,7 @@ def test_task_creator_tracked_in_metadata(test_app, admin_headers):
# Check metadata contains created_by # Check metadata contains created_by
assert "metadata" in task 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): def test_cannot_view_others_task_details(test_app, admin_headers):
@@ -1,10 +1,28 @@
"""Integration tests for system router""" """Integration tests for system router"""
import pytest import pytest
from unittest.mock import Mock, patch
def test_system_stats_endpoint(test_app, admin_headers): def test_system_stats_endpoint(test_app, admin_headers):
"""Test system stats endpoint returns expected format""" """Test system stats endpoint returns expected format"""
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) response = test_app.get("/api/system/stats", headers=admin_headers)
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()