test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129)
Triaged all 20 known-red tests. 13 were stale tests asserting rotted
expectations against deliberate, documented behavior changes; fixed by
deriving expected values instead of hard-coding, or updating the
expectation to match a documented policy change:
- test_adapter_config_foundation.py / test_adapter_config_api.py:
REGISTRY/API key-count and key-set guards hard-coded magic numbers
(59/94/17) that rotted repeatedly. Now derive expectations from
REGISTRY itself and, for the schema version, from the migrations
directory, so they can't rot the same way again.
- test_fire_tracker_phase4.py: two tests hardcoded a nonexistent
deployment path (/opt/meshai/meshai/router.py) that matches no
Dockerfile WORKDIR in this repo; resolve the module path via
importlib.util.find_spec instead.
- test_tombstone_broadcast.py: asserted fire severity == "immediate",
which commit 2f677e85 deliberately downgraded to "priority" (to stop
fire broadcasts bypassing the Grouper/cooldown during NATS backlog
replay) without updating this test.
- test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper
asserted an immediate-severity bypass that commit 85d48ce3
("fix(fire): remove immediate-severity exemption from grouper +
cooldown") DELETED on purpose -- fire events carry
_severity_override="immediate", and the exemption left fire with no
rate control at all in normal live operation. Re-adding the bypass
would re-open that fire-spam hole on a public-safety mesh, so the test
moves, not the source. Renamed + inverted to assert the real contract
(all severities coalesce; only a missing group_key passes through).
- test_tail_followups.py: dispatcher mock was missing
dispatch_scheduled_fire_broadcast (a method added alongside the
generic dispatch_scheduled_broadcast; test_reminders.py already
mocks both).
- test_tracking_v057.py: guard required an empty tracking-family
adapter list in Environment.tsx, but the frontend has long grouped
the pre-existing native satpass adapter under the "Tracking" display
section (its own "satpass" backend toggle, unrelated to the Phase-7
tracking family every other guard in this file confirms is still
unimplemented). Narrowed the guard to allow only that known entry.
- test_v052_dispatcher.py: two tests used category="wildfire_incident",
which the phase3b fire migration (#33) forced onto a dedicated
formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot
(same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising
the generic composer logic under test.
Also fixes one stale COMMENT (comment-only, no logic change) in
meshai/notifications/pipeline/__init__.py's start_pipeline(): it still
claimed "Immediate events bypass the grouper and don't need this
[periodic flush]", which has been false since 85d48ce3 and is precisely
what makes the deleted bypass look like a missing feature. The comment
now records that the removal was deliberate and must not be reverted.
The remaining 6 failures are left untouched -- 2 confirmed real bugs, to
be fixed deliberately in their own changes:
- meshcore_transport.py defines `_resolve_contact` TWICE on
MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The
second silently shadows the first, so the DM contact-resolution
refetch-on-miss that #56 added is dead code in production. (3 tests)
- SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual
highest migration v28; v27 and v28 shipped without bumping it.
(3 tests)
Plus 1 environment gap, not a code defect:
test_natural_language_fire_question_routes_to_llm needs the `openai`
package, which is declared in requirements.txt but not installed here.
Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72
skipped. All 7 remaining failures are ones classified above; no new
failures introduced elsewhere in the suite.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
|
|
|
"""Grouper tests: coalescing (all severities) + periodic flush.
|
|
|
|
|
|
|
|
|
|
Note: there is deliberately NO immediate-severity bypass -- commit 85d48ce3
|
|
|
|
|
removed it so fire broadcasts obey rate control. The only pass-through is
|
|
|
|
|
"event has no group_key".
|
|
|
|
|
"""
|
2026-06-16 03:40:31 +00:00
|
|
|
|
|
|
|
|
from meshai.notifications.pipeline.grouper import Grouper
|
|
|
|
|
from meshai.notifications.events import make_event
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Recorder:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.received = []
|
|
|
|
|
|
|
|
|
|
def handle(self, event):
|
|
|
|
|
self.received.append(event)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ev(severity, group_key="gk1"):
|
|
|
|
|
return make_event(
|
|
|
|
|
source="usgs_quake",
|
|
|
|
|
category="earthquake_event",
|
|
|
|
|
severity=severity,
|
|
|
|
|
title=f"test {severity}",
|
|
|
|
|
lat=42.6,
|
|
|
|
|
lon=-114.5,
|
|
|
|
|
group_key=group_key,
|
|
|
|
|
inhibit_keys=[group_key],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129)
Triaged all 20 known-red tests. 13 were stale tests asserting rotted
expectations against deliberate, documented behavior changes; fixed by
deriving expected values instead of hard-coding, or updating the
expectation to match a documented policy change:
- test_adapter_config_foundation.py / test_adapter_config_api.py:
REGISTRY/API key-count and key-set guards hard-coded magic numbers
(59/94/17) that rotted repeatedly. Now derive expectations from
REGISTRY itself and, for the schema version, from the migrations
directory, so they can't rot the same way again.
- test_fire_tracker_phase4.py: two tests hardcoded a nonexistent
deployment path (/opt/meshai/meshai/router.py) that matches no
Dockerfile WORKDIR in this repo; resolve the module path via
importlib.util.find_spec instead.
- test_tombstone_broadcast.py: asserted fire severity == "immediate",
which commit 2f677e85 deliberately downgraded to "priority" (to stop
fire broadcasts bypassing the Grouper/cooldown during NATS backlog
replay) without updating this test.
- test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper
asserted an immediate-severity bypass that commit 85d48ce3
("fix(fire): remove immediate-severity exemption from grouper +
cooldown") DELETED on purpose -- fire events carry
_severity_override="immediate", and the exemption left fire with no
rate control at all in normal live operation. Re-adding the bypass
would re-open that fire-spam hole on a public-safety mesh, so the test
moves, not the source. Renamed + inverted to assert the real contract
(all severities coalesce; only a missing group_key passes through).
- test_tail_followups.py: dispatcher mock was missing
dispatch_scheduled_fire_broadcast (a method added alongside the
generic dispatch_scheduled_broadcast; test_reminders.py already
mocks both).
- test_tracking_v057.py: guard required an empty tracking-family
adapter list in Environment.tsx, but the frontend has long grouped
the pre-existing native satpass adapter under the "Tracking" display
section (its own "satpass" backend toggle, unrelated to the Phase-7
tracking family every other guard in this file confirms is still
unimplemented). Narrowed the guard to allow only that known entry.
- test_v052_dispatcher.py: two tests used category="wildfire_incident",
which the phase3b fire migration (#33) forced onto a dedicated
formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot
(same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising
the generic composer logic under test.
Also fixes one stale COMMENT (comment-only, no logic change) in
meshai/notifications/pipeline/__init__.py's start_pipeline(): it still
claimed "Immediate events bypass the grouper and don't need this
[periodic flush]", which has been false since 85d48ce3 and is precisely
what makes the deleted bypass look like a missing feature. The comment
now records that the removal was deliberate and must not be reverted.
The remaining 6 failures are left untouched -- 2 confirmed real bugs, to
be fixed deliberately in their own changes:
- meshcore_transport.py defines `_resolve_contact` TWICE on
MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The
second silently shadows the first, so the DM contact-resolution
refetch-on-miss that #56 added is dead code in production. (3 tests)
- SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual
highest migration v28; v27 and v28 shipped without bumping it.
(3 tests)
Plus 1 environment gap, not a code defect:
test_natural_language_fire_question_routes_to_llm needs the `openai`
package, which is declared in requirements.txt but not installed here.
Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72
skipped. All 7 remaining failures are ones classified above; no new
failures introduced elsewhere in the suite.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
|
|
|
def test_immediate_severity_is_also_coalesced_no_bypass():
|
|
|
|
|
"""An immediate event WITH a group_key is held, like every other severity.
|
|
|
|
|
|
|
|
|
|
The grouper used to exempt severity == "immediate" from the coalescing
|
|
|
|
|
window. Commit 85d48ce3 ("fix(fire): remove immediate-severity exemption
|
|
|
|
|
from grouper + cooldown") DELETED that bypass on purpose: fire events
|
|
|
|
|
carry _severity_override="immediate", and the exemption meant they
|
|
|
|
|
skipped the coalescer and zeroed the dispatcher cooldown, leaving fire
|
|
|
|
|
with no rate control at all in normal live operation. Rate control now
|
|
|
|
|
applies to ALL severities; the drain-mode pacer covers reconnect bursts.
|
|
|
|
|
|
|
|
|
|
This test previously asserted the OLD bypass contract and had been red
|
|
|
|
|
ever since. Re-adding the bypass to make it pass would re-open the fire
|
|
|
|
|
broadcast-spam hole on a public-safety mesh -- so the test is what moves,
|
|
|
|
|
not the source.
|
|
|
|
|
"""
|
2026-06-16 03:40:31 +00:00
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
g.handle(_ev("immediate"))
|
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129)
Triaged all 20 known-red tests. 13 were stale tests asserting rotted
expectations against deliberate, documented behavior changes; fixed by
deriving expected values instead of hard-coding, or updating the
expectation to match a documented policy change:
- test_adapter_config_foundation.py / test_adapter_config_api.py:
REGISTRY/API key-count and key-set guards hard-coded magic numbers
(59/94/17) that rotted repeatedly. Now derive expectations from
REGISTRY itself and, for the schema version, from the migrations
directory, so they can't rot the same way again.
- test_fire_tracker_phase4.py: two tests hardcoded a nonexistent
deployment path (/opt/meshai/meshai/router.py) that matches no
Dockerfile WORKDIR in this repo; resolve the module path via
importlib.util.find_spec instead.
- test_tombstone_broadcast.py: asserted fire severity == "immediate",
which commit 2f677e85 deliberately downgraded to "priority" (to stop
fire broadcasts bypassing the Grouper/cooldown during NATS backlog
replay) without updating this test.
- test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper
asserted an immediate-severity bypass that commit 85d48ce3
("fix(fire): remove immediate-severity exemption from grouper +
cooldown") DELETED on purpose -- fire events carry
_severity_override="immediate", and the exemption left fire with no
rate control at all in normal live operation. Re-adding the bypass
would re-open that fire-spam hole on a public-safety mesh, so the test
moves, not the source. Renamed + inverted to assert the real contract
(all severities coalesce; only a missing group_key passes through).
- test_tail_followups.py: dispatcher mock was missing
dispatch_scheduled_fire_broadcast (a method added alongside the
generic dispatch_scheduled_broadcast; test_reminders.py already
mocks both).
- test_tracking_v057.py: guard required an empty tracking-family
adapter list in Environment.tsx, but the frontend has long grouped
the pre-existing native satpass adapter under the "Tracking" display
section (its own "satpass" backend toggle, unrelated to the Phase-7
tracking family every other guard in this file confirms is still
unimplemented). Narrowed the guard to allow only that known entry.
- test_v052_dispatcher.py: two tests used category="wildfire_incident",
which the phase3b fire migration (#33) forced onto a dedicated
formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot
(same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising
the generic composer logic under test.
Also fixes one stale COMMENT (comment-only, no logic change) in
meshai/notifications/pipeline/__init__.py's start_pipeline(): it still
claimed "Immediate events bypass the grouper and don't need this
[periodic flush]", which has been false since 85d48ce3 and is precisely
what makes the deleted bypass look like a missing feature. The comment
now records that the removal was deliberate and must not be reverted.
The remaining 6 failures are left untouched -- 2 confirmed real bugs, to
be fixed deliberately in their own changes:
- meshcore_transport.py defines `_resolve_contact` TWICE on
MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The
second silently shadows the first, so the DM contact-resolution
refetch-on-miss that #56 added is dead code in production. (3 tests)
- SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual
highest migration v28; v27 and v28 shipped without bumping it.
(3 tests)
Plus 1 environment gap, not a code defect:
test_natural_language_fire_question_routes_to_llm needs the `openai`
package, which is declared in requirements.txt but not installed here.
Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72
skipped. All 7 remaining failures are ones classified above; no new
failures introduced elsewhere in the suite.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
|
|
|
# Held for coalescing, NOT delivered straight through.
|
|
|
|
|
assert rec.received == []
|
|
|
|
|
assert g.held_count() == 1
|
|
|
|
|
# The periodic flush (start_pipeline's _grouper_flush_loop) is what
|
|
|
|
|
# eventually delivers it, once the window expires.
|
|
|
|
|
g2 = Grouper(next_handler=rec.handle, window_seconds=0.0)
|
|
|
|
|
g2.handle(_ev("immediate", group_key="gk2"))
|
|
|
|
|
assert g2.tick() == 1
|
2026-06-16 03:40:31 +00:00
|
|
|
assert len(rec.received) == 1
|
|
|
|
|
assert rec.received[0].severity == "immediate"
|
test: fix 13 stale tests in the red suite, leave 6 real-bug failures (#124) (#129)
Triaged all 20 known-red tests. 13 were stale tests asserting rotted
expectations against deliberate, documented behavior changes; fixed by
deriving expected values instead of hard-coding, or updating the
expectation to match a documented policy change:
- test_adapter_config_foundation.py / test_adapter_config_api.py:
REGISTRY/API key-count and key-set guards hard-coded magic numbers
(59/94/17) that rotted repeatedly. Now derive expectations from
REGISTRY itself and, for the schema version, from the migrations
directory, so they can't rot the same way again.
- test_fire_tracker_phase4.py: two tests hardcoded a nonexistent
deployment path (/opt/meshai/meshai/router.py) that matches no
Dockerfile WORKDIR in this repo; resolve the module path via
importlib.util.find_spec instead.
- test_tombstone_broadcast.py: asserted fire severity == "immediate",
which commit 2f677e85 deliberately downgraded to "priority" (to stop
fire broadcasts bypassing the Grouper/cooldown during NATS backlog
replay) without updating this test.
- test_pipeline_grouper.py: test_immediate_severity_bypasses_grouper
asserted an immediate-severity bypass that commit 85d48ce3
("fix(fire): remove immediate-severity exemption from grouper +
cooldown") DELETED on purpose -- fire events carry
_severity_override="immediate", and the exemption left fire with no
rate control at all in normal live operation. Re-adding the bypass
would re-open that fire-spam hole on a public-safety mesh, so the test
moves, not the source. Renamed + inverted to assert the real contract
(all severities coalesce; only a missing group_key passes through).
- test_tail_followups.py: dispatcher mock was missing
dispatch_scheduled_fire_broadcast (a method added alongside the
generic dispatch_scheduled_broadcast; test_reminders.py already
mocks both).
- test_tracking_v057.py: guard required an empty tracking-family
adapter list in Environment.tsx, but the frontend has long grouped
the pre-existing native satpass adapter under the "Tracking" display
section (its own "satpass" backend toggle, unrelated to the Phase-7
tracking family every other guard in this file confirms is still
unimplemented). Narrowed the guard to allow only that known entry.
- test_v052_dispatcher.py: two tests used category="wildfire_incident",
which the phase3b fire migration (#33) forced onto a dedicated
formatter via NATIVE_ALWAYS_DECIDE; swapped to wildfire_hotspot
(same emoji/label, not in NATIVE_ALWAYS_DECIDE) to keep exercising
the generic composer logic under test.
Also fixes one stale COMMENT (comment-only, no logic change) in
meshai/notifications/pipeline/__init__.py's start_pipeline(): it still
claimed "Immediate events bypass the grouper and don't need this
[periodic flush]", which has been false since 85d48ce3 and is precisely
what makes the deleted bypass look like a missing feature. The comment
now records that the removal was deliberate and must not be reverted.
The remaining 6 failures are left untouched -- 2 confirmed real bugs, to
be fixed deliberately in their own changes:
- meshcore_transport.py defines `_resolve_contact` TWICE on
MeshCoreTransport (line 171 from PR #56, line 1227 from PR #92). The
second silently shadows the first, so the DM contact-resolution
refetch-on-miss that #56 added is dead code in production. (3 tests)
- SCHEMA_VERSION (persistence/db.py:33) is stale at 26 vs. the actual
highest migration v28; v27 and v28 shipped without bumping it.
(3 tests)
Plus 1 environment gap, not a code defect:
test_natural_language_fire_question_routes_to_llm needs the `openai`
package, which is declared in requirements.txt but not installed here.
Suite: 20 failed, 2240 passed, 72 skipped -> 7 failed, 2254 passed, 72
skipped. All 7 remaining failures are ones classified above; no new
failures introduced elsewhere in the suite.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:36:19 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_no_group_key_still_passes_through_immediately():
|
|
|
|
|
"""The ONE remaining bypass: an event with no group_key isn't coalesced
|
|
|
|
|
(there's nothing to coalesce it against)."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
g.handle(_ev("immediate", group_key=None))
|
|
|
|
|
assert len(rec.received) == 1
|
2026-06-16 03:40:31 +00:00
|
|
|
assert g.held_count() == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_periodic_flush_drains_routine():
|
|
|
|
|
"""A routine event is held, then released by tick() once its window passes."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=0.0) # 0s window -> tick drains now
|
|
|
|
|
g.handle(_ev("routine"))
|
|
|
|
|
# Held on arrival, not yet delivered.
|
|
|
|
|
assert g.held_count() == 1
|
|
|
|
|
assert rec.received == []
|
|
|
|
|
# The periodic flush task calls tick(); simulate one tick.
|
|
|
|
|
drained = g.tick()
|
|
|
|
|
assert drained == 1
|
|
|
|
|
assert len(rec.received) == 1
|
|
|
|
|
assert rec.received[0].severity == "routine"
|
|
|
|
|
assert g.held_count() == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_priority_is_also_coalesced_not_bypassed():
|
|
|
|
|
"""Priority events still buffer (only immediate bypasses)."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
g.handle(_ev("priority"))
|
|
|
|
|
assert rec.received == []
|
|
|
|
|
assert g.held_count() == 1
|
fix(pipeline): let wildfire_spotting skip the grouper (category-scoped) (#131)
wildfire_spotting is the most urgent signal in the system (a fire
throwing embers past its own containment line), but every fire event
carries a group_key (= event_id), so the Grouper held spotting for the
full grouper_window_seconds (60s live) before it could even reach the
dispatcher -- a ~60s latency FLOOR, and ~120s+ once FirePacer queuing
is added on top.
Add a module-level _NEVER_COALESCE_CATEGORIES frozenset and bypass the
coalescing window for the categories in it. Currently: wildfire_spotting
only.
This is safe because spotting is already rate-limited AT THE SOURCE: a
per-fire 1h cooldown (adapter_config.fires.spotting_cooldown_seconds,
default 3600, latched on fires.last_spotting_broadcast_at per irwin_id
in gating/firms.py) gates spotting DETECTION itself, so N active fires
yield at most N spotting alerts per hour. FirePacer (60s interval, with
head-of-line ordering for immediate severity) and the dispatcher's
per-(toggle, category, region) cooldown still apply downstream.
The bypass is scoped by CATEGORY, never by severity. Commit 85d48ce3
deliberately removed a severity == "immediate" bypass from this exact
spot because ALL fire events carry _severity_override="immediate", so a
severity bypass exempts the entire fire family from rate control. The
comment on the constant spells that out so it does not get re-added.
Tests: spotting with a group_key passes straight through; wildfire_growth
and wildfire_incident at immediate severity are STILL held (proving no
severity bypass crept back in); spotting with no group_key still passes
through. PR #129's test_immediate_severity_is_also_coalesced_no_bypass is
untouched and still passes.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 10:37:07 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wildfire_spotting_bypasses_the_grouper_even_with_group_key():
|
|
|
|
|
"""wildfire_spotting is a CATEGORY-scoped bypass (owner-approved): it
|
|
|
|
|
skips the coalescing window entirely, even though it carries a
|
|
|
|
|
group_key that would otherwise hold it. This is the one narrow
|
|
|
|
|
exemption -- see _NEVER_COALESCE_CATEGORIES in grouper.py for why
|
|
|
|
|
it's safe (source-side 1h per-fire cooldown) and why it must stay
|
|
|
|
|
category-scoped, not severity-scoped."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
ev = make_event(
|
|
|
|
|
source="firms",
|
|
|
|
|
category="wildfire_spotting",
|
|
|
|
|
severity="immediate",
|
|
|
|
|
title="test spotting",
|
|
|
|
|
lat=42.6,
|
|
|
|
|
lon=-114.5,
|
|
|
|
|
group_key="fire-irwin-123",
|
|
|
|
|
)
|
|
|
|
|
g.handle(ev)
|
|
|
|
|
# Passed straight through -- NOT held for the coalescing window.
|
|
|
|
|
assert len(rec.received) == 1
|
|
|
|
|
assert rec.received[0].category == "wildfire_spotting"
|
|
|
|
|
assert g.held_count() == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wildfire_growth_immediate_is_still_held_no_severity_bypass_crept_in():
|
|
|
|
|
"""A same-severity, same-fire-family event of a DIFFERENT category
|
|
|
|
|
(wildfire_growth, not wildfire_spotting) must still be coalesced.
|
|
|
|
|
This proves the new bypass is scoped to category and did not
|
|
|
|
|
accidentally reintroduce a severity-based bypass (the exact bug
|
|
|
|
|
commit 85d48ce3 removed)."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
ev = make_event(
|
|
|
|
|
source="wfigs",
|
|
|
|
|
category="wildfire_growth",
|
|
|
|
|
severity="immediate",
|
|
|
|
|
title="test growth",
|
|
|
|
|
lat=42.6,
|
|
|
|
|
lon=-114.5,
|
|
|
|
|
group_key="fire-irwin-123",
|
|
|
|
|
)
|
|
|
|
|
g.handle(ev)
|
|
|
|
|
assert rec.received == []
|
|
|
|
|
assert g.held_count() == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wildfire_incident_immediate_is_still_held_no_severity_bypass_crept_in():
|
|
|
|
|
"""Same as above for wildfire_incident: only wildfire_spotting bypasses,
|
|
|
|
|
every other fire category (even at immediate severity) is coalesced."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
ev = make_event(
|
|
|
|
|
source="wfigs",
|
|
|
|
|
category="wildfire_incident",
|
|
|
|
|
severity="immediate",
|
|
|
|
|
title="test incident",
|
|
|
|
|
lat=42.6,
|
|
|
|
|
lon=-114.5,
|
|
|
|
|
group_key="fire-irwin-123",
|
|
|
|
|
)
|
|
|
|
|
g.handle(ev)
|
|
|
|
|
assert rec.received == []
|
|
|
|
|
assert g.held_count() == 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_wildfire_spotting_with_no_group_key_still_passes_through():
|
|
|
|
|
"""Existing no-group_key behavior is preserved for spotting too --
|
|
|
|
|
the bypass condition is an `or`, not a replacement."""
|
|
|
|
|
rec = Recorder()
|
|
|
|
|
g = Grouper(next_handler=rec.handle, window_seconds=60.0)
|
|
|
|
|
ev = make_event(
|
|
|
|
|
source="firms",
|
|
|
|
|
category="wildfire_spotting",
|
|
|
|
|
severity="immediate",
|
|
|
|
|
title="test spotting no key",
|
|
|
|
|
lat=42.6,
|
|
|
|
|
lon=-114.5,
|
|
|
|
|
group_key=None,
|
|
|
|
|
)
|
|
|
|
|
g.handle(ev)
|
|
|
|
|
assert len(rec.received) == 1
|
|
|
|
|
assert g.held_count() == 0
|