fix(alerting): reject null fireMode on ExchangeMatchCondition + repair in-flight rows
All checks were successful
All checks were successful
The rule editor wizard reset the condition payload on kind-change without
seeding a fireMode default; the ExchangeMatchCondition ctor allowed null to
pass through; AlertEvaluatorJob then NPE-looped every tick on a saved rule.
- core: compact ctor now rejects null fireMode (Jackson-deser path only — all
production callers already pass a value).
- V14: repair existing EXCHANGE_MATCH rows with fireMode=null to
PER_EXCHANGE + perExchangeLingerSeconds=300 (default matches the wizard).
- ui: ConditionStep.onKindChange seeds EXCHANGE_MATCH defaults so the
Select's displayed fallback ("Per exchange") is actually in form state.
- ui: validateStep('condition', ...) now enforces fireMode presence + the
mode-specific fields before the user reaches Review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@ public record ExchangeMatchCondition(
|
||||
) implements AlertCondition {
|
||||
|
||||
public ExchangeMatchCondition {
|
||||
if (fireMode == null)
|
||||
throw new IllegalArgumentException("fireMode is required (PER_EXCHANGE or COUNT_IN_WINDOW)");
|
||||
if (fireMode == FireMode.COUNT_IN_WINDOW && (threshold == null || windowSeconds == null))
|
||||
throw new IllegalArgumentException("COUNT_IN_WINDOW requires threshold + windowSeconds");
|
||||
if (fireMode == FireMode.PER_EXCHANGE && perExchangeLingerSeconds == null)
|
||||
|
||||
@@ -6,6 +6,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class AlertConditionJsonTest {
|
||||
|
||||
@@ -43,6 +44,34 @@ class AlertConditionJsonTest {
|
||||
assertThat(((ExchangeMatchCondition) parsed).threshold()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void exchangeMatchRejectsNullFireMode() {
|
||||
assertThatThrownBy(() -> new ExchangeMatchCondition(
|
||||
new AlertScope(null, null, null),
|
||||
new ExchangeMatchCondition.ExchangeFilter("FAILED", Map.of()),
|
||||
null, null, null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("fireMode");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exchangeMatchRejectsNullFireModeOnDeserialization() throws Exception {
|
||||
String json = """
|
||||
{
|
||||
"kind": "EXCHANGE_MATCH",
|
||||
"scope": {},
|
||||
"filter": {"status": "FAILED", "attributes": {}},
|
||||
"fireMode": null,
|
||||
"threshold": null,
|
||||
"windowSeconds": null,
|
||||
"perExchangeLingerSeconds": null
|
||||
}
|
||||
""";
|
||||
assertThatThrownBy(() -> om.readValue(json, AlertCondition.class))
|
||||
.hasRootCauseInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("fireMode");
|
||||
}
|
||||
|
||||
@Test
|
||||
void roundtripAgentState() throws Exception {
|
||||
var c = new AgentStateCondition(new AlertScope("orders", null, null), "DEAD", 60);
|
||||
|
||||
Reference in New Issue
Block a user