"""Tests for the NL-query Matrix surface: trigger detection + answer rendering (pure, no network).""" import os import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import query # noqa: E402 # ── parse_trigger ─────────────────────────────────────────────────────────────────────── def test_trigger_question_mark(): assert query.parse_trigger("?who are our top investors") == "who are our top investors" assert query.parse_trigger(" ? spaced out ") == "spaced out" def test_trigger_at_bot(): assert query.parse_trigger("@bot top 10 investors") == "top 10 investors" assert query.parse_trigger("@bot: top 10 investors") == "top 10 investors" # pill-style colon assert query.parse_trigger("@BOT spaced") == "spaced" # case-insensitive def test_trigger_slash_forms(): assert query.parse_trigger("/ask when did we last email Acme?") == "when did we last email Acme?" assert query.parse_trigger("/query top investors") == "top investors" assert query.parse_trigger("/q top investors") == "top investors" def test_trigger_bare_returns_empty_string(): # A bare trigger is matched (so we show help) but carries no question. assert query.parse_trigger("@bot") == "" assert query.parse_trigger("?") == "" def test_non_trigger_routes_to_intake(): assert query.parse_trigger("New investor: Acme — Jane ") is None # 'ask' as a note verb must NOT trigger (would collide with real intake notes). assert query.parse_trigger("Ask Jane to send the Q3 deck") is None assert query.parse_trigger("/asking for a friend") is None # needs a separator after /ask assert query.parse_trigger("") is None assert query.parse_trigger(" ") is None # ── render_answer ─────────────────────────────────────────────────────────────────────── def test_render_scalar_rows(): out = query.render_answer({ "intent": "top_investors_committed", "slots": {"limit": 2}, "summary": "Top 2 investor(s) by committed capital.", "columns": ["investor_name", "total_invested", "lead"], "rows": [{"investor_name": "Acme Capital", "total_invested": 5000000, "lead": "Grant"}, {"investor_name": "Beta Fund", "total_invested": 2500000, "lead": "Jonathan"}], "truncated": False}) assert "Top 2 investor(s)" in out assert "**Acme Capital**" in out assert "$5,000,000" in out # money formatting assert "read as: top_investors_committed" in out # interpretation footer def test_render_nested_contacts_and_commitments(): out = query.render_answer({ "intent": "investor_lookup", "slots": {"name": "Acme"}, "summary": '1 investor(s) matching "Acme".', "columns": ["investor_name", "lead", "total_invested", "graveyard", "contacts", "commitments"], "rows": [{"investor_name": "Acme Capital", "lead": "Grant", "total_invested": 5000000, "graveyard": 0, "contacts": [{"full_name": "Jane Doe", "email": "jane@acme.com", "title": "GP", "city": "Austin", "state": "TX", "country": ""}], "commitments": [{"fund_name": "Fund I", "amount": 5000000}]}], "truncated": False}) assert "Jane Doe " in out assert "Fund I: $5,000,000" in out assert "graveyard" not in out # 0-valued flag column suppressed def test_render_flag_when_set(): out = query.render_answer({ "intent": "investors_follow_up", "slots": {}, "summary": "1 investor(s) with an open follow-up reminder.", "columns": ["investor_name", "title", "due_date", "status", "overdue"], "rows": [{"investor_name": "Acme", "title": "Send deck", "due_date": "2026-01-01", "status": "open", "overdue": 1}]}) assert "⚠️ overdue" in out assert "2026-01-01" in out # date truncated to YYYY-MM-DD def test_render_no_rows(): out = query.render_answer({"intent": "investors_by_city", "slots": {"city": "Nowhere"}, "summary": '0 investor contact(s) in "Nowhere".', "columns": [], "rows": []}) assert "no matching" in out.lower() def test_render_overflow_note(): rows = [{"investor_name": f"Inv {i}", "total_invested": i} for i in range(query.MAX_DISPLAY_ROWS + 5)] out = query.render_answer({"intent": "top_investors_committed", "slots": {}, "summary": "many", "columns": ["investor_name", "total_invested"], "rows": rows}) assert "+5 more not shown" in out def test_render_errors(): assert "couldn't map" in query.render_answer({"error": "no_match", "question": "huh"}).lower() assert "unreachable" in query.render_answer({"error": "model_unavailable"}).lower() assert "failed" in query.render_answer({"error": "query_failed", "detail": "boom"}).lower() assert "bad_slot" in query.render_answer({"error": "bad_slot", "detail": "x"}) if __name__ == "__main__": fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)] for fn in fns: fn() print(f"ok {fn.__name__}") print(f"\n{len(fns)} passed")