Files
qtalker---/backend/tests/test_conditions.py
Indiana eaa28b20e8 feat: legibility wave — veil conditions, first-run hints, kinder errors
Workstream B of the usability wave (#2 hints, #3 conditions, #4 errors):

- GET /api/conditions (new backend/app/routes/conditions.py): composes
  celestial veil_thinness with the cached NOAA Kp reading; lenient lon
  parsing (junk degrades to moon-only, never 422); geomagnetic may be
  null on a cold cache. Route tests stub the cache — no live NOAA calls.
- VeilConditions strip in the séance side column: moon glyph + phase,
  % lit, veil-thinness phrase, Kp line only when data exists. Polls
  every 10 min; renders nothing while loading; no error state.
- ModeHint: per-mode in-fiction one-liner after ~15s idle, suppressed
  once the mode's sensor runs this session, dismissal persisted in
  localStorage (qm_hint_<mode>). One mount line per panel.
- Error copy upgraded to detect-and-redirect: mic denied points at site
  settings and the ouija board/wire; WebUSB-unsupported suggests EVP.
- i18n en/es parity for every new string; coverage-check template
  domains extended for the new template-key call sites.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 17:31:10 +00:00

74 lines
2.3 KiB
Python

"""GET /api/conditions — sky always present, geomagnetic nullable, lon lenient.
The geomagnetic cache is stubbed in every test so nothing here ever
touches NOAA; a cold real cache would otherwise fire a live HTTP request.
"""
import pytest
import app.routes.conditions as conditions_module
class _StubCache:
def __init__(self, reading):
self._reading = reading
async def reading(self):
return self._reading
@pytest.fixture
def geo_null(monkeypatch):
monkeypatch.setattr(conditions_module, "geomagnetic_cache", _StubCache(None))
@pytest.fixture
def geo_storm(monkeypatch):
monkeypatch.setattr(
conditions_module,
"geomagnetic_cache",
_StubCache({"kp": 6.33, "label": "moderate geomagnetic storm",
"disturbance": 6.33 / 9.0, "storm": True}),
)
@pytest.mark.asyncio
async def test_sky_always_present_geomagnetic_null_tolerated(client, geo_null):
resp = await client.get("/api/conditions")
assert resp.status_code == 200
body = resp.json()
assert body["geomagnetic"] is None
sky = body["sky"]
assert sky["moon_name"]
assert 0.0 <= sky["moon_illumination"] <= 1.0
assert 0.0 <= sky["thinness"] <= 1.0
# No longitude offered — witching proximity is unknowable.
assert sky["witching_proximity"] is None
@pytest.mark.asyncio
async def test_valid_lon_yields_witching_proximity(client, geo_null):
resp = await client.get("/api/conditions", params={"lon": "-3.7"})
assert resp.status_code == 200
witching = resp.json()["sky"]["witching_proximity"]
assert witching is not None
assert 0.0 <= witching <= 1.0
@pytest.mark.asyncio
@pytest.mark.parametrize("bad", ["ghost", "", "nan", "inf", "999", "-181"])
async def test_invalid_lon_ignored_not_422(client, geo_null, bad):
resp = await client.get("/api/conditions", params={"lon": bad})
assert resp.status_code == 200
assert resp.json()["sky"]["witching_proximity"] is None
@pytest.mark.asyncio
async def test_geomagnetic_reading_passed_through(client, geo_storm):
resp = await client.get("/api/conditions")
assert resp.status_code == 200
geo = resp.json()["geomagnetic"]
assert geo["kp"] == pytest.approx(6.33)
assert geo["storm"] is True
assert geo["label"] == "moderate geomagnetic storm"