"""Tests for the feature flag layer (spec 2026-08-23). None of these need a running Unleash. That is the point: an unset UNLEASH_URL means every flag is False, which is what local development and CI get. """ from datetime import date, timedelta from backend import flags def test_every_declared_flag_is_keyed_by_its_own_name(): # One string is the registry key, the Unleash flag name and the JSON key. # A mismatch here would mean the UI toggles a flag the code never reads. for key, flag in flags.REGISTRY.items(): assert key == flag.name def test_flag_names_are_snake_case(): # Matches the API's existing convention (admission_distance, # rwm_expected_pct) so no case transformation exists to get wrong. for name in flags.REGISTRY: assert name == name.lower() assert "-" not in name and " " not in name def test_an_unconfigured_client_evaluates_every_flag_false(monkeypatch): monkeypatch.setattr(flags, "_client", None) for name in flags.REGISTRY: assert flags.is_enabled(name) is False def test_an_undeclared_flag_is_false_rather_than_an_error(monkeypatch): # A typo'd flag name must not raise in a request path. It is logged as an # error, because an undeclared flag is always a bug. monkeypatch.setattr(flags, "_client", None) assert flags.is_enabled("no_such_flag") is False def test_an_exploding_client_is_false_rather_than_a_500(monkeypatch): class Boom: def is_enabled(self, *a, **kw): raise RuntimeError("unleash is on fire") monkeypatch.setattr(flags, "_client", Boom()) name = next(iter(flags.REGISTRY)) assert flags.is_enabled(name) is False def test_all_flags_reports_every_declared_flag(monkeypatch): monkeypatch.setattr(flags, "_client", None) assert set(flags.all_flags()) == set(flags.REGISTRY) assert all(v is False for v in flags.all_flags().values()) def test_a_flag_older_than_the_limit_fails_this_test(): """A tripwire, not an assertion about correctness. Flags are temporary scaffolding and the failure mode of every flag system is accumulation. This fails on the day a flag turns 90, on whatever PR happens to be open — which is the point: someone has to decide. To fix: delete the flag and the branches that read it, or, if it genuinely still needs to exist, move its `added` date and say why in the commit. """ stale = [ f.name for f in flags.REGISTRY.values() if date.today() - f.added > timedelta(days=flags.MAX_FLAG_AGE_DAYS) ] assert not stale, ( f"Flags older than {flags.MAX_FLAG_AGE_DAYS} days: {stale}. " "Remove the flag and the code branches it guards, or move its `added` " "date deliberately." ) def _client(): from fastapi.testclient import TestClient from backend import app as app_module return TestClient(app_module.app, raise_server_exceptions=False) def test_the_flags_endpoint_lists_every_declared_flag(monkeypatch): monkeypatch.setattr(flags, "_client", None) body = _client().get("/api/flags").json() assert set(body) == set(flags.REGISTRY) def test_the_flags_endpoint_answers_false_when_unleash_is_unreachable(monkeypatch): # The endpoint must still answer. A frontend that cannot read flags renders # everything dark, which is right; one that gets a 500 renders nothing. monkeypatch.setattr(flags, "_client", None) res = _client().get("/api/flags") assert res.status_code == 200 assert all(v is False for v in res.json().values())