Autonomous_Data_Scientist / test_gradio.py
Megha Panicker
Resolve README.md conflict with HF Spaces metadata
c1b226b
#!/usr/bin/env python3
"""
Standalone tests for the Gradio interface.
Runs without launching the server or needing Ollama/Neon.
"""
import os
import sys
import unittest
from pathlib import Path
from unittest.mock import patch
# Setup path and env before imports
os.environ.setdefault("MPLCONFIGDIR", str(Path(__file__).parent / ".matplotlib"))
sys.path.insert(0, str(Path(__file__).parent))
import pandas as pd
from agent_runner import format_result_for_chat, safe_df
class TestAgentRunner(unittest.TestCase):
"""Tests for agent_runner helpers used by Gradio."""
def test_format_result_empty(self):
text, df, viz = format_result_for_chat({})
self.assertIn("No response data", text)
self.assertIsNone(df)
self.assertIsNone(viz)
def test_format_result_non_dict(self):
text, df, viz = format_result_for_chat("plain string")
self.assertEqual(text, "plain string")
self.assertIsNone(df)
self.assertIsNone(viz)
def test_format_result_with_final_answer_and_table(self):
result = {
"final_answer": "Here are the results.",
"query_result": [{"col_a": 1, "col_b": 2}, {"col_a": 3, "col_b": 4}],
}
text, df, viz = format_result_for_chat(result)
self.assertIn("Here are the results", text)
self.assertIn("**Results**", text)
self.assertIn("| col_a | col_b |", text)
self.assertIsInstance(df, pd.DataFrame)
self.assertEqual(len(df), 2)
self.assertIsNone(viz)
def test_format_result_with_sql_and_error(self):
result = {
"datasource": "sql",
"sql_query": "SELECT * FROM t",
"query_error": "table not found",
}
text, df, viz = format_result_for_chat(result)
self.assertIn("Agent Reasoning", text)
self.assertIn("SELECT * FROM t", text)
self.assertIn("table not found", text)
def test_safe_df(self):
out = safe_df([{"a": 1}, {"a": 2}])
self.assertIsInstance(out, pd.DataFrame)
self.assertEqual(list(out["a"]), [1, 2])
class TestGradioUI(unittest.TestCase):
"""Tests for Gradio UI handlers."""
def setUp(self):
# Import here to avoid loading Gradio/UI until needed
from app_gradio import chat_turn, clear_chat, approve_click, reject_click
self.chat_turn = chat_turn
self.clear_chat = clear_chat
self.approve_click = approve_click
self.reject_click = reject_click
def test_chat_turn_no_db_url(self):
"""When db_url is empty and no env fallback, returns config message without calling agent."""
with patch.dict(os.environ, {"NEON_DATABASE_URL": "", "DATABASE_URL": ""}, clear=False):
hist, tid, awaiting, pending, url, df, viz = self.chat_turn(
"What are sales?",
[],
"thread-123",
False,
None,
"", # empty db_url
"llama3.2",
)
self.assertEqual(len(hist), 1)
self.assertIn("Set Neon connection string", hist[0][1])
self.assertFalse(awaiting)
self.assertIsNone(pending)
self.assertIsNone(df)
self.assertIsNone(viz)
def test_clear_chat(self):
"""clear_chat returns fresh state and empty table/chart."""
out = self.clear_chat("old-thread")
self.assertEqual(len(out), 8)
hist, tid, awaiting, pending, msg, approval_upd, table, chart = out
self.assertEqual(hist, [])
self.assertNotEqual(tid, "old-thread")
self.assertFalse(awaiting)
self.assertIsNone(pending)
self.assertEqual(msg, "")
self.assertTrue(table.empty)
self.assertIsNone(chart)
def test_approve_click_no_pending(self):
"""approve_click with no pending result returns early."""
hist, awaiting, pending, df, viz = self.approve_click(
[("u", "a")], None, "tid", "postgres://...", "llama3.2"
)
self.assertEqual(len(hist), 1)
self.assertFalse(awaiting)
self.assertIsNone(df)
self.assertIsNone(viz)
def test_reject_click_no_pending(self):
"""reject_click with no pending result returns early."""
hist, awaiting, pending, df, viz = self.reject_click(
[("u", "a")], None, "tid", "postgres://...", "llama3.2"
)
self.assertEqual(len(hist), 1)
self.assertFalse(awaiting)
self.assertIsNone(df)
self.assertIsNone(viz)
def test_chat_turn_returns_sql_output_and_chart(self):
"""When agent returns query_result and chart, chat_turn must return df and viz. If not, tests fail."""
mock_result = {
"final_answer": "Here are sales by category.",
"query_result": [{"category": "A", "total": 100}, {"category": "B", "total": 200}],
"visualization_path": "/tmp/test_chart.png",
}
with patch("app_gradio.run_agent", return_value=(mock_result, False)):
with patch("agent_runner.Path") as mock_path_cls:
mock_path_cls.return_value.exists.return_value = True
hist, tid, awaiting, pending, url, df, viz_path = self.chat_turn(
"Sales by category", [], "tid", False, None, "postgres://x", "llama3.2"
)
self.assertFalse(df is None or df.empty, "SQL output must show up in table - df must not be empty")
self.assertIsNotNone(viz_path, "Chart must show up - viz_path must not be None")
def test_approve_click_returns_sql_output_and_chart(self):
"""When approve runs and agent returns query_result and chart, approve_click must return df and viz."""
mock_result = {
"final_answer": "Approved and executed.",
"query_result": [{"region": "North", "sales": 500}],
"visualization_path": "/tmp/approved_chart.png",
}
with patch("app_gradio.resume_agent", return_value=mock_result):
with patch("agent_runner.Path") as mock_path_cls:
mock_path_cls.return_value.exists.return_value = True
hist, awaiting, pending, df, viz_path = self.approve_click(
[("u", "a")], {"sql_query": "SELECT 1"}, "tid", "postgres://x", "llama3.2"
)
self.assertFalse(df is None or df.empty, "SQL output must show up after approve - df must not be empty")
self.assertIsNotNone(viz_path, "Chart must show up after approve - viz_path must not be None")
class TestGradioBlocks(unittest.TestCase):
"""Tests that the Gradio UI builds without error."""
def test_build_ui(self):
"""build_ui returns a Blocks instance."""
from app_gradio import build_ui
import gradio as gr
demo = build_ui()
self.assertIsInstance(demo, gr.Blocks)
def test_sample_questions_present(self):
"""Sample questions list is non-empty."""
from app_gradio import SAMPLE_QUESTIONS
self.assertGreater(len(SAMPLE_QUESTIONS), 0)
self.assertIn("Top 5 products by total sales", SAMPLE_QUESTIONS)
self.assertIn("How many PTO days do we get?", SAMPLE_QUESTIONS)
if __name__ == "__main__":
unittest.main(verbosity=2)