import pytest
from hackathon_advisor.dashboard_chat_contracts import (
CHAT_TOOL_SPECS,
chat_tool_schemas,
heuristic_chat_call,
parse_native_tool_call,
resolve_chat_tool_call,
strip_function_blocks,
)
from hackathon_advisor.tool_contracts import ToolContractError
def test_chat_tool_schemas_are_openai_style() -> None:
schemas = chat_tool_schemas()
assert len(schemas) == len(CHAT_TOOL_SPECS) == 9
for schema in schemas:
assert schema["type"] == "function"
assert schema["function"]["parameters"]["type"] == "object"
def test_parse_native_tool_call_reads_params() -> None:
call = parse_native_tool_call(
'voice agents'
)
assert call.name == "search_projects"
assert call.arguments == {"query": "voice agents"}
def test_parse_native_tool_call_handles_cdata_and_surrounding_prose() -> None:
call = parse_native_tool_call(
'Let me look that up.\n'
" Done."
)
assert call.name == "show_cluster"
assert call.arguments == {"label": "Voice / Chatbot & ASR"}
def test_parse_native_tool_call_without_params() -> None:
call = parse_native_tool_call('')
assert call.name == "list_quests"
assert call.arguments == {}
def test_parse_native_tool_call_rejects_garbage() -> None:
with pytest.raises(ToolContractError):
parse_native_tool_call('unclosed')
with pytest.raises(ToolContractError):
parse_native_tool_call("just prose, no call")
with pytest.raises(ToolContractError):
parse_native_tool_call('missing name')
def test_resolve_marks_plain_prose_as_none() -> None:
resolution = resolve_chat_tool_call("Hello! Ask me about the atlas.", fallback_query="hello")
assert resolution.status == "none"
assert resolution.call is None
assert resolution.errors == ()
def test_resolve_accepts_valid_call() -> None:
resolution = resolve_chat_tool_call(
'Tiny Titan',
fallback_query="tiny titan",
)
assert resolution.status == "valid"
assert resolution.call is not None
assert resolution.call.name == "show_quest"
def test_resolve_defaults_unknown_tool_through_intent_router() -> None:
resolution = resolve_chat_tool_call(
'',
fallback_query="who completed the most quests?",
)
assert resolution.status == "defaulted"
assert resolution.call is not None
assert resolution.call.name == "top_projects_by_quests"
assert resolution.errors
def test_resolve_defaults_malformed_xml_to_search() -> None:
resolution = resolve_chat_tool_call(
'a < b',
fallback_query="projects about voice",
)
assert resolution.status == "defaulted"
assert resolution.call is not None
assert resolution.call.name == "search_projects"
assert resolution.call.arguments["query"] == "projects about voice"
def test_resolve_rejects_missing_required_argument() -> None:
resolution = resolve_chat_tool_call(
'',
fallback_query="show me the voice cluster",
)
assert resolution.status == "defaulted"
assert resolution.errors
def test_heuristic_chat_call_routes_intents() -> None:
assert heuristic_chat_call("who completed the most quests").name == "top_projects_by_quests"
assert heuristic_chat_call("what clusters exist").name == "list_clusters"
assert heuristic_chat_call("list the quests please").name == "list_quests"
assert heuristic_chat_call("what changed recently").name == "recent_activity"
assert heuristic_chat_call("what is everyone building").name == "atlas_overview"
assert heuristic_chat_call("knitting helpers").name == "search_projects"
assert heuristic_chat_call("").name == "atlas_overview"
def test_data_intent_call_separates_detail_from_listing() -> None:
from hackathon_advisor.dashboard_chat_contracts import data_intent_call
detail = data_intent_call("what is in the Dream / Oracle cluster?")
assert detail is not None and detail.name == "show_cluster"
assert "Dream / Oracle" in detail.arguments["label"]
quest_detail = data_intent_call("tell me about the Tiny Titan quest")
assert quest_detail is not None and quest_detail.name == "show_quest"
assert data_intent_call("what clusters exist").name == "list_clusters"
assert data_intent_call("find projects about voice").name == "search_projects"
assert data_intent_call("how many voice apps").name == "search_projects"
assert data_intent_call("what is the coolest project?").name == "atlas_overview"
assert data_intent_call("which project is most liked").name == "atlas_overview"
assert data_intent_call("hello there") is None
assert data_intent_call("thanks!") is None
def test_data_intent_call_routes_project_reading() -> None:
from hackathon_advisor.dashboard_chat_contracts import data_intent_call
readme = data_intent_call("read the readme of Jawbreaker")
assert readme is not None and readme.name == "show_project"
how = data_intent_call("how does Jawbreaker work?")
assert how is not None and how.name == "show_project"
about = data_intent_call("tell me about Jawbreaker")
assert about is not None and about.name == "show_project"
assert about.arguments["project"] == "tell me about Jawbreaker"
# cluster/quest detail intents keep their own tools
assert data_intent_call("tell me about the Tiny Titan quest").name == "show_quest"
assert data_intent_call("what is in the Dream / Oracle cluster?").name == "show_cluster"
def test_smalltalk_intent_only_matches_greetings_and_followups() -> None:
from hackathon_advisor.dashboard_chat_contracts import smalltalk_intent
assert smalltalk_intent("hello!")
assert smalltalk_intent("hey there")
assert smalltalk_intent("thanks")
assert smalltalk_intent("why")
assert smalltalk_intent("are you sure?")
assert smalltalk_intent("who are you")
assert smalltalk_intent("")
assert not smalltalk_intent("how many voice apps")
assert not smalltalk_intent("what is the coolest project?")
assert not smalltalk_intent("voice agents for elderly care")
assert not smalltalk_intent("knitting helpers")
def test_strip_function_blocks_removes_stray_calls() -> None:
text = 'Here you go. v The map shows it.'
assert strip_function_blocks(text) == "Here you go. The map shows it.".strip()
assert strip_function_blocks("plain prose") == "plain prose"