meshai/work/tests/test_adapter_config_api.py

359 lines
11 KiB
Python
Raw Normal View History

"""v0.6-3c API tests for adapter_config + adapter_meta routes.
Uses FastAPI TestClient against a tmp DB seeded by the conftest autouse
fixture. Covers: GET (list, per-adapter, single), PUT (incl. type
validation), POST reset, GET/PUT meta, cache invalidation propagation
to the accessor.
"""
from __future__ import annotations
import json
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
from meshai.adapter_config import adapter_config, invalidate_cache, REGISTRY
from meshai.dashboard.api.adapter_config_routes import router
@pytest.fixture
def client():
app = FastAPI()
app.include_router(router, prefix="/api")
return TestClient(app)
# ============================================================================
# GET /api/adapter-config (grouped)
# ============================================================================
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
def test_list_returns_every_registry_key(client):
"""The grouped listing must return exactly one row per REGISTRY entry.
Previously hard-coded an exact total (59, then 96, then 94 -- the test
name still said 59 long after the asserted number had drifted twice)
that rotted every time a key was legitimately added or removed from
REGISTRY. Comparing against len(REGISTRY) directly is the actual
invariant under test and can't rot the same way.
"""
r = client.get("/api/adapter-config")
assert r.status_code == 200
body = r.json()
total = sum(len(v) for v in body.values())
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
assert total == len(REGISTRY)
def test_list_grouped_by_adapter(client):
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
"""wfigs's key set in the API must match REGISTRY exactly.
Previously hard-coded a 5-key set that missed max_declare_age_seconds
(added by the fire age-gate feature, commit 95b1a23e) -- comparing
against REGISTRY directly means a future added/removed wfigs key can't
silently desync this test again.
"""
r = client.get("/api/adapter-config")
body = r.json()
assert "wfigs" in body
keys = {row["key"] for row in body["wfigs"]}
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129) Triaged all 20 known-red tests. 13 were stale tests asserting rotted expectations against deliberate, documented behavior changes; fixed by deriving expected values instead of hard-coding, or updating the expectation to match a documented policy change: - test_adapter_config_foundation.py / test_adapter_config_api.py: REGISTRY/API key-count and key-set guards hard-coded magic numbers (59/94/17) that rotted repeatedly. Now derive expectations from REGISTRY itself and, for the schema version, from the migrations directory, so they can't rot the same way again. - test_fire_tracker_phase4.py: two tests hardcoded a nonexistent deployment path (/opt/meshai/meshai/router.py) that matches no Dockerfile WORKDIR in this repo; resolve the module path via importlib.util.find_spec instead. - test_tombstone_broadcast.py: asserted fire severity == "immediate", which commit 2f677e85 deliberately downgraded to "priority" (to stop fire broadcasts bypassing the Grouper/cooldown during NATS backlog replay) without updating this test. - test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper asserted an immediate-severity bypass that commit 85d48ce3 ("fix(fire): remove immediate-severity exemption from grouper + cooldown") DELETED on purpose -- fire events carry _severity_override="immediate", and the exemption left fire with no rate control at all in normal live operation. Re-adding the bypass would re-open that fire-spam hole on a public-safety mesh, so the test moves, not the source. Renamed + inverted to assert the real contract (all severities coalesce; only a missing group_key passes through). - test_tail_followups.py: dispatcher mock was missing dispatch_scheduled_fire_broadcast (a method added alongside the generic dispatch_scheduled_broadcast; test_reminders.py already mocks both). - test_tracking_v057.py: guard required an empty tracking-family adapter list in Environment.tsx, but the frontend has long grouped the pre-existing native satpass adapter under the "Tracking" display section (its own "satpass" backend toggle, unrelated to the Phase-7 tracking family every other guard in this file confirms is still unimplemented). Narrowed the guard to allow only that known entry. - test_v052_dispatcher.py: two tests used category="wildfire_incident", which the phase3b fire migration (#33) forced onto a dedicated formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot (same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising the generic composer logic under test. Also fixes one stale COMMENT (comment-only, no logic change) in meshai/notifications/pipeline/__init__.py's start_pipeline(): it still claimed "Immediate events bypass the grouper and don't need this [periodic flush]", which has been false since 85d48ce3 and is precisely what makes the deleted bypass look like a missing feature. The comment now records that the removal was deliberate and must not be reverted. The remaining 6 failures are left untouched -- 2 confirmed real bugs, to be fixed deliberately in their own changes: - meshcore_transport.py defines `_resolve_contact` TWICE on MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The second silently shadows the first, so the DM contact-resolution refetch-on-miss that #56 added is dead code in production. (3 tests) - SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual highest migration v28; v27 and v28 shipped without bumping it. (3 tests) Plus 1 environment gap, not a code defect: test_natural_language_fire_question_routes_to_llm needs the `openai` package, which is declared in requirements.txt but not installed here. Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72 skipped. All 7 remaining failures are ones classified above; no new failures introduced elsewhere in the suite. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
expected = {key for (adapter, key) in REGISTRY if adapter == "wfigs"}
assert keys == expected
def test_list_includes_type_value_default_description(client):
r = client.get("/api/adapter-config")
body = r.json()
row = next(row for row in body["wfigs"] if row["key"] == "cooldown_seconds")
assert row["value"] == 28800
assert row["default"] == 28800
assert row["type"] == "int"
assert row["description"]
# ============================================================================
# GET /api/adapter-config/{adapter}
# ============================================================================
def test_per_adapter_list(client):
r = client.get("/api/adapter-config/usgs_quake")
assert r.status_code == 200
body = r.json()
assert isinstance(body, list)
keys = {row["key"] for row in body}
assert keys == {
"regional_centroid", "regional_radius_mi",
"broadcast_pager_alerts", "global_mag_floor",
"regional_mag_floor", "escalate_mag_floor",
}
def test_per_adapter_empty_for_itd_511(client):
"""itd_511 has zero config keys post-3a.1; returns empty list, not 404."""
r = client.get("/api/adapter-config/itd_511")
assert r.status_code == 200
assert len(r.json()) > 0 # itd_511 has adapter_config keys now
# ============================================================================
# GET /api/adapter-config/{adapter}/{key}
# ============================================================================
def test_get_single_key(client):
r = client.get("/api/adapter-config/usgs_quake/global_mag_floor")
assert r.status_code == 200
body = r.json()
assert body["value"] == 3.0
assert body["default"] == 3.0
assert body["type"] == "float"
def test_get_unknown_key_404(client):
r = client.get("/api/adapter-config/wfigs/no_such_key")
assert r.status_code == 404
# ============================================================================
# PUT /api/adapter-config/{adapter}/{key}
# ============================================================================
def test_put_updates_value(client):
r = client.put(
"/api/adapter-config/usgs_quake/global_mag_floor",
json={"value": 2.8},
)
assert r.status_code == 200
assert r.json()["value"] == 2.8
# GET reflects the new value.
g = client.get("/api/adapter-config/usgs_quake/global_mag_floor")
assert g.json()["value"] == 2.8
def test_put_invalidates_accessor_cache(client):
"""The handler-side accessor reads the new value WITHOUT a restart."""
# Prime the cache with the default.
assert adapter_config.usgs_quake.global_mag_floor == 3.0
# Mutate via API.
client.put(
"/api/adapter-config/usgs_quake/global_mag_floor",
json={"value": 2.5},
)
# Accessor returns the new value -- cache invalidation worked.
assert adapter_config.usgs_quake.global_mag_floor == 2.5
def test_put_int_validation(client):
"""int field rejects string body."""
r = client.put(
"/api/adapter-config/wfigs/cooldown_seconds",
json={"value": "two hours"},
)
assert r.status_code == 400
def test_put_int_rejects_float_with_fraction(client):
r = client.put(
"/api/adapter-config/wfigs/cooldown_seconds",
json={"value": 3600.5},
)
assert r.status_code == 400
def test_put_int_accepts_float_with_integer_value(client):
r = client.put(
"/api/adapter-config/wfigs/cooldown_seconds",
json={"value": 3600.0},
)
assert r.status_code == 200
assert r.json()["value"] == 3600
def test_put_float_accepts_int(client):
r = client.put(
"/api/adapter-config/usgs_quake/global_mag_floor",
json={"value": 4},
)
assert r.status_code == 200
assert r.json()["value"] == 4.0
def test_put_bool_rejects_int(client):
r = client.put(
"/api/adapter-config/wfigs/broadcast_on_acres",
json={"value": 1},
)
assert r.status_code == 400
def test_put_str_validation(client):
r = client.put(
"/api/adapter-config/geocoder/photon_url",
json={"value": 42},
)
assert r.status_code == 400
def test_put_json_accepts_list(client):
chore(config): delete 51 dead keys, fix usgs_quake floor, secret-flag consistency (#46) Backend half of making the dashboard the complete config surface (per an exhaustive per-key audit). Delete 51 vestigial/unread config fields (load-safe: _dict_to_dataclass whitelists by field, so existing files carrying these keys still load and the keys drop on next save): - 36 duplicated MQTT block (host/port/username/password/topic_root/use_tls) on memory/context/commands + env nws/swpc/ducting (grep-proven unread; mesh_sources keeps its real MQTT fields) - 3 no-op history cleanup keys (auto_cleanup/cleanup_interval_hours/max_age_days) - 5 alert scaffolding (alert_cooldown_minutes, RegionAnchor.nws_zones, battery_{warning,critical,emergency}_voltage) - 5 danger-zone non-fire min_acres (kept fire.min_acres via a fire subclass) - 2 deprecated adapter_config keys (nws.broadcast_severities/warning_suffix_promotes) Bug: usgs_quake native magnitude floor was unreachable from the GUI (native reads config.min_magnitude; the GUI "Global Floor" wrote the registry global_mag_floor that only the Central path reads). Reconciled: min_magnitude is the canonical native floor the frontend will bind; registry floors marked Central-path-only. Effective filtering unchanged. Secret-flag consistency: add environmental.roads511.api_key + wzdx.api_key to SECRET_FIELDS (secrets move to .env in the follow-up; ${VAR} interpolation kept). Suite at 10-failure baseline (1703 passed). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:34:27 -06:00
# broadcast_severities was removed in the config-schema cleanup; use the
# surviving nws json-list key tombstone_msgtypes to exercise list PUTs.
r = client.put(
chore(config): delete 51 dead keys, fix usgs_quake floor, secret-flag consistency (#46) Backend half of making the dashboard the complete config surface (per an exhaustive per-key audit). Delete 51 vestigial/unread config fields (load-safe: _dict_to_dataclass whitelists by field, so existing files carrying these keys still load and the keys drop on next save): - 36 duplicated MQTT block (host/port/username/password/topic_root/use_tls) on memory/context/commands + env nws/swpc/ducting (grep-proven unread; mesh_sources keeps its real MQTT fields) - 3 no-op history cleanup keys (auto_cleanup/cleanup_interval_hours/max_age_days) - 5 alert scaffolding (alert_cooldown_minutes, RegionAnchor.nws_zones, battery_{warning,critical,emergency}_voltage) - 5 danger-zone non-fire min_acres (kept fire.min_acres via a fire subclass) - 2 deprecated adapter_config keys (nws.broadcast_severities/warning_suffix_promotes) Bug: usgs_quake native magnitude floor was unreachable from the GUI (native reads config.min_magnitude; the GUI "Global Floor" wrote the registry global_mag_floor that only the Central path reads). Reconciled: min_magnitude is the canonical native floor the frontend will bind; registry floors marked Central-path-only. Effective filtering unchanged. Secret-flag consistency: add environmental.roads511.api_key + wzdx.api_key to SECRET_FIELDS (secrets move to .env in the follow-up; ${VAR} interpolation kept). Suite at 10-failure baseline (1703 passed). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:34:27 -06:00
"/api/adapter-config/nws/tombstone_msgtypes",
json={"value": ["Cancel"]},
)
assert r.status_code == 200
chore(config): delete 51 dead keys, fix usgs_quake floor, secret-flag consistency (#46) Backend half of making the dashboard the complete config surface (per an exhaustive per-key audit). Delete 51 vestigial/unread config fields (load-safe: _dict_to_dataclass whitelists by field, so existing files carrying these keys still load and the keys drop on next save): - 36 duplicated MQTT block (host/port/username/password/topic_root/use_tls) on memory/context/commands + env nws/swpc/ducting (grep-proven unread; mesh_sources keeps its real MQTT fields) - 3 no-op history cleanup keys (auto_cleanup/cleanup_interval_hours/max_age_days) - 5 alert scaffolding (alert_cooldown_minutes, RegionAnchor.nws_zones, battery_{warning,critical,emergency}_voltage) - 5 danger-zone non-fire min_acres (kept fire.min_acres via a fire subclass) - 2 deprecated adapter_config keys (nws.broadcast_severities/warning_suffix_promotes) Bug: usgs_quake native magnitude floor was unreachable from the GUI (native reads config.min_magnitude; the GUI "Global Floor" wrote the registry global_mag_floor that only the Central path reads). Reconciled: min_magnitude is the canonical native floor the frontend will bind; registry floors marked Central-path-only. Effective filtering unchanged. Secret-flag consistency: add environmental.roads511.api_key + wzdx.api_key to SECRET_FIELDS (secrets move to .env in the follow-up; ${VAR} interpolation kept). Suite at 10-failure baseline (1703 passed). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:34:27 -06:00
assert r.json()["value"] == ["Cancel"]
def test_put_json_accepts_dict(client):
r = client.put(
"/api/adapter-config/central/severity_thresholds",
json={"value": {"routine_max": 0, "priority_max": 1, "immediate_min": 2}},
)
assert r.status_code == 200
def test_put_json_accepts_none(client):
r = client.put(
"/api/adapter-config/firms/bbox",
json={"value": None},
)
assert r.status_code == 200
assert r.json()["value"] is None
def test_put_unknown_key_404(client):
r = client.put(
"/api/adapter-config/wfigs/no_such_key",
json={"value": 1},
)
assert r.status_code == 404
def test_put_missing_value_field(client):
r = client.put(
"/api/adapter-config/wfigs/cooldown_seconds",
json={},
)
assert r.status_code == 400
# ============================================================================
# POST /api/adapter-config/{adapter}/{key}/reset
# ============================================================================
def test_reset_restores_default(client):
# Mutate.
client.put(
"/api/adapter-config/usgs_quake/global_mag_floor",
json={"value": 2.8},
)
assert client.get("/api/adapter-config/usgs_quake/global_mag_floor").json()["value"] == 2.8
# Reset.
r = client.post("/api/adapter-config/usgs_quake/global_mag_floor/reset")
assert r.status_code == 200
assert r.json()["value"] == 3.0
# Accessor too.
invalidate_cache()
assert adapter_config.usgs_quake.global_mag_floor == 3.0
def test_reset_invalidates_cache(client):
"""Reset must invalidate the accessor cache the same as PUT."""
client.put(
"/api/adapter-config/usgs_quake/global_mag_floor",
json={"value": 2.8},
)
# Prime cache with the post-PUT value.
assert adapter_config.usgs_quake.global_mag_floor == 2.8
client.post("/api/adapter-config/usgs_quake/global_mag_floor/reset")
# Cache cleared -- next read returns the default.
assert adapter_config.usgs_quake.global_mag_floor == 3.0
def test_reset_unknown_key_404(client):
r = client.post("/api/adapter-config/wfigs/no_such_key/reset")
assert r.status_code == 404
# ============================================================================
# GET /api/adapter-meta
# ============================================================================
def test_list_meta(client):
r = client.get("/api/adapter-meta")
assert r.status_code == 200
body = r.json()
assert "wfigs" in body
assert body["wfigs"]["include_in_llm_context"] is True
# central / geocoder default to False
assert body["central"]["include_in_llm_context"] is False
assert body["geocoder"]["include_in_llm_context"] is False
# ============================================================================
# PUT /api/adapter-meta/{adapter}
# ============================================================================
def test_put_meta_toggles_llm_context(client):
r = client.put(
"/api/adapter-meta/itd_511",
json={"include_in_llm_context": False},
)
assert r.status_code == 200
assert r.json()["include_in_llm_context"] is False
# GET reflects the change.
g = client.get("/api/adapter-meta").json()
assert g["itd_511"]["include_in_llm_context"] is False
def test_put_meta_updates_display_name(client):
r = client.put(
"/api/adapter-meta/wfigs",
json={"display_name": "Active wildfires (WFIGS)"},
)
assert r.status_code == 200
assert r.json()["display_name"] == "Active wildfires (WFIGS)"
def test_put_meta_partial_update(client):
"""Only the fields in the body change; others survive."""
original = client.get("/api/adapter-meta").json()["wfigs"]
r = client.put(
"/api/adapter-meta/wfigs",
json={"include_in_llm_context": False},
)
assert r.status_code == 200
body = r.json()
assert body["display_name"] == original["display_name"] # unchanged
assert body["include_in_llm_context"] is False
def test_put_meta_rejects_bad_bool(client):
r = client.put(
"/api/adapter-meta/wfigs",
json={"include_in_llm_context": "yes"},
)
assert r.status_code == 400
def test_put_meta_rejects_empty_display_name(client):
r = client.put(
"/api/adapter-meta/wfigs",
json={"display_name": " "},
)
assert r.status_code == 400
def test_put_meta_unknown_adapter_404(client):
r = client.put(
"/api/adapter-meta/nonexistent_adapter",
json={"include_in_llm_context": False},
)
assert r.status_code == 404