284 lines
9.9 KiB
Python
284 lines
9.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""HTTP smoke tests for agents lifecycle (list/create/update/order/toggle)."""
|
|
from __future__ import annotations
|
|
|
|
|
|
import pytest
|
|
|
|
from pineagents.constant import BUILTIN_QA_AGENT_ID
|
|
from tests.integration.helpers import wait_for_agent_startup
|
|
|
|
_AGENT_PROFILE_TOP_LEVEL_KEYS = (
|
|
"channels",
|
|
"mcp",
|
|
"heartbeat",
|
|
"running",
|
|
"llm_routing",
|
|
"system_prompt_files",
|
|
"tools",
|
|
"plan",
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.p0
|
|
def test_api_agents_list_create_get_delete(app_server) -> None:
|
|
"""Test purpose:
|
|
- Verify the primary Agents management flow works end-to-end: list system
|
|
agents, create one, read details, delete it, and confirm removal.
|
|
- Verify a newly created agent includes required top-level config groups.
|
|
|
|
Test flow:
|
|
1. GET /api/agents; confirm list contains ``default`` and builtin QA.
|
|
2. POST /api/agents to create a test agent.
|
|
3. GET /api/agents/{agentId} and validate name plus key config groups.
|
|
4. DELETE /api/agents/{agentId}.
|
|
5. GET /api/agents/{agentId} again and assert 404.
|
|
|
|
API endpoints:
|
|
- GET /api/agents
|
|
- POST /api/agents
|
|
- GET /api/agents/{agentId}
|
|
- DELETE /api/agents/{agentId}
|
|
"""
|
|
agent_id = "integ_agents_01"
|
|
|
|
list_resp = app_server.api_request("GET", "/api/agents")
|
|
assert list_resp.status_code == 200, app_server.logs_tail()
|
|
agents_payload = list_resp.json()
|
|
assert "agents" in agents_payload
|
|
assert isinstance(agents_payload["agents"], list)
|
|
listed_ids = {a["id"] for a in agents_payload["agents"]}
|
|
assert "default" in listed_ids, f"missing default agent in {listed_ids}"
|
|
assert (
|
|
BUILTIN_QA_AGENT_ID in listed_ids
|
|
), f"missing builtin QA agent in {listed_ids}"
|
|
|
|
create_resp = app_server.api_request(
|
|
"POST",
|
|
"/api/agents",
|
|
json={
|
|
"id": agent_id,
|
|
"name": "Integration smoke agent",
|
|
"description": "",
|
|
},
|
|
)
|
|
assert create_resp.status_code == 201, app_server.logs_tail()
|
|
created = create_resp.json()
|
|
assert created["id"] == agent_id
|
|
assert created.get("enabled") is True
|
|
assert isinstance(created.get("workspace_dir"), str)
|
|
assert created["workspace_dir"].strip()
|
|
assert wait_for_agent_startup(app_server, agent_id) == "running"
|
|
|
|
try:
|
|
get_resp = app_server.api_request("GET", f"/api/agents/{agent_id}")
|
|
assert get_resp.status_code == 200, app_server.logs_tail()
|
|
profile = get_resp.json()
|
|
assert profile["id"] == agent_id
|
|
assert profile["name"] == "Integration smoke agent"
|
|
missing_keys = [
|
|
k for k in _AGENT_PROFILE_TOP_LEVEL_KEYS if k not in profile
|
|
]
|
|
assert not missing_keys, (
|
|
f"agent profile missing top-level keys {missing_keys}; "
|
|
f"have {sorted(profile.keys())}"
|
|
)
|
|
finally:
|
|
del_resp = app_server.api_request("DELETE", f"/api/agents/{agent_id}")
|
|
assert del_resp.status_code == 200, app_server.logs_tail()
|
|
assert del_resp.json().get("success") is True
|
|
|
|
missing = app_server.api_request("GET", f"/api/agents/{agent_id}")
|
|
assert missing.status_code == 404, app_server.logs_tail()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.p0
|
|
def test_api_agents_update_and_readback(app_server) -> None:
|
|
"""Test purpose:
|
|
- Verify full agent updates (PUT) take effect and can be read back via GET.
|
|
|
|
Test flow:
|
|
1. POST /api/agents to create a test agent.
|
|
2. GET /api/agents/{agentId} to obtain a complete profile payload.
|
|
3. Update name/description/system_prompt_files and PUT it back.
|
|
4. GET /api/agents/{agentId} again and verify updated fields.
|
|
5. DELETE /api/agents/{agentId} for cleanup.
|
|
|
|
API endpoints:
|
|
- POST /api/agents
|
|
- GET /api/agents/{agentId}
|
|
- PUT /api/agents/{agentId}
|
|
- DELETE /api/agents/{agentId}
|
|
"""
|
|
agent_id = "integ_agents_upd_01"
|
|
updated_name = "Integration smoke agent updated"
|
|
updated_desc = "updated by integration test"
|
|
updated_prompt_files = ["AGENTS.md", "PROFILE.md", "SOUL.md"]
|
|
|
|
create_resp = app_server.api_request(
|
|
"POST",
|
|
"/api/agents",
|
|
json={
|
|
"id": agent_id,
|
|
"name": "Integration smoke agent",
|
|
"description": "",
|
|
},
|
|
)
|
|
assert create_resp.status_code == 201, app_server.logs_tail()
|
|
assert wait_for_agent_startup(app_server, agent_id) == "running"
|
|
|
|
try:
|
|
get_before = app_server.api_request("GET", f"/api/agents/{agent_id}")
|
|
assert get_before.status_code == 200, app_server.logs_tail()
|
|
profile = get_before.json()
|
|
|
|
profile["name"] = updated_name
|
|
profile["description"] = updated_desc
|
|
profile["system_prompt_files"] = updated_prompt_files
|
|
|
|
put_resp = app_server.api_request(
|
|
"PUT",
|
|
f"/api/agents/{agent_id}",
|
|
json=profile,
|
|
)
|
|
assert put_resp.status_code == 200, app_server.logs_tail()
|
|
put_payload = put_resp.json()
|
|
assert put_payload["id"] == agent_id
|
|
assert put_payload["name"] == updated_name
|
|
assert put_payload["description"] == updated_desc
|
|
assert put_payload["system_prompt_files"] == updated_prompt_files
|
|
|
|
get_after = app_server.api_request("GET", f"/api/agents/{agent_id}")
|
|
assert get_after.status_code == 200, app_server.logs_tail()
|
|
after_payload = get_after.json()
|
|
assert after_payload["name"] == updated_name
|
|
assert after_payload["description"] == updated_desc
|
|
assert after_payload["system_prompt_files"] == updated_prompt_files
|
|
finally:
|
|
app_server.api_request("DELETE", f"/api/agents/{agent_id}")
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.p1
|
|
def test_api_agents_order_put_roundtrip(app_server) -> None:
|
|
"""Test purpose:
|
|
- Verify /api/agents/order persists a full agent order update.
|
|
|
|
Test flow:
|
|
1. GET /api/agents to capture a stable baseline order.
|
|
2. PUT /api/agents/order with a reordered full agent ID list.
|
|
3. GET /api/agents and verify returned order matches the updated order.
|
|
4. PUT baseline order back for cleanup.
|
|
|
|
API endpoints:
|
|
- GET /api/agents
|
|
- PUT /api/agents/order
|
|
"""
|
|
list_before = app_server.api_request("GET", "/api/agents")
|
|
assert list_before.status_code == 200, app_server.logs_tail()
|
|
agents_before = list_before.json().get("agents", [])
|
|
assert isinstance(agents_before, list)
|
|
assert len(agents_before) >= 2, "need at least two agents for reorder test"
|
|
|
|
baseline_ids = [item["id"] for item in agents_before]
|
|
regular_ids = [
|
|
item["id"]
|
|
for item in agents_before
|
|
if item["id"] != "default" and not item.get("pinned", False)
|
|
]
|
|
if len(regular_ids) < 2:
|
|
pytest.skip("need at least two unpinned agents for reorder test")
|
|
|
|
first_regular = baseline_ids.index(regular_ids[0])
|
|
reordered_ids = list(baseline_ids)
|
|
reordered_ids[first_regular : first_regular + 2] = reversed(
|
|
reordered_ids[first_regular : first_regular + 2],
|
|
)
|
|
|
|
try:
|
|
put_resp = app_server.api_request(
|
|
"PUT",
|
|
"/api/agents/order",
|
|
json={"agent_ids": reordered_ids},
|
|
)
|
|
assert put_resp.status_code == 200, app_server.logs_tail()
|
|
assert put_resp.json().get("success") is True
|
|
assert put_resp.json().get("agent_ids") == reordered_ids
|
|
|
|
list_after = app_server.api_request("GET", "/api/agents")
|
|
assert list_after.status_code == 200, app_server.logs_tail()
|
|
after_ids = [
|
|
item["id"] for item in list_after.json().get("agents", [])
|
|
]
|
|
assert after_ids == reordered_ids
|
|
finally:
|
|
restore = app_server.api_request(
|
|
"PUT",
|
|
"/api/agents/order",
|
|
json={"agent_ids": baseline_ids},
|
|
)
|
|
assert restore.status_code == 200, app_server.logs_tail()
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.p0
|
|
def test_api_agent_patch_toggle_enabled_roundtrip(app_server) -> None:
|
|
"""Test purpose:
|
|
- Verify PATCH /api/agents/{agentId}/toggle updates persisted ``enabled``
|
|
and can be read back from the agent profile.
|
|
|
|
Test flow:
|
|
1. POST a dedicated test agent (starts enabled).
|
|
2. PATCH toggle with ``enabled`` false and GET /api/agents list to verify
|
|
the summary row for that agent shows ``enabled`` false.
|
|
3. PATCH toggle with ``enabled`` true and verify via list again.
|
|
4. Delete test agent.
|
|
|
|
API endpoints:
|
|
- POST /api/agents
|
|
- PATCH /api/agents/{agentId}/toggle
|
|
- GET /api/agents
|
|
- DELETE /api/agents/{agentId}
|
|
"""
|
|
agent_id = "integ_agent_toggle_01"
|
|
|
|
def _enabled_from_list() -> bool | None:
|
|
list_resp = app_server.api_request("GET", "/api/agents")
|
|
assert list_resp.status_code == 200, app_server.logs_tail()
|
|
for row in list_resp.json().get("agents", []):
|
|
if row.get("id") == agent_id:
|
|
return bool(row.get("enabled", True))
|
|
return None
|
|
|
|
create_agent = app_server.api_request(
|
|
"POST",
|
|
"/api/agents",
|
|
json={"id": agent_id, "name": "Toggle agent", "description": ""},
|
|
)
|
|
assert create_agent.status_code == 201, app_server.logs_tail()
|
|
assert wait_for_agent_startup(app_server, agent_id) == "running"
|
|
|
|
try:
|
|
off = app_server.api_request(
|
|
"PATCH",
|
|
f"/api/agents/{agent_id}/toggle",
|
|
json={"enabled": False},
|
|
)
|
|
assert off.status_code == 200, app_server.logs_tail()
|
|
assert off.json().get("enabled") is False
|
|
assert _enabled_from_list() is False
|
|
|
|
on = app_server.api_request(
|
|
"PATCH",
|
|
f"/api/agents/{agent_id}/toggle",
|
|
json={"enabled": True},
|
|
)
|
|
assert on.status_code == 200, app_server.logs_tail()
|
|
assert on.json().get("enabled") is True
|
|
assert _enabled_from_list() is True
|
|
assert wait_for_agent_startup(app_server, agent_id) == "running"
|
|
finally:
|
|
app_server.api_request("DELETE", f"/api/agents/{agent_id}")
|