Spaces:
Sleeping
Sleeping
| """Lightweight smoke tests for app.py. | |
| Building the Gradio Blocks doesn't require the system to be initialised | |
| (no API keys), so we can verify the UI compiles cleanly and the per-call | |
| helpers work without ever launching a server. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import pytest | |
| def test_app_imports() -> None: | |
| import app # noqa: F401 | |
| def test_build_demo_compiles() -> None: | |
| """Calling build_demo() must not raise — catches Gradio API drift.""" | |
| pytest.importorskip("gradio") | |
| from app import build_demo | |
| demo = build_demo() | |
| assert demo is not None | |
| def test_build_user_profile_round_trip() -> None: | |
| from app import build_user_profile | |
| payload = build_user_profile( | |
| name="Test", | |
| age=30, | |
| sex="male", | |
| height_cm=175, | |
| weight_kg=72, | |
| activity="moderately active", | |
| goal="maintain weight", | |
| allergies="peanut, shrimp", | |
| dislikes="okra", | |
| country="Egypt", | |
| conditions="hypertension", | |
| medications="lisinopril", | |
| lab_results="HbA1c 6.1%, LDL 145 mg/dL", | |
| ) | |
| # Round-trip via JSON to mirror what the hidden Textbox carries. | |
| serialised = json.dumps(payload) | |
| parsed = json.loads(serialised) | |
| assert parsed["user_profile"]["name"] == "Test" | |
| assert parsed["user_profile"]["allergies"] == ["peanut", "shrimp"] | |
| assert parsed["medical_history"]["conditions"] == ["hypertension"] | |
| assert parsed["medical_history"]["lab_results"] == "HbA1c 6.1%, LDL 145 mg/dL" | |
| def test_render_metrics_is_markdown() -> None: | |
| from app import _render_metrics | |
| snap = { | |
| "agents": {"Coach": {"calls": 1, "total_seconds": 0.5, "errors": 0, "last_seconds": 0.5}}, | |
| "tools": {"QuantitiesFinder": {"calls": 2, "total_seconds": 0.1, "errors": 0, "last_seconds": 0.05}}, | |
| "parsing": {"native": 5, "fallback": 0, "failure": 0, "by_model": {}}, | |
| } | |
| md = _render_metrics(snap) | |
| assert "Coach" in md | |
| assert "QuantitiesFinder" in md | |
| assert "native=5" in md | |
| def test_session_state_default_shape() -> None: | |
| from app import SessionState | |
| s = SessionState() | |
| assert s.initialised is False | |
| assert s.memory == { | |
| "user_profile": {}, | |
| "medical_history": {}, | |
| "flags_and_assessments": {}, | |
| "plans": {}, | |
| } | |
| assert s.conversation_history == [] | |
| def test_chat_handles_uninitialised_system() -> None: | |
| """Calling chat() before init must not crash; returns a friendly error. | |
| ``chat`` is a generator that streams (chat, trace, metrics, session, | |
| progress) tuples — drain to the last yield and inspect the final | |
| chatbot history. | |
| """ | |
| pytest.importorskip("gradio") | |
| from app import SessionState, chat | |
| # Make sure mealgraph.APP is None so we hit the guard. | |
| import mealgraph | |
| mealgraph.APP = None | |
| last = None | |
| for last in chat( | |
| user_message="hi", history=[], session=SessionState(), profile_json="" | |
| ): | |
| pass | |
| assert last is not None | |
| history, _log, _metrics, _session, progress = last | |
| # messages-format chatbot: list of {role, content} dicts | |
| assert history[-1]["role"] == "assistant" | |
| assert history[-1]["content"].startswith("❌ System not initialised") | |
| # Progress panel should explain the early exit, not be empty. | |
| assert "system not initialised" in progress.lower() | |
| def test_request_stop_with_no_active_run() -> None: | |
| """Pressing Stop with no in-flight run is a friendly no-op.""" | |
| pytest.importorskip("gradio") | |
| from app import request_stop, _set_current_bus | |
| # Make sure no leftover bus from another test is hanging around. | |
| _set_current_bus(None) | |
| msg = request_stop() | |
| assert "No run is in progress" in msg | |
| def test_progress_bus_check_stop_raises_after_set() -> None: | |
| """ProgressBus.check_stop is the gate every agent / tool wrapper hits | |
| on entry. Once the stop flag is flipped, the next check raises so the | |
| in-flight workflow unwinds at the next agent / tool boundary.""" | |
| pytest.importorskip("gradio") | |
| from app import ProgressBus, StopRequested | |
| bus = ProgressBus() | |
| # Fresh bus: no raise. | |
| bus.check_stop() | |
| bus.stop_event.set() | |
| with pytest.raises(StopRequested): | |
| bus.check_stop() | |