from __future__ import annotations import pytest from pydantic import ValidationError from math_env import models from math_env.server.app import create_math_app def test_app_exposes_native_tools() -> None: assert "/tools" in {route.path for route in create_math_app(1).routes} def test_native_tool_spec_is_generated_from_model_facing_action_fields() -> None: assert hasattr(models, "MathSubmitAnswer") assert hasattr(models, "native_tool_specs") assert models.native_tool_specs() == ( { "name": "submit_answer", "description": "Submit the final answer for the current MATH problem.", "input_schema": models.MathSubmitAnswer.model_json_schema(), }, ) schema = models.native_tool_specs()[0]["input_schema"] assert schema["additionalProperties"] is False assert set(schema["properties"]) == {"answer"} assert "metadata" not in schema["properties"] assert "oneOf" not in schema assert "discriminator" not in schema def test_native_tool_action_validates_arguments_and_builds_wire_action() -> None: action = models.native_tool_action("submit_answer", {"answer": "42"}) assert isinstance(action, models.MathAction) assert action.model_dump() == {"answer": "42", "metadata": {}} with pytest.raises(ValidationError): models.native_tool_action( "submit_answer", {"answer": "42", "metadata": {"retry": 1}} ) def test_action_accepts_the_uniform_native_call_envelope() -> None: action = models.MathAction.model_validate( {"name": "submit_answer", "arguments": {"answer": "42"}} ) assert action.answer == "42" assert action.metadata == {} with pytest.raises(ValidationError): models.MathAction.model_validate( { "name": "submit_answer", "arguments": {"answer": "42"}, "tool": "submit_answer", } ) def test_action_keeps_accepting_its_direct_payload() -> None: assert models.MathAction.model_validate({"answer": "42"}).answer == "42" def test_native_tool_action_rejects_unknown_tool() -> None: with pytest.raises(ValueError, match="unknown MATH native tool"): models.native_tool_action("act", {"answer": "42"})