mirror of
https://github.com/smittix/intercept.git
synced 2026-07-04 07:43:39 -07:00
76fcce949c
Create data/signals.json with 20 seed signals (FM broadcast, airband, ISM bands, maritime VHF, AIS, ACARS, ADS-B, POCSAG, cellular, WiFi/BT, amateur radio, DAB, PMR446, FRS/GMRS, NOAA weather radio). Point frequencies adjusted to ±500 Hz windows; fixed-value bandwidth_ranges widened to strict min < max windows required by schema. Add tests/test_signals_json.py with 9 schema validation tests covering id uniqueness, required string fields, frequency range validity, bandwidth range, modulation casing, categories, region codes, and sigidwiki URLs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.6 KiB
Python
104 lines
4.6 KiB
Python
"""Schema validation for data/signals.json."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
SIGNALS_PATH = Path(__file__).resolve().parent.parent / "data" / "signals.json"
|
|
|
|
VALID_REGIONS = {"GLOBAL", "EU", "US", "UK", "AU"}
|
|
|
|
|
|
def _load() -> list[dict]:
|
|
assert SIGNALS_PATH.exists(), f"signals.json not found at {SIGNALS_PATH}"
|
|
with open(SIGNALS_PATH) as f:
|
|
data = json.load(f)
|
|
assert isinstance(data, list), "signals.json must be a JSON array"
|
|
return data
|
|
|
|
|
|
class TestSignalsJsonSchema:
|
|
def test_file_loads_as_list(self):
|
|
data = _load()
|
|
assert len(data) > 0
|
|
|
|
def test_all_ids_unique(self):
|
|
data = _load()
|
|
ids = [s["id"] for s in data]
|
|
assert len(ids) == len(set(ids)), f"Duplicate ids: {[x for x in ids if ids.count(x) > 1]}"
|
|
|
|
def test_required_string_fields(self):
|
|
data = _load()
|
|
for s in data:
|
|
for field in ("id", "name", "description"):
|
|
assert field in s, f"Missing '{field}' in signal {s.get('id', '?')}"
|
|
assert isinstance(s[field], str), f"'{field}' must be str in {s['id']}"
|
|
assert s[field].strip(), f"'{field}' must not be empty in {s['id']}"
|
|
|
|
def test_frequency_ranges_valid(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "frequency_ranges" in s, f"Missing frequency_ranges in {sid}"
|
|
assert isinstance(s["frequency_ranges"], list), f"frequency_ranges must be list in {sid}"
|
|
assert len(s["frequency_ranges"]) > 0, f"frequency_ranges must not be empty in {sid}"
|
|
for r in s["frequency_ranges"]:
|
|
assert isinstance(r.get("min_hz"), int), f"min_hz must be int in {sid}"
|
|
assert isinstance(r.get("max_hz"), int), f"max_hz must be int in {sid}"
|
|
assert r["min_hz"] > 0, f"min_hz must be > 0 in {sid}"
|
|
assert r["min_hz"] < r["max_hz"], f"min_hz must be < max_hz in {sid}"
|
|
|
|
def test_bandwidth_range_valid_or_null(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "bandwidth_range" in s, f"Missing bandwidth_range in {sid}"
|
|
bw = s["bandwidth_range"]
|
|
if bw is None:
|
|
continue
|
|
assert isinstance(bw.get("min_hz"), int), f"bandwidth_range.min_hz must be int in {sid}"
|
|
assert isinstance(bw.get("max_hz"), int), f"bandwidth_range.max_hz must be int in {sid}"
|
|
assert bw["min_hz"] > 0, f"bandwidth_range.min_hz must be > 0 in {sid}"
|
|
assert bw["min_hz"] < bw["max_hz"], f"bandwidth_range.min_hz must be < max_hz in {sid}"
|
|
|
|
def test_modulations_uppercase_strings(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "modulations" in s, f"Missing modulations in {sid}"
|
|
assert isinstance(s["modulations"], list), f"modulations must be list in {sid}"
|
|
for m in s["modulations"]:
|
|
assert isinstance(m, str), f"modulation token must be str in {sid}"
|
|
assert m == m.upper(), f"modulation '{m}' must be uppercase in {sid}"
|
|
assert m.strip(), f"modulation token must not be empty in {sid}"
|
|
|
|
def test_categories_list_of_strings(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "categories" in s, f"Missing categories in {sid}"
|
|
assert isinstance(s["categories"], list), f"categories must be list in {sid}"
|
|
for c in s["categories"]:
|
|
assert isinstance(c, str) and c.strip(), f"category must be non-empty str in {sid}"
|
|
|
|
def test_regions_valid_values(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "regions" in s, f"Missing regions in {sid}"
|
|
assert isinstance(s["regions"], list), f"regions must be list in {sid}"
|
|
assert len(s["regions"]) > 0, f"regions must not be empty in {sid}"
|
|
for r in s["regions"]:
|
|
assert r in VALID_REGIONS, f"Invalid region '{r}' in {sid}. Valid: {VALID_REGIONS}"
|
|
|
|
def test_sigidwiki_url_string_or_null(self):
|
|
data = _load()
|
|
for s in data:
|
|
sid = s.get("id", "?")
|
|
assert "sigidwiki_url" in s, f"Missing sigidwiki_url in {sid}"
|
|
url = s["sigidwiki_url"]
|
|
if url is not None:
|
|
assert isinstance(url, str), f"sigidwiki_url must be str or null in {sid}"
|
|
assert url.startswith("https://www.sigidwiki.com/"), f"sigidwiki_url must be sigidwiki.com URL in {sid}"
|