why-agent / tests /test_streamlit_app.py
MapoTofu9's picture
deploy: HF Spaces
5d30bdc
Raw
History Blame Contribute Delete
8.15 kB
"""Tests for streamlit_app.py business logic and UI smoke."""
from __future__ import annotations
from unittest.mock import MagicMock, patch
# ---------------------------------------------------------------------------
# format_report
# ---------------------------------------------------------------------------
class TestFormatReport:
def test_renders_text(self):
from streamlit_app import format_report
report = {
"text": "Root cause: platform outage at 3 PM.",
"evidence_count": 5,
"hypotheses": [],
"error": None,
}
out = format_report(report)
assert "Root cause: platform outage at 3 PM." in out
def test_includes_evidence_count(self):
from streamlit_app import format_report
report = {"text": "analysis", "evidence_count": 7, "hypotheses": [], "error": None}
out = format_report(report)
assert "7" in out
def test_includes_hypothesis_count(self):
from streamlit_app import format_report
report = {
"text": "analysis",
"evidence_count": 3,
"hypotheses": [{"id": "H1"}, {"id": "H2"}],
"error": None,
}
out = format_report(report)
assert "2" in out
def test_shows_error_note_when_present(self):
from streamlit_app import format_report
report = {
"text": "partial analysis",
"evidence_count": 2,
"hypotheses": [],
"error": "Max critique retries reached.",
}
out = format_report(report)
assert "Max critique retries reached." in out
def test_no_error_note_when_absent(self):
from streamlit_app import format_report
report = {"text": "clean", "evidence_count": 1, "hypotheses": [], "error": None}
out = format_report(report)
assert "Note:" not in out
# ---------------------------------------------------------------------------
# format_evidence
# ---------------------------------------------------------------------------
class TestFormatEvidence:
def test_empty_returns_empty_string(self):
from streamlit_app import format_evidence
assert format_evidence([]) == ""
def test_includes_tool_name(self):
from streamlit_app import format_evidence
evidence = [
{
"phase": "plan",
"tool_name": "inspect_schema",
"args": {},
"output": {"tables": ["t1"]},
}
]
out = format_evidence(evidence)
assert "inspect_schema" in out
def test_includes_phase(self):
from streamlit_app import format_evidence
evidence = [
{
"phase": "decompose",
"tool_name": "compare_periods",
"args": {},
"output": {"abs_delta": 10},
}
]
out = format_evidence(evidence)
assert "decompose" in out
def test_marks_error_entries(self):
from streamlit_app import format_evidence
evidence = [
{"phase": "drill", "tool_name": "run_sql", "args": {}, "output": {"error": "bad query"}}
]
out = format_evidence(evidence)
assert "⚠️" in out
def test_null_error_field_not_flagged(self):
# RunSqlOutput always serializes error: null — the null value must NOT trigger the error tag
from streamlit_app import format_evidence
evidence = [
{
"phase": "plan",
"tool_name": "run_sql",
"args": {},
"output": {"rows": [], "error": None},
}
]
out = format_evidence(evidence)
assert "⚠️" not in out
def test_multiple_entries_numbered(self):
from streamlit_app import format_evidence
evidence = [
{"phase": "plan", "tool_name": "inspect_schema", "args": {}, "output": {}},
{"phase": "decompose", "tool_name": "run_sql", "args": {}, "output": {}},
]
out = format_evidence(evidence)
assert "[1]" in out
assert "[2]" in out
# ---------------------------------------------------------------------------
# run_investigation
# ---------------------------------------------------------------------------
def _mock_graph_stream(chunks: list[dict]):
"""Return a mock graph whose .stream() yields the given chunks."""
mock_graph = MagicMock()
mock_graph.stream.return_value = iter(chunks)
return mock_graph
def _mock_status():
"""Return a mock st.status context manager."""
ctx = MagicMock()
ctx.__enter__ = MagicMock(return_value=ctx)
ctx.__exit__ = MagicMock(return_value=False)
return ctx
class TestRunInvestigation:
def test_returns_report_on_success(self):
from streamlit_app import run_investigation
fake_report = {
"user_question": "Why?",
"text": "Because X.",
"hypotheses": [],
"evidence_count": 2,
"error": None,
}
chunks = [{"final_report": fake_report, "evidence": [], "phase": "plan"}]
with (
patch("streamlit_app.get_graph", return_value=_mock_graph_stream(chunks)),
patch("streamlit_app.st.status", return_value=_mock_status()),
):
report, evidence, err = run_investigation("Why?")
assert report == fake_report
assert err == ""
def test_returns_error_string_when_no_report(self):
from streamlit_app import run_investigation
chunks = [{"final_report": None, "error": "graph blew up", "evidence": [], "phase": "plan"}]
with (
patch("streamlit_app.get_graph", return_value=_mock_graph_stream(chunks)),
patch("streamlit_app.st.status", return_value=_mock_status()),
):
report, evidence, err = run_investigation("Why?")
assert report is None
assert "graph blew up" in err
def test_returns_error_string_on_exception(self):
from streamlit_app import run_investigation
mock_graph = MagicMock()
mock_graph.stream.side_effect = RuntimeError("connection refused")
with (
patch("streamlit_app.get_graph", return_value=mock_graph),
patch("streamlit_app.st.status", return_value=_mock_status()),
):
report, evidence, err = run_investigation("Why?")
assert report is None
assert "connection refused" in err
def test_passes_evidence_from_state(self):
from streamlit_app import run_investigation
ev = [{"phase": "plan", "tool_name": "inspect_schema", "args": {}, "output": {}}]
chunks = [
{
"final_report": {
"text": "ok",
"hypotheses": [],
"evidence_count": 1,
"error": None,
},
"evidence": ev,
"phase": "plan",
}
]
with (
patch("streamlit_app.get_graph", return_value=_mock_graph_stream(chunks)),
patch("streamlit_app.st.status", return_value=_mock_status()),
):
_, evidence, _ = run_investigation("Why?")
assert len(evidence) == 1
assert evidence[0]["tool_name"] == "inspect_schema"
# ---------------------------------------------------------------------------
# AppTest smoke — just verify the app renders without crashing
# ---------------------------------------------------------------------------
class TestAppSmoke:
def test_app_renders_without_exception(self):
from streamlit.testing.v1 import AppTest
at = AppTest.from_file("streamlit_app.py", default_timeout=10)
at.run()
assert not at.exception, f"App raised: {at.exception}"
def test_app_has_chat_input(self):
from streamlit.testing.v1 import AppTest
at = AppTest.from_file("streamlit_app.py", default_timeout=10)
at.run()
assert len(at.chat_input) >= 1