feat: initial agent-reviewer meta-agent
A2A agent that audits another deployed agent's source pre-deploy. Reads the target Gitea repo via a short-lived read-only token minted through ctx.mint_gitea_token(), runs an inner DeepAgents graph backed by GiteaBackend, and emits a typed ReviewReport. Skill bundles: - security-anti-patterns (credentials, eval, sandbox bypass, egress) - a2apack-best-practices (class shape, decorators, types, timeouts) - grant-scope-evaluation (declared vs actual workspace scope) Tools: - sandbox_a2a_card : round-trips the project through microsandbox - sandbox_ruff_check: objective static checks - submit_review_report: typed final report 7 smoke tests pass; agent card loads cleanly via `a2a card`.
This commit is contained in:
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
139
tests/test_agent.py
Normal file
139
tests/test_agent.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""Smoke tests for agent-reviewer: card, schema, validation behaviors."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from agent import AgentReviewer # noqa: E402
|
||||
from agent_reviewer.tools import Finding, ReviewReport, ToolContext, build_tools # noqa: E402
|
||||
from agent_reviewer.config import load_settings # noqa: E402
|
||||
|
||||
|
||||
def test_class_metadata() -> None:
|
||||
assert AgentReviewer.name == "agent-reviewer"
|
||||
assert AgentReviewer.version
|
||||
assert AgentReviewer.wants_cp_jwt is True
|
||||
|
||||
|
||||
def test_review_skill_registered() -> None:
|
||||
spec = AgentReviewer._skills["review"] # type: ignore[attr-defined]
|
||||
assert spec.stream is True
|
||||
assert spec.policy.timeout_seconds == 900
|
||||
# Reviewer is read-only — no write grants declared.
|
||||
assert not spec.policy.grant_write_prefixes
|
||||
assert spec.policy.grant_outputs_prefix is None
|
||||
|
||||
|
||||
def test_review_report_round_trips() -> None:
|
||||
report = ReviewReport(
|
||||
ok=False,
|
||||
agent_name="hello",
|
||||
ref="main",
|
||||
summary="one critical finding",
|
||||
findings=[
|
||||
Finding(
|
||||
severity="critical",
|
||||
category="security",
|
||||
message="hardcoded API key",
|
||||
file="agent.py",
|
||||
line=42,
|
||||
suggestion="read ctx.llm.api_key instead",
|
||||
)
|
||||
],
|
||||
)
|
||||
dumped = report.model_dump()
|
||||
assert dumped["ok"] is False
|
||||
again = ReviewReport.model_validate(dumped)
|
||||
assert again.findings[0].severity == "critical"
|
||||
|
||||
|
||||
def test_invalid_severity_rejected() -> None:
|
||||
with pytest.raises(Exception):
|
||||
Finding(
|
||||
severity="catastrophic", # type: ignore[arg-type]
|
||||
category="security",
|
||||
message="x",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_review_report_writes_completion_box() -> None:
|
||||
box: dict = {}
|
||||
tools = build_tools(
|
||||
ToolContext(
|
||||
settings=load_settings(),
|
||||
agent_name="hello",
|
||||
ref="main",
|
||||
fetch_source=lambda: {}, # unused for submit
|
||||
completion_box=box,
|
||||
)
|
||||
)
|
||||
submit = next(t for t in tools if t.name == "submit_review_report")
|
||||
result = await submit.ainvoke(
|
||||
{
|
||||
"report_json": json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"agent_name": "ignored",
|
||||
"ref": "ignored",
|
||||
"summary": "clean",
|
||||
"findings": [],
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
assert result == {"accepted": True}
|
||||
assert "report" in box
|
||||
# Reviewer overrides agent_name / ref from ctx, not from the LLM.
|
||||
assert box["report"].agent_name == "hello"
|
||||
assert box["report"].ref == "main"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_review_report_rejects_bad_json() -> None:
|
||||
box: dict = {}
|
||||
tools = build_tools(
|
||||
ToolContext(
|
||||
settings=load_settings(),
|
||||
agent_name="hello",
|
||||
ref="main",
|
||||
fetch_source=lambda: {},
|
||||
completion_box=box,
|
||||
)
|
||||
)
|
||||
submit = next(t for t in tools if t.name == "submit_review_report")
|
||||
result = await submit.ainvoke({"report_json": "{not json"})
|
||||
assert result["accepted"] is False
|
||||
assert "error" in result
|
||||
assert "report" not in box
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_review_report_rejects_bad_schema() -> None:
|
||||
box: dict = {}
|
||||
tools = build_tools(
|
||||
ToolContext(
|
||||
settings=load_settings(),
|
||||
agent_name="hello",
|
||||
ref="main",
|
||||
fetch_source=lambda: {},
|
||||
completion_box=box,
|
||||
)
|
||||
)
|
||||
submit = next(t for t in tools if t.name == "submit_review_report")
|
||||
result = await submit.ainvoke(
|
||||
{
|
||||
"report_json": json.dumps(
|
||||
{"ok": True, "summary": "missing agent_name"}
|
||||
)
|
||||
}
|
||||
)
|
||||
assert result["accepted"] is False
|
||||
assert "report" not in box
|
||||
Reference in New Issue
Block a user