"""Tests for OpenAPI tool generator module. RED phase: written before implementation. """ from __future__ import annotations import pytest from app.openapi.models import ClassificationResult, EndpointInfo, ParameterInfo pytestmark = pytest.mark.unit _BASE_URL = "https://api.example.com" def _make_endpoint( path: str = "/items", method: str = "GET", operation_id: str = "list_items", summary: str = "List items", description: str = "Returns all items", parameters: tuple[ParameterInfo, ...] = (), request_body_schema: dict | None = None, ) -> EndpointInfo: return EndpointInfo( path=path, method=method, operation_id=operation_id, summary=summary, description=description, parameters=parameters, request_body_schema=request_body_schema, ) def _make_classification( endpoint: EndpointInfo, access_type: str = "read", needs_interrupt: bool = False, agent_group: str = "read_agent", ) -> ClassificationResult: return ClassificationResult( endpoint=endpoint, access_type=access_type, customer_params=(), agent_group=agent_group, confidence=0.9, needs_interrupt=needs_interrupt, ) _PATH_PARAM = ParameterInfo( name="item_id", location="path", required=True, schema_type="string" ) _QUERY_PARAM = ParameterInfo( name="filter", location="query", required=False, schema_type="string" ) class TestGenerateToolCode: """Tests for generate_tool_code function.""" def test_generate_tool_for_get_endpoint(self) -> None: """Generated tool for GET endpoint is a GeneratedTool with non-empty code.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint(method="GET") clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) assert tool.function_name == "list_items" assert tool.code != "" assert "@tool" in tool.code def test_generate_tool_contains_function_name(self) -> None: """Generated code contains the function name.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint(operation_id="get_order", method="GET") clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) assert "get_order" in tool.code def test_generate_tool_contains_base_url(self) -> None: """Generated code contains the base URL.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint() clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) assert _BASE_URL in tool.code def test_generate_tool_contains_http_method(self) -> None: """Generated code uses the correct HTTP method.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint(method="POST") clf = _make_classification(ep, access_type="write") tool = generate_tool_code(clf, _BASE_URL) assert "post" in tool.code.lower() def test_generate_tool_for_post_with_body(self) -> None: """Generated tool for POST includes body parameter.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint( method="POST", request_body_schema={"type": "object", "properties": {"name": {"type": "string"}}}, ) clf = _make_classification(ep, access_type="write") tool = generate_tool_code(clf, _BASE_URL) assert tool.code != "" assert "POST" in tool.code or "post" in tool.code def test_generate_tool_with_path_params(self) -> None: """Generated tool includes path parameter in function signature.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint( path="/items/{item_id}", operation_id="get_item", parameters=(_PATH_PARAM,), ) clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) assert "item_id" in tool.code def test_write_tool_includes_interrupt_marker(self) -> None: """Write tools that need interrupt include a marker comment.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint(method="DELETE", operation_id="delete_item") clf = _make_classification(ep, access_type="write", needs_interrupt=True) tool = generate_tool_code(clf, _BASE_URL) assert "interrupt" in tool.code.lower() or "approval" in tool.code.lower() def test_generated_code_is_executable(self) -> None: """Generated code can be exec'd without syntax errors.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint( path="/items/{item_id}", operation_id="fetch_item", parameters=(_PATH_PARAM,), ) clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) # Must be valid Python syntax compile(tool.code, "", "exec") def test_generated_tool_code_exec_imports(self) -> None: """Generated code exec'd with required imports does not raise.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint() clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) namespace: dict = {} try: import httpx from langchain_core.tools import tool as lc_tool namespace = {"httpx": httpx, "tool": lc_tool} exec(tool.code, namespace) # noqa: S102 except ImportError: pytest.skip("langchain_core not available for exec test") def test_returns_generated_tool_instance(self) -> None: """generate_tool_code returns a GeneratedTool instance.""" from app.openapi.generator import generate_tool_code from app.openapi.models import GeneratedTool ep = _make_endpoint() clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) assert isinstance(tool, GeneratedTool) def test_generated_tool_is_frozen(self) -> None: """GeneratedTool instance is immutable.""" from app.openapi.generator import generate_tool_code ep = _make_endpoint() clf = _make_classification(ep) tool = generate_tool_code(clf, _BASE_URL) with pytest.raises((AttributeError, TypeError)): tool.code = "new code" # type: ignore[misc] class TestGenerateAgentYaml: """Tests for generate_agent_yaml function.""" def test_generate_yaml_is_valid_string(self) -> None: """generate_agent_yaml returns a non-empty string.""" from app.openapi.generator import generate_agent_yaml ep = _make_endpoint() clf = _make_classification(ep) result = generate_agent_yaml((clf,), _BASE_URL) assert isinstance(result, str) assert len(result) > 0 def test_generated_yaml_is_parseable(self) -> None: """Output can be parsed as YAML.""" import yaml from app.openapi.generator import generate_agent_yaml ep = _make_endpoint() clf = _make_classification(ep) result = generate_agent_yaml((clf,), _BASE_URL) parsed = yaml.safe_load(result) assert isinstance(parsed, dict) def test_generated_yaml_contains_agents_key(self) -> None: """Generated YAML has an 'agents' key matching AgentConfig format.""" import yaml from app.openapi.generator import generate_agent_yaml ep = _make_endpoint() clf = _make_classification(ep) result = generate_agent_yaml((clf,), _BASE_URL) parsed = yaml.safe_load(result) assert "agents" in parsed def test_generated_yaml_contains_tool_name(self) -> None: """Generated YAML references the tool function name.""" from app.openapi.generator import generate_agent_yaml ep = _make_endpoint(operation_id="list_orders") clf = _make_classification(ep) result = generate_agent_yaml((clf,), _BASE_URL) assert "list_orders" in result def test_empty_classifications_returns_empty_agents(self) -> None: """No classifications yields YAML with empty agents list.""" import yaml from app.openapi.generator import generate_agent_yaml result = generate_agent_yaml((), _BASE_URL) parsed = yaml.safe_load(result) assert parsed.get("agents") == [] or parsed.get("agents") is None