import pytest from hackathon_advisor.tool_contracts import ( ToolCall, ToolContractError, parse_xml_tool_call, resolve_tool_call, tool_schemas, validate_tool_call, ) def test_tool_schemas_are_model_ready() -> None: schemas = tool_schemas() assert len(schemas) >= 8 assert schemas[0]["type"] == "function" assert {schema["function"]["name"] for schema in schemas} >= { "search_projects", "find_whitespace", "save_idea", "score_idea", "make_plan", "set_goals", } schema_text = str(schemas) assert "set_target" not in schema_text assert "side_quests" not in schema_text assert "prize" not in schema_text.lower() assert "badge" not in schema_text.lower() assert "Build Small" not in schema_text assert "hackathon" not in schema_text.lower() def test_parse_and_validate_minicpm_xml_tool_call() -> None: call = parse_xml_tool_call('{"query":"lullaby audio"}') assert validate_tool_call(call) == ToolCall("search_projects", {"query": "lullaby audio"}) def test_validate_rejects_unknown_tool() -> None: with pytest.raises(ToolContractError, match="unknown tool"): validate_tool_call(ToolCall("invent_project", {})) def test_validate_rejects_bad_argument_type() -> None: with pytest.raises(ToolContractError, match="search_projects.query must be string"): validate_tool_call(ToolCall("search_projects", {"query": 47})) def test_validate_rejects_extra_arguments() -> None: with pytest.raises(ToolContractError, match="unexpected arguments"): validate_tool_call(ToolCall("find_whitespace", {"query": "unused"})) def test_resolve_defaults_to_search_when_output_is_broken() -> None: resolution = resolve_tool_call(" None: resolution = resolve_tool_call("no function here") assert resolution.status == "defaulted" assert resolution.call == ToolCall("find_whitespace", {})