mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fix: register satpass adapter on EnvironmentalConfig
_subject_owned() iterates _SUBJECTS_BARE keys and calls getattr(self._env, attr) — but EnvironmentalConfig had no satpass attribute, so Central never subscribed to central.sat.> subjects. Changes: - Add SatpassConfig(_SourcedFeed) with feed_source=central default - Add satpass field to EnvironmentalConfig - Add satpass YAML parsing handler in _dict_to_dataclass - Add (satpass, enabled) to adapter_config REGISTRY - Fix 3 satpass REGISTRY entries: type list -> json (vocabulary bug) - Update stale count assertions (SCHEMA 16->17, REGISTRY 84->90, ADAPTER_META 21->23) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5418ed1f65
commit
aa2838fee3
5 changed files with 143 additions and 8 deletions
|
|
@ -595,9 +595,14 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = {
|
|||
# =================================================================
|
||||
# SATPASS -- Satellite pass broadcasts
|
||||
# =================================================================
|
||||
("satpass", "enabled"): {
|
||||
"default": False,
|
||||
"type": "bool",
|
||||
"description": "Enable satellite pass broadcasts from Central.",
|
||||
},
|
||||
("satpass", "observers"): {
|
||||
"default": [],
|
||||
"type": "list",
|
||||
"type": "json",
|
||||
"description": "Observer location names to include (empty = all).",
|
||||
},
|
||||
("satpass", "min_elevation"): {
|
||||
|
|
@ -607,12 +612,12 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = {
|
|||
},
|
||||
("satpass", "norad_ids"): {
|
||||
"default": [],
|
||||
"type": "list",
|
||||
"type": "json",
|
||||
"description": "NORAD catalog IDs to include (empty = all).",
|
||||
},
|
||||
("satpass", "command_norad_ids"): {
|
||||
"default": [25544],
|
||||
"type": "list",
|
||||
"type": "json",
|
||||
"description": "Default NORAD IDs for bare !satpass command (default: [25544] ISS).",
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -450,6 +450,16 @@ class FIRMSConfig(_SourcedFeed):
|
|||
proximity_km: float = 10.0 # km to match known fire
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class SatpassConfig(_SourcedFeed):
|
||||
"""Satellite pass prediction settings (central-only feed)."""
|
||||
|
||||
enabled: bool = False
|
||||
feed_source: str = "central"
|
||||
|
||||
|
||||
@dataclass
|
||||
class CentralConsumerConfig:
|
||||
"""Connection settings for the Central NATS JetStream consumer (v0.4).
|
||||
|
|
@ -495,6 +505,7 @@ class EnvironmentalConfig:
|
|||
roads511: Roads511Config = field(default_factory=Roads511Config)
|
||||
wzdx: WZDxConfig = field(default_factory=WZDxConfig)
|
||||
firms: FIRMSConfig = field(default_factory=FIRMSConfig)
|
||||
satpass: SatpassConfig = field(default_factory=SatpassConfig)
|
||||
central: CentralConsumerConfig = field(default_factory=CentralConsumerConfig)
|
||||
geocoder: GeocoderConfig = field(default_factory=GeocoderConfig)
|
||||
|
||||
|
|
@ -831,6 +842,8 @@ def _dict_to_dataclass(cls, data: dict):
|
|||
kwargs[key] = _dict_to_dataclass(WZDxConfig, value)
|
||||
elif key == "firms" and isinstance(value, dict):
|
||||
kwargs[key] = _dict_to_dataclass(FIRMSConfig, value)
|
||||
elif key == "satpass" and isinstance(value, dict):
|
||||
kwargs[key] = _dict_to_dataclass(SatpassConfig, value)
|
||||
elif key == "environmental" and isinstance(value, dict):
|
||||
kwargs[key] = _dict_to_dataclass(EnvironmentalConfig, value)
|
||||
elif key == "dashboard" and isinstance(value, dict):
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ def test_list_returns_all_59_keys(client):
|
|||
# 14 adapters with at least one key (itd_511 has zero -- not in the
|
||||
# grouped dict because the SQL only returns rows that exist).
|
||||
total = sum(len(v) for v in body.values())
|
||||
assert total == 84
|
||||
assert total == 90
|
||||
|
||||
|
||||
def test_list_grouped_by_adapter(client):
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ def test_schema_meta_at_v12(fresh_db):
|
|||
v = fresh_db.execute(
|
||||
"SELECT value FROM schema_meta WHERE key='version'"
|
||||
).fetchone()["value"]
|
||||
assert int(v) == 16
|
||||
assert int(v) == 17
|
||||
|
||||
|
||||
def test_adapter_config_type_check_constrains_vocabulary(fresh_db):
|
||||
|
|
@ -75,14 +75,14 @@ def test_adapter_config_type_check_constrains_vocabulary(fresh_db):
|
|||
|
||||
def test_registry_at_59_entries():
|
||||
"""v0.6-3a.1 trim: 43 CONFIG-only keys (was 77 in v0.6-3a draft)."""
|
||||
assert len(REGISTRY) == 84, (
|
||||
f"REGISTRY should have 43 entries after CONFIG-vs-CODE trim; got {len(REGISTRY)}. "
|
||||
assert len(REGISTRY) == 90, (
|
||||
f"REGISTRY drift guard; got {len(REGISTRY)}. "
|
||||
f"If a sentence template / emoji / heuristic snuck in, it belongs in CODE not config."
|
||||
)
|
||||
|
||||
|
||||
def test_adapter_meta_at_19(fresh_db):
|
||||
assert len(ADAPTER_META) == 21
|
||||
assert len(ADAPTER_META) == 23
|
||||
|
||||
|
||||
# ---------- seed ----------------------------------------------------------
|
||||
|
|
|
|||
117
tests/test_satpass_registration.py
Normal file
117
tests/test_satpass_registration.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""Tests for satpass adapter registration on EnvironmentalConfig.
|
||||
|
||||
Verifies:
|
||||
- SatpassConfig exists and defaults to feed_source="central"
|
||||
- EnvironmentalConfig.satpass field is present and correctly typed
|
||||
- _subject_owned() includes central.sat.* subjects when satpass is registered
|
||||
- adapter_config REGISTRY contains satpass keys with valid types
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# -- config model tests -------------------------------------------------------
|
||||
|
||||
def test_satpass_config_exists():
|
||||
from meshai.config import SatpassConfig
|
||||
cfg = SatpassConfig()
|
||||
assert cfg.feed_source == "central"
|
||||
assert cfg.enabled is False
|
||||
|
||||
|
||||
def test_satpass_config_is_sourced_feed():
|
||||
from meshai.config import SatpassConfig, _SourcedFeed
|
||||
assert issubclass(SatpassConfig, _SourcedFeed)
|
||||
|
||||
|
||||
def test_satpass_config_rejects_invalid_feed_source():
|
||||
from meshai.config import SatpassConfig
|
||||
with pytest.raises(ValueError, match="feed_source"):
|
||||
SatpassConfig(feed_source="bogus")
|
||||
|
||||
|
||||
def test_environmental_config_has_satpass():
|
||||
from meshai.config import EnvironmentalConfig, SatpassConfig
|
||||
env = EnvironmentalConfig()
|
||||
assert hasattr(env, "satpass")
|
||||
assert isinstance(env.satpass, SatpassConfig)
|
||||
|
||||
|
||||
def test_environmental_satpass_default_central():
|
||||
from meshai.config import EnvironmentalConfig
|
||||
env = EnvironmentalConfig()
|
||||
assert env.satpass.feed_source == "central"
|
||||
|
||||
|
||||
# -- _subject_owned() integration ---------------------------------------------
|
||||
|
||||
def test_subject_owned_includes_satpass_subjects():
|
||||
"""When EnvironmentalConfig has satpass with feed_source='central',
|
||||
_subject_owned() must return subjects containing 'central.sat.'."""
|
||||
from meshai.config import EnvironmentalConfig
|
||||
from meshai.central.consumer import _SUBJECTS_BARE
|
||||
|
||||
env = EnvironmentalConfig()
|
||||
|
||||
# Simulate what _subject_owned does for the satpass attr
|
||||
cfg = getattr(env, "satpass", None)
|
||||
assert cfg is not None, "satpass attr missing from EnvironmentalConfig"
|
||||
assert getattr(cfg, "feed_source", "native") == "central"
|
||||
|
||||
# Verify _SUBJECTS_BARE has satpass entry
|
||||
assert "satpass" in _SUBJECTS_BARE, "satpass missing from _SUBJECTS_BARE"
|
||||
subjects = _SUBJECTS_BARE["satpass"]
|
||||
assert any("central.sat.pass" in s for s in subjects)
|
||||
assert any("central.sat.tle" in s for s in subjects)
|
||||
|
||||
|
||||
# -- adapter_config REGISTRY --------------------------------------------------
|
||||
|
||||
def test_registry_has_satpass_enabled():
|
||||
from meshai.adapter_config.defaults import REGISTRY
|
||||
assert ("satpass", "enabled") in REGISTRY
|
||||
spec = REGISTRY[("satpass", "enabled")]
|
||||
assert spec["type"] == "bool"
|
||||
assert spec["default"] is False
|
||||
|
||||
|
||||
def test_registry_satpass_types_valid():
|
||||
"""All satpass REGISTRY entries must use types in the valid vocabulary."""
|
||||
from meshai.adapter_config.defaults import REGISTRY
|
||||
valid = {"int", "float", "str", "bool", "json"}
|
||||
satpass_keys = [(a, k) for a, k in REGISTRY if a == "satpass"]
|
||||
assert len(satpass_keys) >= 5, f"Expected >= 5 satpass keys, got {len(satpass_keys)}"
|
||||
for a, k in satpass_keys:
|
||||
assert REGISTRY[(a, k)]["type"] in valid, (
|
||||
f"satpass.{k} has invalid type {REGISTRY[(a, k)]['type']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_registry_satpass_list_types_are_json():
|
||||
"""List-valued satpass keys must use type='json', not 'list'."""
|
||||
from meshai.adapter_config.defaults import REGISTRY
|
||||
for key in ("observers", "norad_ids", "command_norad_ids"):
|
||||
spec = REGISTRY[("satpass", key)]
|
||||
assert spec["type"] == "json", (
|
||||
f"satpass.{key} should be type='json', got {spec['type']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_adapter_meta_has_satpass():
|
||||
from meshai.adapter_config.defaults import ADAPTER_META
|
||||
assert "satpass" in ADAPTER_META
|
||||
|
||||
|
||||
# -- YAML round-trip -----------------------------------------------------------
|
||||
|
||||
def test_yaml_parsing_satpass():
|
||||
"""SatpassConfig should be deserialized from YAML config dict."""
|
||||
from meshai.config import SatpassConfig, _dict_to_dataclass, EnvironmentalConfig
|
||||
import yaml
|
||||
|
||||
yaml_str = "environmental:\n satpass:\n enabled: true\n feed_source: central\n"
|
||||
data = yaml.safe_load(yaml_str)
|
||||
env = _dict_to_dataclass(EnvironmentalConfig, data["environmental"])
|
||||
assert isinstance(env.satpass, SatpassConfig)
|
||||
assert env.satpass.enabled is True
|
||||
assert env.satpass.feed_source == "central"
|
||||
Loading…
Add table
Add a link
Reference in a new issue