From 6903aadd6bad9348432cc48ce608a13936419758 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Sun, 17 May 2026 00:47:39 +0800 Subject: [PATCH] auto-sync: 2026-05-17 00:47:39 --- src/main.py | 9 ++- tests/test_api.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 tests/test_api.py diff --git a/src/main.py b/src/main.py index 4753a01..be74d52 100644 --- a/src/main.py +++ b/src/main.py @@ -129,9 +129,12 @@ async def daemon_status(): @app.get("/api/projects") -async def list_projects(): - """列出所有项目(占位,F3 实现)""" - return {"projects": []} +async def list_projects_compat(): + """兼容旧端点""" + from src.api.project_routes import _registry + reg = _registry() + return {"projects": {pid: info for pid, info in reg.list_projects().items() + if info.get("status") != "archived"}} # --------------------------------------------------------------------------- diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..20a0f64 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,198 @@ +"""F5 测试:API 层""" + +import json +import os +from pathlib import Path + +import pytest +from fastapi.testclient import TestClient + +from src.blackboard.operations import Blackboard +from src.blackboard.models import Task +from src.blackboard.registry import ProjectRegistry +from src.main import app + + +@pytest.fixture +def project_env(tmp_path): + """创建临时项目环境""" + project_root = tmp_path / "projects" + project_root.mkdir() + os.environ["BLACKBOARD_ROOT"] = str(project_root) + + # Create a test project with registry + DB + reg = ProjectRegistry(project_root) + reg.create_project("test-proj", "Test Project", agents=["agent1"]) + + bb = Blackboard(project_root / "test-proj" / "blackboard.db") + bb.create_task(Task(id="t1", title="Existing Task", task_type="coding")) + + yield project_root + del os.environ["BLACKBOARD_ROOT"] + + +@pytest.fixture +def client(): + return TestClient(app) + + +# =================================================================== +# Daemon API +# =================================================================== + +class TestDaemonAPI: + def test_status(self, client): + resp = client.get("/api/daemon/status") + assert resp.status_code == 200 + assert resp.json()["status"] == "running" + + def test_manual_tick(self, client): + resp = client.post("/api/daemon/tick") + assert resp.status_code == 200 + + +# =================================================================== +# Project API +# =================================================================== + +class TestProjectAPI: + def test_list_projects(self, client, project_env): + resp = client.get("/api/projects") + assert resp.status_code == 200 + assert "test-proj" in resp.json()["projects"] + + def test_create_project(self, client, tmp_path): + root = tmp_path / "new_root" + root.mkdir() + os.environ["BLACKBOARD_ROOT"] = str(root) + try: + resp = client.post("/api/projects", json={ + "id": "new-proj", "name": "New", + "agents": ["a1"], "description": "test", + }) + assert resp.status_code == 200 + assert resp.json()["ok"] + finally: + del os.environ["BLACKBOARD_ROOT"] + + def test_create_duplicate(self, client, project_env): + resp = client.post("/api/projects", json={ + "id": "test-proj", "name": "Dup", + }) + assert resp.status_code == 409 + + def test_get_project(self, client, project_env): + resp = client.get("/api/projects/test-proj") + assert resp.status_code == 200 + assert resp.json()["name"] == "Test Project" + + def test_get_nonexistent(self, client, project_env): + resp = client.get("/api/projects/nope") + assert resp.status_code == 404 + + +# =================================================================== +# Blackboard API +# =================================================================== + +class TestBlackboardAPI: + def test_list_tasks(self, client, project_env): + resp = client.get("/api/projects/test-proj/tasks") + assert resp.status_code == 200 + assert len(resp.json()["tasks"]) == 1 + + def test_get_task(self, client, project_env): + resp = client.get("/api/projects/test-proj/tasks/t1") + assert resp.status_code == 200 + assert resp.json()["title"] == "Existing Task" + + def test_get_task_404(self, client, project_env): + resp = client.get("/api/projects/test-proj/tasks/nope") + assert resp.status_code == 404 + + def test_create_task(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks", json={ + "id": "t2", "title": "New Task", "task_type": "review", + }) + assert resp.status_code == 200 + assert resp.json()["task_id"] == "t2" + + def test_claim_task(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/claim", + json={"agent": "agent1"}) + assert resp.status_code == 200 + + def test_update_status(self, client, project_env): + # Claim first + client.post("/api/projects/test-proj/tasks/t1/claim", + json={"agent": "agent1"}) + resp = client.post("/api/projects/test-proj/tasks/t1/status", + json={"status": "working", "agent": "agent1"}) + assert resp.status_code == 200 + + def test_invalid_status_transition(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/status", + json={"status": "done"}) + assert resp.status_code == 409 + + def test_add_comment(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/comments", json={ + "author": "pangtong", "body": "Nice", "comment_type": "general", + "mentions": ["agent1"], + }) + assert resp.status_code == 200 + + def test_get_comments(self, client, project_env): + client.post("/api/projects/test-proj/tasks/t1/comments", json={ + "author": "a", "body": "Hello", + }) + resp = client.get("/api/projects/test-proj/tasks/t1/comments") + assert resp.status_code == 200 + assert len(resp.json()["comments"]) == 1 + + def test_write_output(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/outputs", json={ + "agent": "agent1", "type": "code", "title": "main.py", + }) + assert resp.status_code == 200 + + def test_add_decision(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/decisions", json={ + "decider": "pangtong", "decision": "Use X", + "rationale": "Better", + }) + assert resp.status_code == 200 + + def test_add_observation(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/observations", json={ + "observer": "simayi", "body": "Warning!", "severity": "warning", + }) + assert resp.status_code == 200 + + def test_add_review(self, client, project_env): + resp = client.post("/api/projects/test-proj/tasks/t1/reviews", json={ + "id": "rev-1", "reviewer": "simayi", + "review_type": "output_review", "verdict": "approved", + "summary": "LGTM", "confidence": 0.9, + }) + assert resp.status_code == 200 + + def test_get_events(self, client, project_env): + resp = client.get("/api/projects/test-proj/events") + assert resp.status_code == 200 + assert "events" in resp.json() + + def test_summary(self, client, project_env): + resp = client.get("/api/projects/test-proj/summary") + assert resp.status_code == 200 + assert "pending" in resp.json()["summary"] + + +# =================================================================== +# SSE +# =================================================================== + +class TestSSE: + def test_sse_connection(self, client): + resp = client.get("/api/events") + assert resp.status_code == 200