mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 09:21:33 +00:00
_TOWN_ANCHORS_SEED covered only 29 hand-picked towns, while the live CT108 town_anchors table (GUI-curated since) has grown to 186. Any fresh deploy (fresh volume, or the pre-v19 sqlite path) would seed only the original 29 and silently lose anchor-resolution coverage for the other 157 real, already-in-production towns. Regenerated the dict from a live dump of CT108's town_anchors (name, lat, lon, state; all 186 rows confirmed enabled=1), alphabetized, same dict-of-dicts shape and column-aligned brace formatting as before (alignment column widened to fit the longest name, "mountain home afb"). seed_town_anchors() is unchanged and still INSERT OR IGNORE, so it stays idempotent against the existing production rows. Expanding the seed changes which town resolves as "nearest" for a few existing test fixtures whose incident/work-zone coordinates are genuinely closer to a newly-added real town (e.g. Oakley, Wilder) than to the old 29-town subset's nearest match -- those goldens and assertions are updated to the new (correct) nearest-town result. test_api_post_add_town's probe town is renamed from the now-real "Bellevue" to a fictitious "Testopolis" to avoid a duplicate-name 400.
217 lines
6.4 KiB
Python
217 lines
6.4 KiB
Python
"""v0.6-4 curation tests: schema, seed, accessors, REST API."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from meshai.persistence.curation import (
|
|
invalidate_curation_cache,
|
|
lookup_gauge_site,
|
|
lookup_town_anchor,
|
|
seed_gauge_sites,
|
|
seed_town_anchors,
|
|
_GAUGE_SITES_SEED,
|
|
_TOWN_ANCHORS_SEED,
|
|
)
|
|
from meshai.persistence import get_db
|
|
from meshai.dashboard.api.curation_routes import router as curation_router
|
|
|
|
|
|
# ============================================================================
|
|
# Schema + seed
|
|
# ============================================================================
|
|
|
|
|
|
def test_v8_v9_tables_present():
|
|
conn = get_db()
|
|
tables = {r["name"] for r in conn.execute(
|
|
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
).fetchall()}
|
|
assert "gauge_sites" in tables
|
|
assert "town_anchors" in tables
|
|
|
|
|
|
def test_gauge_sites_seeded_at_init():
|
|
conn = get_db()
|
|
n = conn.execute("SELECT COUNT(*) FROM gauge_sites").fetchone()[0]
|
|
assert n == len(_GAUGE_SITES_SEED)
|
|
|
|
|
|
def test_town_anchors_seeded_at_init():
|
|
conn = get_db()
|
|
n = conn.execute("SELECT COUNT(*) FROM town_anchors").fetchone()[0]
|
|
assert n == len(_TOWN_ANCHORS_SEED)
|
|
|
|
|
|
def test_seed_idempotent():
|
|
conn = get_db()
|
|
a = seed_gauge_sites(conn)
|
|
b = seed_town_anchors(conn)
|
|
assert a == 0 and b == 0
|
|
|
|
|
|
def test_seed_does_not_overwrite_user_edits():
|
|
conn = get_db()
|
|
conn.execute(
|
|
"UPDATE gauge_sites SET action_ft=99 WHERE site_id='USGS-13186000'"
|
|
)
|
|
seed_gauge_sites(conn)
|
|
r = conn.execute(
|
|
"SELECT action_ft FROM gauge_sites WHERE site_id='USGS-13186000'"
|
|
).fetchone()
|
|
assert r["action_ft"] == 99
|
|
|
|
|
|
# ============================================================================
|
|
# Accessors
|
|
# ============================================================================
|
|
|
|
|
|
def test_lookup_gauge_site_hits():
|
|
invalidate_curation_cache()
|
|
r = lookup_gauge_site("USGS-13186000")
|
|
assert r is not None
|
|
assert r["gauge_name"] == "Snake River at Heise"
|
|
assert r["action_ft"] == 12.0
|
|
|
|
|
|
def test_lookup_gauge_site_miss():
|
|
invalidate_curation_cache()
|
|
assert lookup_gauge_site("USGS-99999") is None
|
|
|
|
|
|
def test_lookup_gauge_disabled_row_invisible():
|
|
conn = get_db()
|
|
conn.execute("UPDATE gauge_sites SET enabled=0 WHERE site_id='USGS-13186000'")
|
|
invalidate_curation_cache()
|
|
assert lookup_gauge_site("USGS-13186000") is None
|
|
|
|
|
|
def test_lookup_town_anchor_hits():
|
|
invalidate_curation_cache()
|
|
coord = lookup_town_anchor("Boise")
|
|
assert coord is not None
|
|
assert coord[0] == pytest.approx(43.6150)
|
|
assert coord[1] == pytest.approx(-116.2023)
|
|
|
|
|
|
def test_lookup_town_anchor_case_insensitive():
|
|
invalidate_curation_cache()
|
|
a = lookup_town_anchor("MERIDIAN")
|
|
b = lookup_town_anchor("meridian")
|
|
assert a == b
|
|
|
|
|
|
def test_lookup_town_anchor_miss():
|
|
invalidate_curation_cache()
|
|
assert lookup_town_anchor("xxxx") is None
|
|
|
|
|
|
# ============================================================================
|
|
# REST API: gauge_sites
|
|
# ============================================================================
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
app = FastAPI()
|
|
app.include_router(curation_router, prefix="/api")
|
|
return TestClient(app)
|
|
|
|
|
|
def test_api_list_gauges(client):
|
|
r = client.get("/api/gauge-sites")
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert len(body) == len(_GAUGE_SITES_SEED)
|
|
|
|
|
|
def test_api_get_one_gauge(client):
|
|
r = client.get("/api/gauge-sites/USGS-13186000")
|
|
assert r.status_code == 200
|
|
assert r.json()["gauge_name"] == "Snake River at Heise"
|
|
|
|
|
|
def test_api_get_404(client):
|
|
r = client.get("/api/gauge-sites/USGS-99999")
|
|
assert r.status_code == 404
|
|
|
|
|
|
def test_api_post_add_gauge(client):
|
|
r = client.post("/api/gauge-sites", json={
|
|
"site_id": "USGS-NEW1", "gauge_name": "Test Gauge",
|
|
"lat": 44.0, "lon": -115.0, "action_ft": 5.0,
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
assert r.json()["site_id"] == "USGS-NEW1"
|
|
# Accessor sees it.
|
|
invalidate_curation_cache()
|
|
assert lookup_gauge_site("USGS-NEW1") is not None
|
|
|
|
|
|
def test_api_put_updates_gauge(client):
|
|
r = client.put("/api/gauge-sites/USGS-13186000",
|
|
json={"action_ft": 15.5})
|
|
assert r.status_code == 200
|
|
assert r.json()["action_ft"] == 15.5
|
|
invalidate_curation_cache()
|
|
assert lookup_gauge_site("USGS-13186000")["action_ft"] == 15.5
|
|
|
|
|
|
def test_api_delete_gauge(client):
|
|
r = client.delete("/api/gauge-sites/USGS-13186000")
|
|
assert r.status_code == 200
|
|
invalidate_curation_cache()
|
|
assert lookup_gauge_site("USGS-13186000") is None
|
|
|
|
|
|
def test_api_post_missing_field_400(client):
|
|
r = client.post("/api/gauge-sites", json={"gauge_name": "X"})
|
|
assert r.status_code == 400
|
|
|
|
|
|
# ============================================================================
|
|
# REST API: town_anchors
|
|
# ============================================================================
|
|
|
|
|
|
def test_api_list_towns(client):
|
|
r = client.get("/api/town-anchors")
|
|
assert r.status_code == 200
|
|
assert len(r.json()) == len(_TOWN_ANCHORS_SEED)
|
|
|
|
|
|
def test_api_post_add_town(client):
|
|
# A clearly-fictitious name, not a real Idaho town -- avoids colliding
|
|
# with the town_anchors seed (real curated Idaho/neighbor towns; a
|
|
# UNIQUE(name) constraint 400s on a duplicate insert).
|
|
r = client.post("/api/town-anchors", json={
|
|
"name": "Testopolis", "lat": 43.4670, "lon": -114.2557, "state": "ID",
|
|
})
|
|
assert r.status_code == 200
|
|
assert r.json()["name"] == "testopolis"
|
|
invalidate_curation_cache()
|
|
coord = lookup_town_anchor("testopolis")
|
|
assert coord is not None
|
|
|
|
|
|
def test_api_put_town(client):
|
|
# Find Boise's anchor_id first.
|
|
r = client.get("/api/town-anchors")
|
|
boise = next(t for t in r.json() if t["name"] == "boise")
|
|
r2 = client.put(f"/api/town-anchors/{boise['anchor_id']}",
|
|
json={"enabled": False})
|
|
assert r2.status_code == 200
|
|
assert r2.json()["enabled"] is False
|
|
invalidate_curation_cache()
|
|
assert lookup_town_anchor("boise") is None
|
|
|
|
|
|
def test_api_delete_town(client):
|
|
r = client.get("/api/town-anchors")
|
|
nampa = next(t for t in r.json() if t["name"] == "nampa")
|
|
r2 = client.delete(f"/api/town-anchors/{nampa['anchor_id']}")
|
|
assert r2.status_code == 200
|
|
invalidate_curation_cache()
|
|
assert lookup_town_anchor("nampa") is None
|