Files
webber/webber-api/tests/test_plan_agent.py
T
jpmschweitzerandClaude eb3467d06a fix(webber-api): clear ruff, and two things it was pointing at
97 findings to zero. Most were mechanical — 52 unsorted import blocks, 10
unsorted __all__, assorted pyupgrade and simplify hints. Two were not, and both
were visible only because the lint made me look.

`webber version` did not exist. src/cli/commands/version.py defines
show_version(), main.py imported it, and the registration line was never
written — the CLI exposed chat and explore only. The import carried
`# noqa: F401`, which is what kept the omission quiet: someone marked the
symptom as intentional instead of asking why it was unused. show_version is not
redundant with the --version flag; it prints the resolved Ollama URL, model and
debug state, which is the form worth having when something is misconfigured.
Registered, and the suppression dropped because the import is now genuinely used.

test_spawn_explore_agent asserted nothing. It built a mock RunContext, patched
get_agent, and stopped at the comment "For now, verify the explore agent would
be called correctly". It had been counted as a passing test. An AST sweep of all
238 test functions found it was the only one, which is worth knowing — the
problem was contained, not systemic. It is now skipped with a reason, so it
reports as unfinished rather than as passing. Reducing it rather than deleting
its imports was the point: tidying the imports would have made a hollow test
look clean.

Two findings were false positives, and both are recorded rather than silently
worked around:

B023 flagged run_agent closing over full_prompt and ctx. Traced: agent_task is
awaited at line 326 before `continue` reaches the next iteration, so neither
name can be rebound while the closure is pending, and the exception path
cancels and awaits too. Not a bug. Bound as defaults anyway, because that stays
true if the await ever moves. I had called it a live bug before tracing it,
which is the mistake Rule 5 exists for.

RUF012 flagged `rules: list[ApprovalRule] = []` on ApprovalRuleSet. Its
suggested fix — annotate ClassVar — would remove the field from the model.
ApprovalRuleSet is a pydantic model and pydantic deep-copies defaults per
instance; verified by constructing two and confirming their lists are distinct
objects. Suppressed with that evidence in the comment. Ruff cannot see the
pydantic base because BaseSchema is a local subclass of BaseModel.

Also moved a stray `from src.shared.logging import ...` that had drifted below
a function definition, and merged a nested if in the ollama provider.

215 passed, 23 skipped, unchanged except for the new skip. `webber version`
exercised end to end.

mypy is NOT addressed here and the gate still fails on it — 55 errors in 14
files, 35 of them no-any-return from pydantic_ai's untyped returns. That was
hidden behind ruff, because the gate stops at the first failing stage.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 15:05:02 +02:00

153 lines
4.9 KiB
Python

"""
Tests for the Plan agent.
Tests registration, API endpoints, and tool restrictions.
"""
import pytest
from src.domains.agents.base import get_agent, list_agents
from src.domains.agents.plan import PlanAgentImpl, plan_agent
class TestPlanAgentRegistration:
"""Tests for Plan agent registration."""
def test_plan_agent_registered(self):
"""Test that plan agent is registered in registry."""
agent = get_agent("plan")
assert agent is not None
assert agent.name == "plan"
def test_plan_agent_in_list(self):
"""Test that plan agent appears in agent list."""
agents = list_agents()
names = [a["name"] for a in agents]
assert "plan" in names
def test_plan_agent_has_description(self):
"""Test that plan agent has a description."""
agent = get_agent("plan")
assert agent is not None
assert len(agent.description) > 0
assert "plan" in agent.description.lower() or "architect" in agent.description.lower()
def test_plan_agent_singleton(self):
"""Test that plan_agent is the registered instance."""
registered = get_agent("plan")
assert registered is plan_agent
def test_plan_agent_is_correct_type(self):
"""Test that plan agent is correct implementation type."""
assert isinstance(plan_agent, PlanAgentImpl)
class TestPlanAgentTools:
"""Tests for Plan agent tool restrictions."""
def test_plan_agent_has_read_only_tools(self):
"""Test that plan agent has read-only tools."""
# Access the underlying PydanticAI agent to check tools
agent = plan_agent.agent
tool_names = list(agent._function_toolset.tools.keys())
# Should have read-only tools
assert "read_file" in tool_names
assert "glob_files" in tool_names
assert "grep_content" in tool_names
assert "bash_readonly" in tool_names
def test_plan_agent_no_write_tools(self):
"""Test that plan agent does NOT have write tools."""
agent = plan_agent.agent
tool_names = list(agent._function_toolset.tools.keys())
# Should NOT have write tools
assert "edit_file" not in tool_names
assert "write_file" not in tool_names
assert "bash" not in tool_names
assert "web_search" not in tool_names
def test_plan_agent_tool_count(self):
"""Test that plan agent has exactly 4 tools."""
agent = plan_agent.agent
tool_count = len(agent._function_toolset.tools)
assert tool_count == 4
class TestPlanAgentAPI:
"""Tests for Plan agent REST API."""
@pytest.mark.anyio
async def test_list_agents_includes_plan(self, auth_client):
"""Test that agent list includes plan agent."""
response = await auth_client.get("/agents/")
assert response.status_code == 200
data = response.json()
names = [a["name"] for a in data["agents"]]
assert "plan" in names
@pytest.mark.anyio
async def test_get_plan_agent_info(self, auth_client):
"""Test getting plan agent info."""
response = await auth_client.get("/agents/plan")
assert response.status_code == 200
data = response.json()
assert data["name"] == "plan"
assert "description" in data
assert len(data["description"]) > 0
@pytest.mark.anyio
async def test_run_plan_with_invalid_body(self, auth_client):
"""Test running plan agent with invalid request."""
response = await auth_client.post(
"/agents/run",
json={
"agent_type": "plan",
# Missing prompt
}
)
assert response.status_code == 422
@pytest.mark.anyio
async def test_stream_plan_with_invalid_body(self, auth_client):
"""Test streaming plan agent with invalid request."""
response = await auth_client.post(
"/agents/stream",
json={
"agent_type": "plan",
# Missing prompt
}
)
assert response.status_code == 422
class TestPlanAgentProperties:
"""Tests for Plan agent properties and configuration."""
def test_plan_agent_name(self):
"""Test plan agent name property."""
assert plan_agent.name == "plan"
def test_plan_agent_description_not_empty(self):
"""Test plan agent description is not empty."""
assert plan_agent.description
assert len(plan_agent.description) > 10
def test_plan_agent_creates_agent_lazily(self):
"""Test that PydanticAI agent is created lazily."""
# Create a fresh instance
fresh_agent = PlanAgentImpl()
# _agent should be None before first access
assert fresh_agent._agent is None
# Access the agent property
_ = fresh_agent.agent
# Now _agent should be set
assert fresh_agent._agent is not None