| """FOUND-04 class coverage test. |
| |
| Every one of the 10 disconnect classes has a registered generator emitting |
| >=1 frame whose dict matches the TelemetryFrame field names. |
| """ |
| from __future__ import annotations |
|
|
| from numpy.random import PCG64, Generator |
|
|
| from model.synth.state_machines import GENERATORS |
|
|
| EXPECTED_CLASSES: frozenset[str] = frozenset({ |
| "auth_8021x_eap_fail", |
| "ap_roam_rekey_fail", |
| "radius_timeout", |
| "captive_portal_expiry", |
| "mac_randomization_reject", |
| "dhcp_lease_churn", |
| "dns_resolver_fail", |
| "driver_power_save_wake", |
| "rf_sticky_client", |
| "isp_upstream_fail", |
| }) |
|
|
| CANONICAL_ORDER = [ |
| "auth_8021x_eap_fail", |
| "ap_roam_rekey_fail", |
| "radius_timeout", |
| "captive_portal_expiry", |
| "mac_randomization_reject", |
| "dhcp_lease_churn", |
| "dns_resolver_fail", |
| "driver_power_save_wake", |
| "rf_sticky_client", |
| "isp_upstream_fail", |
| ] |
|
|
|
|
| def test_all_ten_classes_registered(): |
| assert set(GENERATORS.keys()) == EXPECTED_CLASSES, ( |
| "GENERATORS must contain exactly the 10 canonical class slugs; " |
| f"got {sorted(GENERATORS.keys())}" |
| ) |
|
|
|
|
| def test_generators_in_canonical_order(): |
| """Insertion order matters for byte-identicality (RESEARCH Pattern 4 Critical note).""" |
| assert list(GENERATORS.keys()) == CANONICAL_ORDER |
|
|
|
|
| def test_each_class_emits_valid_frame(expected_field_names): |
| rng = Generator(PCG64(42)) |
| for cls, fn in GENERATORS.items(): |
| frames = fn(rng) |
| assert len(frames) >= 1, f"Class {cls} emitted zero frames" |
| sample = frames[0] |
| extra = set(sample.keys()) - expected_field_names |
| missing = expected_field_names - set(sample.keys()) |
| assert not extra, f"Class {cls} emitted unknown fields: {extra}" |
| assert not missing, f"Class {cls} missing required fields: {missing}" |
|
|
|
|
| def test_window_ms_per_class(): |
| """D-04: 30000 ms for fast events, 120000 ms for driver / sticky-client.""" |
| rng = Generator(PCG64(42)) |
| slow_classes = {"driver_power_save_wake", "rf_sticky_client"} |
| for cls, fn in GENERATORS.items(): |
| sample = fn(rng)[0] |
| expected_window = 120000 if cls in slow_classes else 30000 |
| assert sample["window_ms"] == expected_window, ( |
| f"Class {cls} window_ms={sample['window_ms']}, expected {expected_window} (D-04)" |
| ) |
|
|