diff --git a/ai-hub/integration_tests/API_COVERAGE.md b/ai-hub/integration_tests/API_COVERAGE.md index 61938df..ffb70c9 100644 --- a/ai-hub/integration_tests/API_COVERAGE.md +++ b/ai-hub/integration_tests/API_COVERAGE.md @@ -9,6 +9,8 @@ - [x] Fully map coverage for the `/models` endpoints to `test_provider_config.py`. - [ ] Write missing tests for `/users/me/profile` endpoints. - [ ] Review `/skills` and `/knowledge` mapping against `test_browser_llm.py` which only touches standard capability mapping. +- [x] Fully map coverage for the Agents logic into `test_agents.py` + ## Covered APIs @@ -66,3 +68,17 @@ - [x] `GET /speech/voices` (List available TTS voices) - [x] `POST /speech` (Generate speech from text) - [x] `POST /stt/transcribe` (Transcribe audio to text) + +### Agents (`/agents`) +Covered by `integration_tests/test_agents.py` +- [x] `POST /agents/templates` (Create Template) +- [x] `POST /agents/instances` (Create Instance) +- [x] `PATCH /agents/{id}/status` (Update Status) +- [x] `PATCH /agents/{id}/config` (Update Config) +- [x] `GET /agents` (Get Agents) +- [x] `POST /agents/{id}/triggers` (Create Agent Trigger) +- [x] `GET /agents/{id}/triggers` (Get Agent Triggers) +- [x] `DELETE /agents/triggers/{trigger_id}` (Delete trigger) +- [x] `DELETE /agents/{id}` (Delete agent) +- [ ] `POST /agents/{id}/webhook` (Webhook Receiver) +- [ ] `POST /agents/deploy` (Deploy Agent) diff --git a/ai-hub/integration_tests/test_agents.py b/ai-hub/integration_tests/test_agents.py new file mode 100644 index 0000000..685c793 --- /dev/null +++ b/ai-hub/integration_tests/test_agents.py @@ -0,0 +1,98 @@ +import pytest +import httpx +import os +import uuid +from conftest import BASE_URL + +def _headers(): + uid = os.getenv("SYNC_TEST_USER_ID", "") + return {"X-User-ID": uid} + +def test_agent_lifecycle_and_api_coverage(): + """ + Test suite covering Agent API endpoints: + 1. Register Node + 2. Register Template + 3. Register Instance + 4. Register Trigger + 5. List Agents + 6. Stop Agent + 7. Remove Agent + """ + node_id = f"test-agent-node-{uuid.uuid4().hex[:8]}" + admin_id = os.getenv("SYNC_TEST_USER_ID", "") + + with httpx.Client(timeout=10.0) as client: + # 1. Register a test node specifically for this agent testing + node_payload = { + "node_id": node_id, + "display_name": "Agent Test Node", + "is_active": True, + "skill_config": {"shell": {"enabled": True}} + } + r_node = client.post(f"{BASE_URL}/nodes/admin", params={"admin_id": admin_id}, json=node_payload) + # If conflicts, clear first + if r_node.status_code in (400, 409): + client.delete(f"{BASE_URL}/nodes/admin/{node_id}", params={"admin_id": admin_id}) + r_node = client.post(f"{BASE_URL}/nodes/admin", params={"admin_id": admin_id}, json=node_payload) + assert r_node.status_code == 200, f"Node registration failed: {r_node.text}" + + # 2. Register a basic Agent Template + tmpl_payload = { + "name": "Cron Print Agent", + "description": "Periodically prints to the node console", + "system_prompt_path": "You are a cron agent. Run shell tasks periodically.", + "max_loop_iterations": 1 + } + r_tmpl = client.post(f"{BASE_URL}/agents/templates", json=tmpl_payload, headers=_headers()) + assert r_tmpl.status_code == 200, f"Template registration failed: {r_tmpl.text}" + template_id = r_tmpl.json()["id"] + + # 3. Register a simple Agent Instance + inst_payload = { + "template_id": template_id, + "mesh_node_id": node_id, + "status": "active" + } + r_inst = client.post(f"{BASE_URL}/agents/instances", json=inst_payload, headers=_headers()) + assert r_inst.status_code == 200, f"Instance registration failed: {r_inst.text}" + instance_id = r_inst.json()["id"] + + # 4. Choose a basic trigger method (interval/cron) + trig_payload = { + "trigger_type": "interval", + "interval_seconds": 10, + "default_prompt": "Run the task now" + } + r_trig = client.post(f"{BASE_URL}/agents/{instance_id}/triggers", json=trig_payload, headers=_headers()) + assert r_trig.status_code == 200, f"Trigger logic failed: {r_trig.text}" + trigger_id = r_trig.json()["id"] + + # 5. Test if agent is in the active list + r_list = client.get(f"{BASE_URL}/agents", headers=_headers()) + assert r_list.status_code == 200 + agents = r_list.json() + assert any(a["id"] == instance_id for a in agents), "Instance not found in active list" + + # Fetch triggers back + r_trig_get = client.get(f"{BASE_URL}/agents/{instance_id}/triggers", headers=_headers()) + assert r_trig_get.status_code == 200 + assert any(t["id"] == trigger_id for t in r_trig_get.json()), "Trigger not found on instance" + + # 6. Test Stop Update / Config Update + r_stop = client.patch(f"{BASE_URL}/agents/{instance_id}/status", json={"status": "stopped"}, headers=_headers()) + assert r_stop.status_code == 200 + assert r_stop.json()["status"] == "stopped" + + r_cfg = client.patch(f"{BASE_URL}/agents/{instance_id}/config", json={"name": "Updated Cron Agent", "mesh_node_id": node_id}, headers=_headers()) + assert r_cfg.status_code == 200 + + # 7. Test Remove (delete trigger first, then agent) + r_del_trig = client.delete(f"{BASE_URL}/agents/triggers/{trigger_id}", headers=_headers()) + assert r_del_trig.status_code == 200 + + r_del_agent = client.delete(f"{BASE_URL}/agents/{instance_id}", headers=_headers()) + assert r_del_agent.status_code == 200 + + # Cleanup Node + client.delete(f"{BASE_URL}/nodes/admin/{node_id}", params={"admin_id": admin_id})