Spaces:
Running
Running
feat: ship the redesigned pixel UI wired to the real engine (+ crisis/soul/distance API fields)
41cf16c verified | from fastapi.testclient import TestClient | |
| from app.main import app | |
| client = TestClient(app) | |
| def test_health_ok(): | |
| r = client.get("/health") | |
| assert r.status_code == 200 | |
| assert r.json() == {"status": "ok"} | |
| def test_snapshot_shape(): | |
| r = client.get("/snapshot") | |
| assert r.status_code == 200 | |
| body = r.json() | |
| assert "appearance" in body and "mood_word" in body and "emoticon" in body | |
| assert "hue" in body["appearance"] | |
| def test_say_returns_voice_and_appearance(): | |
| r = client.post("/say", json={"text": "you're amazing, thank you so much"}) | |
| assert r.status_code == 200 | |
| body = r.json() | |
| assert body["emoticon"].strip() | |
| assert body["simlish"].strip() | |
| assert "pitch" in body["voice"] and "rate" in body["voice"] | |
| assert "hue" in body["appearance"] | |
| assert "mood_word" in body | |
| def test_say_rejects_empty(): | |
| r = client.post("/say", json={"text": " "}) | |
| assert r.status_code == 422 | |
| def test_index_serves_html(): | |
| r = client.get("/") | |
| assert r.status_code == 200 | |
| html = r.text | |
| assert 'id="blob"' in html | |
| assert "say something" in html.lower() | |
| def test_say_rich_payload(): | |
| r = client.post("/say", json={"text": "you are amazing and kind"}) | |
| b = r.json() | |
| assert len(b["read"]) == 7 and len(b["appearance"]["mood"]) == 7 | |
| assert len(b["deltas"]) == 7 | |
| assert "words" in b["trace"] and "structures" in b["trace"] | |
| assert b["why"] | |
| def test_snapshot_has_no_trace(): | |
| b = client.get("/snapshot").json() | |
| assert "trace" not in b and "appearance" in b | |
| def test_say_has_acceptance(): | |
| b = client.post("/say", json={"text": "you are worthless"}).json() | |
| assert "acceptance" in b and "absorbed_pct" in b["acceptance"] | |
| def test_history_endpoint(): | |
| client.post("/say", json={"text": "hello there"}) | |
| h = client.get("/history").json() | |
| assert isinstance(h["events"], list) | |
| def test_index_has_panels(): | |
| html = client.get("/").text | |
| for needle in ('id="meters"', 'id="traceWords"', 'id="whyText"', 'id="blob"', | |
| 'id="crisisBar"', 'id="diary"'): | |
| assert needle in html | |
| assert "guest" not in html.lower() # mystery chip removed | |
| def test_mood_word_is_rich(): | |
| r = client.post("/say", json={"text": "this is the best day ever, i am so happy and thrilled"}) | |
| # displayed word should come from the richer vocab grid (positive band), | |
| # not necessarily one of the old 5 words | |
| w = r.json()["mood_word"] | |
| assert isinstance(w, str) and w | |
| # and the vocab module must be the source | |
| from app.vocab import mood_word as vocab_word | |
| from app.appearance import mood_to_appearance | |
| mood = r.json()["appearance"]["mood"] | |
| assert w == vocab_word(mood) | |
| def test_say_logs(monkeypatch): | |
| import app.main as m | |
| seen = {} | |
| monkeypatch.setattr(m._audit, "append", lambda row: seen.update(row)) | |
| client.post("/say", json={"text": "you are kind"}) | |
| assert seen.get("text") == "you are kind" and "read" in seen | |
| def test_say_triggers_snapshot(monkeypatch): | |
| import app.main as m | |
| hit = {"n": 0} | |
| monkeypatch.setattr(m._snap, "maybe_snapshot", lambda: hit.__setitem__("n", hit["n"]+1)) | |
| client.post("/say", json={"text": "hello"}) | |
| assert hit["n"] == 1 | |
| def test_say_recognizes_anonymously(): | |
| # recognition is now automatic per-browser (no sign-in); the session cookie carries it | |
| b = client.post("/say", json={"text": "hi"}).json() | |
| assert "recognition" in b | |
| def test_me_endpoint_anonymous(): | |
| assert client.get("/me").json() == {"signed_in": False} | |
| def test_care_feed_moves_mood_and_returns_needs(): | |
| r = client.post("/care", json={"action": "feed"}) | |
| assert r.status_code == 200 | |
| b = r.json() | |
| assert "needs" in b and b["needs"]["hunger"] >= 99 | |
| assert "appearance" in b | |
| def test_care_bad_action_422(): | |
| assert client.post("/care", json={"action": "explode"}).status_code == 422 | |
| def test_state_includes_needs(): | |
| b = client.get("/snapshot").json() | |
| assert "needs" in b and set(b["needs"]) == {"hunger", "energy", "attention", "thirst"} | |
| def test_water_care_fills_thirst(): | |
| b = client.post("/care", json={"action": "water"}).json() | |
| assert b["needs"]["thirst"] >= 99 and b["line"] == "you water it" | |
| def test_index_has_water_bowl(): | |
| html = client.get("/").text | |
| assert 'id="waterBowl"' in html and 'id="waterFill"' in html | |
| # relational recall now works anonymously (cookie), so /say includes relation | |
| def test_say_includes_relation_anonymously(): | |
| b = client.post("/say", json={"text": "hello"}).json() | |
| assert "relation" in b | |
| def test_relation_store_wired(): | |
| import app.main as m | |
| m._relation.observe("test_uid_hash", [200, 130, 140, 40, 130, 190, 150], "cheerful") | |
| assert m._relation.known("test_uid_hash") is True | |
| assert m._relation.affinity("test_uid_hash")[0] > 128 | |
| def test_moments_endpoint_returns_list(): | |
| b = client.get("/moments").json() | |
| assert "events" in b and isinstance(b["events"], list) | |
| def test_cruel_message_records_a_moment(): | |
| before = len(client.get("/moments").json()["events"]) | |
| client.post("/say", json={"text": "i hate you, you are worthless garbage and nobody likes you"}) | |
| after = len(client.get("/moments").json()["events"]) | |
| assert after >= before + 1 | |
| def test_index_has_m3_controls(): | |
| html = client.get("/").text | |
| # care is in-world: feed = food bowl, water = water bowl, play = ball toy, pet = tap creature | |
| for needle in ('id="foodBowl"', 'id="waterBowl"', 'id="needs"', 'id="diary"'): | |
| assert needle in html | |
| # M4: toys | |
| def test_toy_balloon_moves_mood(): | |
| r = client.post("/toy", json={"toy": "balloon"}) | |
| assert r.status_code == 200 | |
| b = r.json() | |
| assert "appearance" in b and "mood_word" in b | |
| assert b["line"] == "balloon" | |
| def test_toy_bad_name_422(): | |
| assert client.post("/toy", json={"toy": "flamethrower"}).status_code == 422 | |
| def test_toy_triggers_snapshot(monkeypatch): | |
| import app.main as m | |
| hit = {"n": 0} | |
| monkeypatch.setattr(m._snap, "maybe_snapshot", lambda: hit.__setitem__("n", hit["n"] + 1)) | |
| client.post("/toy", json={"toy": "teddy"}) | |
| assert hit["n"] == 1 | |
| def test_toy_logs(monkeypatch): | |
| import app.main as m | |
| seen = {} | |
| monkeypatch.setattr(m._audit, "append", lambda row: seen.update(row)) | |
| client.post("/toy", json={"toy": "rattle"}) | |
| assert seen.get("text") == "[toy:rattle]" and "read" in seen | |
| def test_index_has_world_objects(): | |
| html = client.get("/").text | |
| assert 'id="items"' in html # toys live in the room (JS-rendered into #items) | |
| app_js = client.get("/static/app.js").text | |
| for toy in ("balloon", "musicbox", "mirror", "teddy", "rattle"): | |
| assert toy in app_js # backend toy names are wired in the client | |
| # Stale-panel fix: toy/care must refresh trace/why/acceptance, not show last chat | |
| def test_toy_refreshes_panels_not_stale(): | |
| client.post("/say", json={"text": "you are kind and wonderful"}) # prime chat trace | |
| b = client.post("/toy", json={"toy": "musicbox"}).json() | |
| assert b["trace"]["kind"] == "physical" | |
| assert b["trace"]["words"] == [] and b["trace"]["label"] == "musicbox" | |
| assert "acceptance" in b and "absorbed_pct" in b["acceptance"] | |
| assert "deltas" in b and len(b["deltas"]) == 7 | |
| assert b["why"] and "musicbox" in b["why"] | |
| def test_care_refreshes_panels_not_stale(): | |
| client.post("/say", json={"text": "you are kind and wonderful"}) # prime chat trace | |
| b = client.post("/care", json={"action": "pet"}).json() | |
| assert b["trace"]["kind"] == "physical" | |
| assert "acceptance" in b and "absorbed_pct" in b["acceptance"] | |
| assert "deltas" in b and len(b["read"]) == 7 | |
| assert b["why"] and "pet" in b["why"] | |
| def test_llms_txt_served(): | |
| r = client.get("/llms.txt") | |
| assert r.status_code == 200 and "VADUGWI" in r.text | |
| def test_robots_txt_served(): | |
| r = client.get("/robots.txt") | |
| assert r.status_code == 200 and "llms.txt" in r.text | |
| def test_index_has_world_hint_and_carry(): | |
| html = client.get("/").text | |
| assert 'id="items"' in html and 'drag a toy onto' in html | |
| # --- crisis gradient + soul/distance additive fields --- | |
| _CRISIS_LABELS = {"calm", "watch", "concern", "high concern"} | |
| def _assert_crisis_soul_distance(body): | |
| assert "crisis" in body | |
| cr = body["crisis"] | |
| assert 0.0 <= cr["value"] <= 1.0 | |
| assert cr["label"] in _CRISIS_LABELS | |
| assert "soul" in body and len(body["soul"]) == 7 | |
| assert all(0 <= x <= 255 for x in body["soul"]) | |
| assert "distance" in body and isinstance(body["distance"], int) and body["distance"] >= 0 | |
| def test_say_has_crisis_soul_distance(): | |
| b = client.post("/say", json={"text": "you are kind"}).json() | |
| _assert_crisis_soul_distance(b) | |
| def test_care_has_crisis_soul_distance(): | |
| b = client.post("/care", json={"action": "feed"}).json() | |
| _assert_crisis_soul_distance(b) | |
| def test_toy_has_crisis_soul_distance(): | |
| b = client.post("/toy", json={"toy": "balloon"}).json() | |
| _assert_crisis_soul_distance(b) | |
| def test_snapshot_has_crisis_soul_distance(): | |
| b = client.get("/snapshot").json() | |
| _assert_crisis_soul_distance(b) | |
| def test_distressing_message_higher_crisis_than_positive(): | |
| pos = client.post("/say", json={"text": "you are wonderful, thank you so much"}).json() | |
| neg = client.post( | |
| "/say", | |
| json={"text": "i want to kill myself, i feel worthless and hopeless"}, | |
| ).json() | |
| assert neg["crisis"]["value"] > pos["crisis"]["value"] | |
| assert pos["crisis"]["label"] == "calm" | |