# Test layout One **topic per file** — run only what you need while developing or validating a change. ```bash # Install into the pyenv site-packages (not ~/.local); Cursor/VS Code pytest sets PYTHONNOUSERSITE=1 python3 -m pip install --no-user -e ".[dev]" python3 -m pytest tests//test_.py -q # single file python3 -m pytest tests//test_.py::test_foo -q # single test python3 -m pytest tests/bridge/ -q # whole domain python3 -m pytest tests/ -q # full suite python3 -m pytest tests/ -q -m "not mqtt" # skip MQTT-heavy tests ``` Use the project interpreter, e.g. `/opt/.pyenv/versions/3.11.8/bin/python3`. ## Directories | Directory | What it covers | |-----------|----------------| | `bridge/` | Static TG, startup bridges, unit data, CRC dedup, echo, private voice, config reload | | `hbp/` | HBP ingress, loop control, rate limit, timeout/collision, master maintenance | | `obp/` | OpenBridge loop, rate limit, unit-data loop | | `voice/` | Announcements, TTS schedule, broadcast queue, disconnected voice, in-band signalling | | `talker_alias/` | Encode/decode, passthrough, MMDVM wire, bridge inject (DeterministicScenario) | | `parrot/` | Recording timers, playback loop, seq preservation, ingress path | | `replay/` | JSONL session replay (V2-P0-007) | | `schemas/` | Report v2 JSON Schema validation (`jsonschema` dev dep) | | `application/` | Report payloads, monitor topology, proxy use cases, runtime context | | `infrastructure/` | Logging reload, bridge router, **HBP REPEAT + proxy fan-in integration**, MQTT | | `smoke/` | Quick routing smoke | | `support/` | Shared stacks (`hbp_repeat_stack`, monitor sim) — not run as tests | | `harness/` | Shared fakes (`DeterministicScenario`, assertions) — not run as tests | ## Integration vs harness Most bridge/voice tests inject packets via **`DeterministicScenario`** (`bridge.dmrd_received` on fakes). That is fast but **skips** `udp_hbp` REPEAT rewrite and proxy UDP fan-in. For regressions on those paths, use: | File | Topic | |------|-------| | `infrastructure/test_hbp_repeat_talker_alias.py` | Real `HBPProtocol` REPEAT + embedded TA | | `infrastructure/test_proxy_repeat_e2e.py` | Proxy fan-in → REPEAT downlink | | `infrastructure/test_proxy_reload.py` | Hot reload keeps UDP listener | Mark new stack tests with `@pytest.mark.integration`. ## Files by domain ### bridge/ | File | Tests | Topic | |------|-------|-------| | `test_config_reload.py` | 1 | Merge system config on reload | | `test_crc_dedup.py` | 3 | HBP/OBP CRC dedup, seq=0 | | `test_echo_bridgereset.py` | 5 | Echo leg after BRIDGERESET / OPTIONS | | `test_options_config_loop.py` | 2 | 26s OPTIONS static TG | | `test_private_voice.py` | 3 | Private call routing | | `test_startup_bridges.py` | 4 | Startup BRIDGES + voice E2E | | `test_static_tg_options.py` | 4 | Static TG from peer OPTIONS | | `test_unit_data_ingress.py` | 4 | Unit headers, CSBK, reports | | `test_unit_data_routing.py` | 5 | SUB_MAP, hotspot, gateway, OBP fanout | ### hbp/ | File | Tests | Topic | |------|-------|-------| | `test_hbp_loop_control.py` | 2 | HBP loop winner/loser | | `test_hbp_rate_limit.py` | 2 | Ingress rate drop + stall | | `test_ingress.py` | 3 | RX start, rate drop, OBP loop loser | | `test_master_maintenance.py` | 2 | Peer timeout / shared PEERS dict | | `test_timeout_collision.py` | 3 | 180s timeout, collision, rekey | ### obp/ | File | Tests | Topic | |------|-------|-------| | `test_loop_control.py` | 4 | OBP loop, VTERM, BCSQ | | `test_obp_rate_limit.py` | 1 | Rate limit epoch | | `test_unit_data_loop.py` | 2 | Unit data loop loser | ### voice/ | File | Tests | Topic | |------|-------|-------| | `test_announcement_anticollision.py` | 3 | Busy slot skip / abort | | `test_broadcast_queue.py` | 2 | Same-TG broadcast queue | | `test_disconnected_voice.py` | 3 | Not-linked / reflector prompts | | `test_in_band_signalling.py` | 5 | Reflector / single-mode VTERM | | `test_play_file_on_request.py` | 3 | On-demand file playback | | `test_scheduled_announcement.py` | 4 | File announcements (AMBE) | | `test_scheduled_tts.py` | 7 | TTS schedule + conversion | | `test_voice_config_reload.py` | 3 | Hot reload announcement/TTS loops | ### talker_alias/ | File | Tests | Topic | |------|-------|-------| | `test_bridge_inject.py` | 3 | TA inject on bridge VHEAD (harness) | | `test_embed_ta.py` | 4 | Embedded LC modes | | `test_encode_decode.py` | 8 | Domain encode/decode | | `test_format.py` | 2 | Format from subscriber profile | | `test_mmdvm_wire.py` | 10 | MMDVM wire blocks | | `test_passthrough.py` | 9 | Passthrough / both modes | ### parrot/ | File | Tests | Topic | |------|-------|-------| | `test_playback_ingress.py` | 2 | `ingress_pkt_time`, record→playback | | `test_playback_logging.py` | 1 | Duration log format | | `test_playback_send_loop.py` | 4 | Send loop, max duration, interval | | `test_recording_timers.py` | 2 | Idle timeout, VTERM commit | | `test_rekey_playback.py` | 4 | Seq preservation past 255 / 30s | ### infrastructure (integration highlights) | File | Topic | |------|-------| | `test_hbp_repeat_talker_alias.py` | REPEAT embed TA through real HBP | | `test_proxy_repeat_e2e.py` | Proxy → REPEAT E2E | | `test_proxy_reload.py` | Proxy hot reload | | `test_udp_fanin.py` | UDP fan-in routing | | `test_report_server_wire.py` | Report server wire opcodes | | `test_logging_reload.py` | Log level reload | | `test_bridge_router_index.py` | BRIDGES O(1) index vs legacy scan | ### smoke/ · application/ | File | Topic | |------|-------| | `smoke/test_bridge_routing.py` | Static TG forward smoke | | `application/test_monitor_topology.py` | Inject-only proxy monitor remap | | `application/test_runtime_context.py` | RuntimeContext holder, SIGHUP swap prep | ## Examples (copy-paste) ```bash # After changing bridge unit-data routing python3 -m pytest tests/bridge/test_unit_data_routing.py -q # After parrot seq fix python3 -m pytest tests/parrot/test_rekey_playback.py -q # Talker Alias REPEAT (real stack) python3 -m pytest tests/infrastructure/test_hbp_repeat_talker_alias.py -q # HBP loop + rate (common RF regressions) python3 -m pytest tests/hbp/test_hbp_loop_control.py tests/hbp/test_hbp_rate_limit.py -q # One test by name python3 -m pytest tests/bridge/test_startup_bridges.py::test_startup_bridge_routes_voice_after_apply -q ``` ## Policy - **New tests:** add a new file (or extend the smallest existing file for the same topic). Avoid large multi-topic modules. - **Harness:** shared code lives in `harness/` and `support/` only. - **Stack regressions:** prefer `infrastructure/test_*_e2e.py` with real adapters over duplicating in `DeterministicScenario`. - Full audit: `docs-priv/en/test-audit.md` (maintainer checkout).