File size: 3,201 Bytes
a7ea8bc
 
 
 
 
 
 
 
 
 
 
 
bd5e8e1
a7ea8bc
 
 
 
 
 
 
 
bd5e8e1
 
 
 
 
a7ea8bc
bd5e8e1
a7ea8bc
 
 
bd5e8e1
 
 
 
 
 
 
a7ea8bc
 
 
 
 
 
 
 
 
 
bd5e8e1
 
 
 
 
 
 
 
 
a7ea8bc
 
 
bd5e8e1
a7ea8bc
 
bd5e8e1
a7ea8bc
 
bd5e8e1
a7ea8bc
bd5e8e1
a7ea8bc
 
 
 
 
 
 
 
 
 
bd5e8e1
 
 
 
 
 
 
a7ea8bc
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
"""Render-time smoke test using Streamlit's AppTest API.

This actually executes `app.main()` the way a real Streamlit session would,
so it catches errors that pure imports miss (missing i18n keys, session_state
crashes, render-time exceptions in module handlers).

We patch out the network-calling chat() function so we don't need an API key.
"""
from __future__ import annotations

import sys
import traceback
import types
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))

def _fake_chat(*args, **kwargs):
    return "(stubbed Claude reply for smoke test)"

def _fake_chat_structured(*args, **kwargs):
    # Return a valid empty-ish instance of whatever Pydantic schema is passed.
    schema = kwargs.get("schema")
    if schema is None and args:
        schema = args[0]
    try:
        return schema(overall_mood="neutral", distortions=[], summary="stubbed journal summary")
    except Exception:
        return None

# Stub backend.claude_client before anything in the module tree imports it, so
# the render test never depends on dotenv, provider SDKs, API keys, or network.
cc = types.ModuleType("backend.claude_client")
cc.chat = _fake_chat  # type: ignore[attr-defined]
cc.chat_structured = _fake_chat_structured  # type: ignore[attr-defined]
cc.get_active_provider_label = lambda: "Smoke-test LLM stub"  # type: ignore[attr-defined]
sys.modules["backend.claude_client"] = cc

try:
    from streamlit.testing.v1 import AppTest
except Exception as e:
    print("AppTest not available:", e)
    sys.exit(2)

print("=== Streamlit AppTest render ===")
failures = []

JOURNAL_SECTIONS = [
    ("journal", "Journal"),
    ("phq9", "PHQ-9"),
    ("gad7", "GAD-7"),
    ("checkin", "Daily check-in"),
    ("dashboard", "My patterns"),
]

def run_with_lang(lang_code: str, label: str, journal_section: str, section_label: str) -> None:
    at = AppTest.from_file(str(ROOT / "app.py"), default_timeout=30)
    # Pre-set the language in session_state so we exercise every label lookup path.
    at.session_state["saathi_language"] = lang_code
    at.session_state["cognitive_journal_section"] = journal_section
    at.run()
    if at.exception:
        print(f"  FAIL [{label} / {section_label}] rendered with exceptions:")
        for ex in at.exception:
            print(f"    - {ex.value if hasattr(ex, 'value') else ex}")
        failures.append(f"{label}:{section_label}")
    else:
        print(f"  OK   [{label} / {section_label}] rendered clean ({len(at.tabs)} tabs)")

for code, label in [
    ("en", "English"),
    ("hi", "Hindi"),
    ("bn", "Bengali"),
    ("ta", "Tamil"),
    ("te", "Telugu"),
    ("mr", "Marathi"),
    ("ur", "Urdu"),
]:
    for journal_section, section_label in JOURNAL_SECTIONS:
        try:
            run_with_lang(code, label, journal_section, section_label)
        except Exception as e:
            print(f"  FAIL [{label} / {section_label}] crashed: {e}")
            traceback.print_exc()
            failures.append(f"{label}:{section_label}")

print()
if failures:
    print(f"FAIL — {len(failures)} render errors: {failures}")
    sys.exit(1)
else:
    print("ALL RENDERS PASSED")
    sys.exit(0)