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})