alerting(it): RED tests for PER_EXCHANGE cross-field validation + empty targets
Three failing IT tests documenting the contract Task 3.3 will satisfy: - createPerExchangeRule_withReNotifyMinutesNonZero_returns400 - createPerExchangeRule_withForDurationSecondsNonZero_returns400 - createAnyRule_withEmptyWebhooksAndTargets_returns400
This commit is contained in:
@@ -246,6 +246,61 @@ class AlertRuleControllerIT extends AbstractPostgresIT {
|
||||
assertThat(preview.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
}
|
||||
|
||||
// --- PER_EXCHANGE cross-field validation + empty-targets validation ---
|
||||
// RED tests: today's controller accepts these bodies; Task 3.3 adds the validator.
|
||||
|
||||
@Test
|
||||
void createPerExchangeRule_withReNotifyMinutesNonZero_returns400() {
|
||||
String body = perExchangeRuleBodyWithExtras(
|
||||
"per-exchange-renotify",
|
||||
/*reNotifyMinutes*/ 60,
|
||||
/*forDurationSeconds*/ null);
|
||||
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(resp.getBody()).contains("reNotifyMinutes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createPerExchangeRule_withForDurationSecondsNonZero_returns400() {
|
||||
String body = perExchangeRuleBodyWithExtras(
|
||||
"per-exchange-forduration",
|
||||
/*reNotifyMinutes*/ null,
|
||||
/*forDurationSeconds*/ 60);
|
||||
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(resp.getBody()).contains("forDurationSeconds");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAnyRule_withEmptyWebhooksAndTargets_returns400() {
|
||||
// baseValidPerExchangeRuleRequest() already produces no webhooks / no targets — that's
|
||||
// precisely the "empty webhooks + empty targets" shape this test pins as a 400.
|
||||
String body = baseValidPerExchangeRuleRequest("no-sinks");
|
||||
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(resp.getBody()).satisfiesAnyOf(
|
||||
s -> assertThat(s).contains("webhook"),
|
||||
s -> assertThat(s).contains("target"));
|
||||
}
|
||||
|
||||
// --- Unknown env returns 404 ---
|
||||
|
||||
@Test
|
||||
@@ -275,4 +330,41 @@ class AlertRuleControllerIT extends AbstractPostgresIT {
|
||||
"metric":"ERROR_RATE","comparator":"GT","threshold":0.05,"windowSeconds":60}}
|
||||
""".formatted(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a request body for a valid PER_EXCHANGE rule (baseline) — no webhooks,
|
||||
* no targets, no reNotifyMinutes, no forDurationSeconds. The controller currently
|
||||
* accepts this shape; Task 3.3 tightens that (empty sinks will 400).
|
||||
*/
|
||||
private static String baseValidPerExchangeRuleRequest(String name) {
|
||||
return """
|
||||
{"name":"%s","severity":"WARNING","conditionKind":"EXCHANGE_MATCH",
|
||||
"condition":{"kind":"EXCHANGE_MATCH","scope":{},
|
||||
"filter":{"status":"FAILED","attributes":{}},
|
||||
"fireMode":"PER_EXCHANGE"}}
|
||||
""".formatted(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Variant of {@link #baseValidPerExchangeRuleRequest(String)} that sets
|
||||
* reNotifyMinutes and/or forDurationSeconds at the top-level request. Used to pin
|
||||
* the PER_EXCHANGE cross-field validation contract (Task 3.3).
|
||||
*/
|
||||
private static String perExchangeRuleBodyWithExtras(String name,
|
||||
Integer reNotifyMinutes,
|
||||
Integer forDurationSeconds) {
|
||||
StringBuilder extras = new StringBuilder();
|
||||
if (reNotifyMinutes != null) {
|
||||
extras.append(",\"reNotifyMinutes\":").append(reNotifyMinutes);
|
||||
}
|
||||
if (forDurationSeconds != null) {
|
||||
extras.append(",\"forDurationSeconds\":").append(forDurationSeconds);
|
||||
}
|
||||
return """
|
||||
{"name":"%s","severity":"WARNING","conditionKind":"EXCHANGE_MATCH",
|
||||
"condition":{"kind":"EXCHANGE_MATCH","scope":{},
|
||||
"filter":{"status":"FAILED","attributes":{}},
|
||||
"fireMode":"PER_EXCHANGE"}%s}
|
||||
""".formatted(name, extras.toString());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user