Files
agent_management/tests/test_template_agent_contract.py

119 lines
5.9 KiB
Python

"""Contract coverage for the template-agent lifecycle compatibility surface."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def _read(relative_path: str) -> str:
"""Load a repository file as text."""
return (ROOT / relative_path).read_text(encoding="utf-8")
def test_template_agent_lifecycle_routes_are_registered():
"""The HM-facing template-agent lifecycle endpoints should exist."""
app_source = _read("app.py")
assert 'class AgentLifecycleResponse(BaseModel):' in app_source
assert '@app.get("/agents/{agent_name}", response_model=AgentLifecycleResponse)' in app_source
assert '@app.post("/agents/{agent_name}/stop", response_model=MessageResponse)' in app_source
assert '"runtime_id": resolved_name' in app_source
assert '"runtime_status": runtime_status' in app_source
assert '"subdomain": subdomain' in app_source
def test_create_response_exposes_hm_friendly_alias_fields():
"""POST /agents should emit the alias fields that HM already knows how to parse."""
app_source = _read("app.py")
assert "runtime_id: Optional[str] = None" in app_source
assert "agent_id: Optional[str] = None" in app_source
assert 'result["runtime_id"] = result["name"]' in app_source
assert 'result["agent_id"] = result["name"]' in app_source
assert 'result["runtime_status"] = _normalize_agent_runtime_status(result.get("status"))' in app_source
assert 'result["subdomain"] = access_info.get("domain") or access_info.get("external_ip")' in app_source
def test_delete_endpoint_queries_db_before_openclaw_branch():
"""DELETE /agents/{id} should not reference db_agent before it is loaded."""
app_source = _read("app.py")
delete_start = app_source.index('@app.delete("/agents/{agent_name}", response_model=MessageResponse)')
delete_section = app_source[delete_start:]
assert 'db_agent = db.query(Agent).filter(Agent.name == agent_name).first()' in delete_section
assert delete_section.index('db_agent = db.query(Agent).filter(Agent.name == agent_name).first()') < delete_section.index(
"if db_agent and db_agent.agent_framework:"
)
def test_template_agent_doc_mentions_new_lifecycle_contract():
"""The coding A2A doc should describe the new lifecycle contract for HM."""
doc_source = _read("docs/CODING_A2A_AGENT_CREATE_AND_INVOKE.md")
assert "## 3.1 生命周期接口" in doc_source
assert "GET /agents/{agent_name}" in doc_source
assert "POST /agents/{agent_name}/stop" in doc_source
assert "`runtime_id` / `agent_id` / `id`" in doc_source
def test_integration_doc_mentions_template_agent_runtime_contract():
"""The main integration doc should include the /agents lifecycle compatibility APIs."""
doc_source = _read("docs/HEICODE_API_INTEGRATION.md")
assert "### 3.11 模板 Agent Runtime 兼容接口" in doc_source
assert "| `POST` | `/agents` | 创建模板 Agent;返回 HM 可直接解析的实例标识和访问地址别名字段 |" in doc_source
assert "| `GET` | `/agents/{agent_name}` | 查询模板 Agent 生命周期状态;返回平铺 `status` / `runtime_status` / `state` |" in doc_source
assert "| `POST` | `/agents/{agent_name}/stop` | 幂等停止模板 Agent;停止运行 Pod,但保留数据库记录 |" in doc_source
assert "删除接口当前已修复模板 Agent 场景下的数据库变量引用问题" in doc_source
def test_a2a_servers_enforce_agent_access_token_contract():
"""A2A servers should support local X-Agent-Access-Token validation."""
coding_server_source = _read("agent_templates/agents/coding_a2a_agent/a2a_server.py")
litellm_server_source = _read("agent_templates/agents/a2a_litellm_agent/a2a_server.py")
for source in (coding_server_source, litellm_server_source):
assert 'AGENT_ACCESS_TOKEN = os.getenv("AGENT_ACCESS_TOKEN", "")' in source
assert 'AGENT_ACCESS_HEADER = "X-Agent-Access-Token"' in source
assert "secrets.compare_digest" in source
assert 'status_code=401' in source
assert 'status_code=403' in source
assert 'request.headers.get(AGENT_ACCESS_HEADER, "")' in source
def test_a2a_docs_describe_agent_access_token_header():
"""Template-agent docs should explain the local access-token authentication flow."""
coding_doc_source = _read("docs/CODING_A2A_AGENT_CREATE_AND_INVOKE.md")
integration_doc_source = _read("docs/HEICODE_API_INTEGRATION.md")
assert "AGENT_ACCESS_TOKEN" in coding_doc_source
assert "X-Agent-Access-Token" in coding_doc_source
assert "缺少请求头时返回 `401`" in coding_doc_source
assert "请求头不匹配时返回 `403`" in coding_doc_source
assert "客户端直连鉴权" in integration_doc_source
assert "X-Agent-Access-Token == AGENT_ACCESS_TOKEN" in integration_doc_source
def test_dedicated_template_agent_contract_doc_exists():
"""A standalone HM-facing template-agent contract doc should exist."""
doc_source = _read("docs/HEICODE_TEMPLATE_AGENT_RUNTIME_CONTRACT.md")
assert "# Heicode Template Agent Runtime 对接文档" in doc_source
assert "## 3. 状态接口" in doc_source
assert "GET /agents/{agent_name}" in doc_source
assert "GET /agents/{agent_name}/status" in doc_source
assert "POST /agents/{agent_name}/stop" in doc_source
assert "DELETE /agents/{agent_name}" in doc_source
assert "X-Agent-Access-Token" in doc_source
def test_template_manager_exposes_agent_access_token_envs():
"""Template definitions should advertise the HM access-token envs explicitly."""
template_source = _read("template_manager.py")
assert '"a2a_litellm_agent": {' in template_source
assert '"coding_a2a_agent": {' in template_source
assert '"AGENT_ACCESS_TOKEN": "客户端访问令牌;若设置,A2A 请求必须携带 X-Agent-Access-Token"' in template_source
assert '"HEICODE_AGENT_ID": "上层系统分配的 agent_id,用于 health / agent card 暴露与联调排查"' in template_source