meshai/work/tests/test_formatter_scaffold.py

66 lines
2.6 KiB
Python
Raw Normal View History

refactor(phase0): source-agnostic formatter/gating scaffold + harness (inert) (#28) Foundation for making all hazard formatting+gating source-agnostic. ZERO behavior change — the formatter/decider registries are empty (get_formatter/ get_decider return None → existing precomposed/Mode-B path preserved), and the shadow comparator is off unless MESHAI_SHADOW_CATEGORIES is set. - notifications/formatters/ (registry+dispatch with family fallback), gating/ (GateResult + deferred-commit contract), both empty registries. - notifications/clock.py determinism seam; route wfigs/quake/nws gating time reads through it (identical values) so goldens can freeze time. - formatters/_budget.py = copy of central/budget.py; central/budget.py is now a re-export shim (import-smoke test guards it). - compose_mesh_message consults the registry first (verbatim, no Mode-B re-cap), falls back to legacy; _resolve_budget injects per-category budget. - notifications/shadow.py + two DRY-RUN hooks (consumer._normalize, dispatcher render): compute the new result and diff-log SHADOW_MISMATCH JSONL, but NEVER commit/emit/write tables and always broadcast the OLD result. Inert by default. - tests/harness (pinned_time/pinned_tz, byte-golden, gate-sequence) + scripts/capture_fixtures.py (ephemeral read-only NATS capture); tzdata pinned. Tests: +60 (18 scaffold + 42 harness/shadow); 0 new failures (34 baseline). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 12:55:48 -06:00
"""Phase-0 scaffold tests: formatter registry empty + dispatch correctness.
(a) With an empty registry, get_formatter returns None for real categories.
(b) A registered dummy formatter is called verbatim multi-line output is
returned as-is (no Mode-B single-line cap applied).
"""
import pytest
from meshai.notifications.formatters import FORMATTERS, get_formatter, register
from meshai.notifications.events import make_event
from meshai.notifications.renderers.composer import compose_mesh_message
# ── (a) empty registry returns None for known categories ──────────────────────
@pytest.mark.parametrize("category", [
"weather_warning",
"earthquake_event",
"wildfire_incident",
"road_closure",
"battery_critical",
])
def test_get_formatter_returns_none_while_registry_empty(category):
"""FORMATTERS is empty; get_formatter must return None for any real category."""
# Guarantee the registry is empty for these categories (it starts empty at
# module level; tests run in isolation from each other's registrations).
assert category not in FORMATTERS, (
f"Category {category!r} should not be in FORMATTERS at Phase 0"
)
assert get_formatter(category) is None
# ── (b) registered dummy formatter is dispatched verbatim ────────────────────
_SYNTHETIC_CATEGORY = "_test_scaffold_dummy_category_phase0"
_MULTILINE_OUTPUT = "Line one\nLine two\nLine three"
def _dummy_formatter(event, *, now: float, budget: int) -> str:
"""Returns a fixed multi-line string to prove verbatim passthrough."""
return _MULTILINE_OUTPUT
def test_registered_formatter_returns_verbatim_multiline(monkeypatch):
"""Register a dummy for a synthetic category; compose_mesh_message must
return the dummy's multi-line output verbatim — newlines preserved — not
re-processed through Mode-B's single-line budget loop.
"""
# Register the dummy (clean up afterwards to avoid cross-test pollution).
register(_SYNTHETIC_CATEGORY, _dummy_formatter)
try:
event = make_event(
source="test",
category=_SYNTHETIC_CATEGORY,
severity="routine",
title="First line\nSecond line", # multi-line title
)
result = compose_mesh_message(event)
assert result == _MULTILINE_OUTPUT, (
f"Expected verbatim multi-line output, got: {result!r}"
)
# Verify newlines are preserved (Mode-B would strip them).
assert "\n" in result, "Newlines must survive the formatter dispatch path"
finally:
FORMATTERS.pop(_SYNTHETIC_CATEGORY, None)