test(alerting): fix duplicate @MockBean after AbstractPostgresIT centralised mocks + Plan 02 verification report
AbstractPostgresIT gained clickHouseSearchIndex and agentRegistryService mocks in Phase 9. All 14 alerting IT subclasses that re-declared the same @MockBean fields now fail with "Duplicate mock definition". Removed the redundant declarations; per-class clickHouseLogStore mock kept where needed. 120 alerting tests now pass (0 failures). Also adds docs/alerting-02-verification.md (Task 43). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package com.cameleer.server.app.alerting;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Verifies that alert instances from env-A are invisible from env-B's inbox endpoint.
|
||||
*/
|
||||
class AlertingEnvIsolationIT extends AbstractPostgresIT {
|
||||
|
||||
// AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks.
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
@Autowired private AlertInstanceRepository instanceRepo;
|
||||
|
||||
@Value("${cameleer.server.tenant.id:default}")
|
||||
private String tenantId;
|
||||
|
||||
private String operatorJwt;
|
||||
private UUID envIdA;
|
||||
private UUID envIdB;
|
||||
private String envSlugA;
|
||||
private String envSlugB;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(agentRegistryService.findAll()).thenReturn(List.of());
|
||||
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO users (user_id, provider, email) VALUES ('test-operator', 'test', 'op@test.lc') ON CONFLICT (user_id) DO NOTHING");
|
||||
|
||||
envSlugA = "iso-env-a-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
envSlugB = "iso-env-b-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
envIdA = UUID.randomUUID();
|
||||
envIdB = UUID.randomUUID();
|
||||
|
||||
jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdA, envSlugA, "ISO A");
|
||||
jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdB, envSlugB, "ISO B");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
jdbcTemplate.update("DELETE FROM alert_notifications WHERE alert_instance_id IN (SELECT id FROM alert_instances WHERE environment_id IN (?, ?))", envIdA, envIdB);
|
||||
jdbcTemplate.update("DELETE FROM alert_instances WHERE environment_id IN (?, ?)", envIdA, envIdB);
|
||||
jdbcTemplate.update("DELETE FROM environments WHERE id IN (?, ?)", envIdA, envIdB);
|
||||
jdbcTemplate.update("DELETE FROM users WHERE user_id = 'test-operator'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void alertInEnvA_isInvisibleFromEnvB() throws Exception {
|
||||
// Seed a FIRING instance in env-A targeting the operator user
|
||||
UUID instanceA = seedFiringInstance(envIdA, "test-operator");
|
||||
|
||||
// GET inbox for env-A — should see it
|
||||
ResponseEntity<String> respA = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlugA + "/alerts",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(securityHelper.authHeadersNoBody(operatorJwt)),
|
||||
String.class);
|
||||
assertThat(respA.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
JsonNode bodyA = objectMapper.readTree(respA.getBody());
|
||||
boolean foundInA = false;
|
||||
for (JsonNode node : bodyA) {
|
||||
if (instanceA.toString().equals(node.path("id").asText())) {
|
||||
foundInA = true;
|
||||
}
|
||||
}
|
||||
assertThat(foundInA).as("instance from env-A should appear in env-A inbox").isTrue();
|
||||
|
||||
// GET inbox for env-B — should NOT see env-A's instance
|
||||
ResponseEntity<String> respB = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlugB + "/alerts",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<>(securityHelper.authHeadersNoBody(operatorJwt)),
|
||||
String.class);
|
||||
assertThat(respB.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
JsonNode bodyB = objectMapper.readTree(respB.getBody());
|
||||
for (JsonNode node : bodyB) {
|
||||
assertThat(node.path("id").asText())
|
||||
.as("env-A instance must not appear in env-B inbox")
|
||||
.isNotEqualTo(instanceA.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private UUID seedFiringInstance(UUID envId, String userId) {
|
||||
UUID ruleId = UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO users (user_id, provider, email) VALUES (?, 'test', ?) ON CONFLICT (user_id) DO NOTHING",
|
||||
userId, userId + "@test.lc");
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_rules
|
||||
(id, environment_id, name, severity, condition_kind, condition,
|
||||
notification_title_tmpl, notification_message_tmpl, created_by, updated_by)
|
||||
VALUES (?, ?, 'iso-rule', 'WARNING', 'AGENT_STATE', '{}'::jsonb, 't', 'm', ?, ?)
|
||||
""", ruleId, envId, userId, userId);
|
||||
|
||||
UUID instanceId = UUID.randomUUID();
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_instances
|
||||
(id, rule_id, rule_snapshot, environment_id, state, severity,
|
||||
fired_at, silenced, context, title, message,
|
||||
target_user_ids, target_group_ids, target_role_names)
|
||||
VALUES (?, ?, ?::jsonb, ?, 'FIRING'::alert_state_enum, 'WARNING'::severity_enum,
|
||||
now(), false, '{}'::jsonb, 'T', 'M',
|
||||
ARRAY[?]::text[], '{}'::uuid[], '{}'::text[])
|
||||
""",
|
||||
instanceId, ruleId,
|
||||
"{\"name\":\"iso-rule\",\"id\":\"" + ruleId + "\"}",
|
||||
envId, userId);
|
||||
return instanceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
package com.cameleer.server.app.alerting;
|
||||
|
||||
import com.cameleer.common.model.LogEntry;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.alerting.eval.AlertEvaluatorJob;
|
||||
import com.cameleer.server.app.alerting.notify.NotificationDispatchJob;
|
||||
import com.cameleer.server.app.outbound.crypto.SecretCipher;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.cameleer.server.core.ingestion.BufferedLogEntry;
|
||||
import com.cameleer.server.core.outbound.OutboundConnectionRepository;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.tomakehurst.wiremock.WireMockServer;
|
||||
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Canary integration test — exercises the full alerting lifecycle end-to-end:
|
||||
* fire → notify → ack → silence → re-fire (suppressed) → resolve → rule delete.
|
||||
*
|
||||
* Uses real Postgres (Testcontainers) and real ClickHouse for log seeding.
|
||||
* WireMock provides the webhook target.
|
||||
*/
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class AlertingFullLifecycleIT extends AbstractPostgresIT {
|
||||
|
||||
// AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks.
|
||||
|
||||
// ── Spring beans ──────────────────────────────────────────────────────────
|
||||
|
||||
@Autowired private AlertEvaluatorJob evaluatorJob;
|
||||
@Autowired private NotificationDispatchJob dispatchJob;
|
||||
@Autowired private AlertRuleRepository ruleRepo;
|
||||
@Autowired private AlertInstanceRepository instanceRepo;
|
||||
@Autowired private AlertNotificationRepository notificationRepo;
|
||||
@Autowired private AlertSilenceRepository silenceRepo;
|
||||
@Autowired private OutboundConnectionRepository outboundRepo;
|
||||
@Autowired private ClickHouseLogStore logStore;
|
||||
@Autowired private SecretCipher secretCipher;
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
@Value("${cameleer.server.tenant.id:default}")
|
||||
private String tenantId;
|
||||
|
||||
// ── Test state shared across @Test methods ─────────────────────────────────
|
||||
|
||||
private WireMockServer wm;
|
||||
|
||||
private String operatorJwt;
|
||||
private String envSlug;
|
||||
private UUID envId;
|
||||
private UUID ruleId;
|
||||
private UUID connId;
|
||||
private UUID instanceId; // filled after first FIRING
|
||||
|
||||
// ── Setup / teardown ──────────────────────────────────────────────────────
|
||||
|
||||
@BeforeAll
|
||||
void seedFixtures() throws Exception {
|
||||
wm = new WireMockServer(WireMockConfiguration.options()
|
||||
.httpDisabled(true)
|
||||
.dynamicHttpsPort());
|
||||
wm.start();
|
||||
// ClickHouse schema is auto-initialized by ClickHouseSchemaInitializer on Spring context startup.
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
|
||||
// Seed operator user in Postgres
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO users (user_id, provider, email, display_name) VALUES ('test-operator', 'test', 'op@lc.test', 'Op') ON CONFLICT (user_id) DO NOTHING");
|
||||
|
||||
// Seed environment
|
||||
envSlug = "lc-env-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
envId = UUID.randomUUID();
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)",
|
||||
envId, envSlug, "LC Env");
|
||||
|
||||
// Seed outbound connection (WireMock HTTPS, TRUST_ALL, with HMAC secret)
|
||||
connId = UUID.randomUUID();
|
||||
String hmacCiphertext = secretCipher.encrypt("test-hmac-secret");
|
||||
String webhookUrl = "https://localhost:" + wm.httpsPort() + "/webhook";
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO outbound_connections" +
|
||||
" (id, tenant_id, name, url, method, tls_trust_mode, tls_ca_pem_paths," +
|
||||
" hmac_secret_ciphertext, auth_kind, auth_config, default_headers," +
|
||||
" allowed_environment_ids, created_by, updated_by)" +
|
||||
" VALUES (?, ?, 'lc-webhook', ?," +
|
||||
" 'POST'::outbound_method_enum," +
|
||||
" 'TRUST_ALL'::trust_mode_enum," +
|
||||
" '[]'::jsonb," +
|
||||
" ?, 'NONE'::outbound_auth_kind_enum, '{}'::jsonb, '{}'::jsonb," +
|
||||
" '{}'," +
|
||||
" 'test-operator', 'test-operator')",
|
||||
connId, tenantId, webhookUrl, hmacCiphertext);
|
||||
|
||||
// Seed alert rule (LOG_PATTERN, forDurationSeconds=0, threshold=0 so >=1 log fires immediately)
|
||||
ruleId = UUID.randomUUID();
|
||||
UUID webhookBindingId = UUID.randomUUID();
|
||||
String webhooksJson = objectMapper.writeValueAsString(List.of(
|
||||
Map.of("id", webhookBindingId.toString(),
|
||||
"outboundConnectionId", connId.toString())));
|
||||
String conditionJson = objectMapper.writeValueAsString(Map.of(
|
||||
"kind", "LOG_PATTERN",
|
||||
"scope", Map.of("appSlug", "lc-app"),
|
||||
"level", "ERROR",
|
||||
"pattern", "TimeoutException",
|
||||
"threshold", 0,
|
||||
"windowSeconds", 300));
|
||||
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_rules
|
||||
(id, environment_id, name, severity, enabled,
|
||||
condition_kind, condition,
|
||||
evaluation_interval_seconds, for_duration_seconds,
|
||||
notification_title_tmpl, notification_message_tmpl,
|
||||
webhooks, next_evaluation_at,
|
||||
created_by, updated_by)
|
||||
VALUES (?, ?, 'lc-timeout-rule', 'WARNING'::severity_enum, true,
|
||||
'LOG_PATTERN'::condition_kind_enum, ?::jsonb,
|
||||
60, 0,
|
||||
'Alert: {{rule.name}}', 'Instance {{alert.id}} fired',
|
||||
?::jsonb, now() - interval '1 second',
|
||||
'test-operator', 'test-operator')
|
||||
""",
|
||||
ruleId, envId, conditionJson, webhooksJson);
|
||||
|
||||
// Seed alert_rule_targets so the instance shows up in inbox
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_rule_targets (id, rule_id, target_kind, target_id) VALUES (gen_random_uuid(), ?, 'USER'::target_kind_enum, 'test-operator') ON CONFLICT (rule_id, target_kind, target_id) DO NOTHING",
|
||||
ruleId);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
void cleanupFixtures() {
|
||||
if (wm != null) wm.stop();
|
||||
jdbcTemplate.update("DELETE FROM alert_silences WHERE environment_id = ?", envId);
|
||||
jdbcTemplate.update("DELETE FROM alert_notifications WHERE alert_instance_id IN (SELECT id FROM alert_instances WHERE environment_id = ?)", envId);
|
||||
jdbcTemplate.update("DELETE FROM alert_instances WHERE environment_id = ?", envId);
|
||||
jdbcTemplate.update("DELETE FROM alert_rule_targets WHERE rule_id = ?", ruleId);
|
||||
jdbcTemplate.update("DELETE FROM alert_rules WHERE id = ?", ruleId);
|
||||
jdbcTemplate.update("DELETE FROM outbound_connections WHERE id = ?", connId);
|
||||
jdbcTemplate.update("DELETE FROM environments WHERE id = ?", envId);
|
||||
jdbcTemplate.update("DELETE FROM users WHERE user_id = 'test-operator'");
|
||||
}
|
||||
|
||||
// ── Test methods (ordered) ────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void step1_seedLogAndEvaluate_createsFireInstance() throws Exception {
|
||||
// Stub WireMock to return 200
|
||||
wm.stubFor(post("/webhook").willReturn(aResponse().withStatus(200).withBody("accepted")));
|
||||
|
||||
// Seed a matching log into ClickHouse
|
||||
seedMatchingLog();
|
||||
|
||||
// Tick evaluator
|
||||
evaluatorJob.tick();
|
||||
|
||||
// Assert FIRING instance created
|
||||
List<AlertInstance> instances = instanceRepo.listForInbox(
|
||||
envId, List.of(), "test-operator", List.of("OPERATOR"), 10);
|
||||
assertThat(instances).hasSize(1);
|
||||
assertThat(instances.get(0).state()).isEqualTo(AlertState.FIRING);
|
||||
assertThat(instances.get(0).ruleId()).isEqualTo(ruleId);
|
||||
instanceId = instances.get(0).id();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void step2_dispatchJob_deliversWebhook() throws Exception {
|
||||
assertThat(instanceId).isNotNull();
|
||||
|
||||
// Tick dispatcher
|
||||
dispatchJob.tick();
|
||||
|
||||
// Assert DELIVERED notification
|
||||
List<AlertNotification> notifs = notificationRepo.listForInstance(instanceId);
|
||||
assertThat(notifs).hasSize(1);
|
||||
assertThat(notifs.get(0).status()).isEqualTo(NotificationStatus.DELIVERED);
|
||||
assertThat(notifs.get(0).lastResponseStatus()).isEqualTo(200);
|
||||
|
||||
// WireMock received exactly one POST with HMAC header
|
||||
wm.verify(1, postRequestedFor(urlEqualTo("/webhook"))
|
||||
.withHeader("X-Cameleer-Signature", matching("sha256=[0-9a-f]{64}")));
|
||||
|
||||
// Body should contain rule name
|
||||
wm.verify(postRequestedFor(urlEqualTo("/webhook"))
|
||||
.withRequestBody(containing("lc-timeout-rule")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
void step3_ack_transitionsToAcknowledged() throws Exception {
|
||||
assertThat(instanceId).isNotNull();
|
||||
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/" + instanceId + "/ack",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
JsonNode body = objectMapper.readTree(resp.getBody());
|
||||
assertThat(body.path("state").asText()).isEqualTo("ACKNOWLEDGED");
|
||||
|
||||
// DB state
|
||||
AlertInstance updated = instanceRepo.findById(instanceId).orElseThrow();
|
||||
assertThat(updated.state()).isEqualTo(AlertState.ACKNOWLEDGED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
void step4_silence_suppressesSubsequentNotification() throws Exception {
|
||||
// Create a silence matching this rule
|
||||
String silenceBody = objectMapper.writeValueAsString(Map.of(
|
||||
"matcher", Map.of("ruleId", ruleId.toString()),
|
||||
"reason", "lifecycle-test-silence",
|
||||
"startsAt", Instant.now().minusSeconds(10).toString(),
|
||||
"endsAt", Instant.now().plusSeconds(3600).toString()
|
||||
));
|
||||
ResponseEntity<String> silenceResp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/silences",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(silenceBody, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
assertThat(silenceResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
|
||||
// Reset WireMock counter
|
||||
wm.resetRequests();
|
||||
|
||||
// Inject a fresh PENDING notification for the existing instance — simulates a re-notification
|
||||
// attempt that the dispatcher should silently suppress.
|
||||
UUID newNotifId = UUID.randomUUID();
|
||||
// Look up the webhook_id from the existing notification for this instance
|
||||
UUID existingWebhookId = jdbcTemplate.queryForObject(
|
||||
"SELECT webhook_id FROM alert_notifications WHERE alert_instance_id = ? LIMIT 1",
|
||||
UUID.class, instanceId);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_notifications" +
|
||||
" (id, alert_instance_id, outbound_connection_id, webhook_id," +
|
||||
" status, attempts, next_attempt_at, payload, created_at)" +
|
||||
" VALUES (?, ?, ?, ?," +
|
||||
" 'PENDING'::notification_status_enum, 0, now() - interval '1 second'," +
|
||||
" '{}'::jsonb, now())",
|
||||
newNotifId, instanceId, connId, existingWebhookId);
|
||||
|
||||
// Tick dispatcher — the silence should suppress the notification
|
||||
dispatchJob.tick();
|
||||
|
||||
// The injected notification should now be FAILED with snippet "silenced"
|
||||
List<AlertNotification> notifs = notificationRepo.listForInstance(instanceId);
|
||||
boolean foundSilenced = notifs.stream()
|
||||
.anyMatch(n -> NotificationStatus.FAILED.equals(n.status())
|
||||
&& n.lastResponseSnippet() != null
|
||||
&& n.lastResponseSnippet().contains("silenced"));
|
||||
assertThat(foundSilenced).as("At least one notification should be silenced").isTrue();
|
||||
|
||||
// WireMock should NOT have received a new POST
|
||||
wm.verify(0, postRequestedFor(urlEqualTo("/webhook")));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(5)
|
||||
void step5_deleteRule_nullifiesRuleIdButPreservesSnapshot() throws Exception {
|
||||
// Delete the rule via DELETE endpoint
|
||||
ResponseEntity<String> deleteResp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlug + "/alerts/rules/" + ruleId,
|
||||
HttpMethod.DELETE,
|
||||
new HttpEntity<>(securityHelper.authHeadersNoBody(operatorJwt)),
|
||||
String.class);
|
||||
assertThat(deleteResp.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
|
||||
|
||||
// Rule should be gone from DB
|
||||
assertThat(ruleRepo.findById(ruleId)).isEmpty();
|
||||
|
||||
// Existing alert instances should have rule_id = NULL but rule_snapshot still contains name
|
||||
List<AlertInstance> remaining = instanceRepo.listForInbox(
|
||||
envId, List.of(), "test-operator", List.of("OPERATOR"), 10);
|
||||
assertThat(remaining).isNotEmpty();
|
||||
for (AlertInstance inst : remaining) {
|
||||
// rule_id should now be null (FK ON DELETE SET NULL)
|
||||
assertThat(inst.ruleId()).isNull();
|
||||
// rule_snapshot should still contain the rule name
|
||||
assertThat(inst.ruleSnapshot()).containsKey("name");
|
||||
assertThat(inst.ruleSnapshot().get("name").toString()).contains("lc-timeout-rule");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void seedMatchingLog() {
|
||||
LogEntry entry = new LogEntry(
|
||||
Instant.now(),
|
||||
"ERROR",
|
||||
"com.example.OrderService",
|
||||
"java.net.SocketTimeoutException: TimeoutException after 5000ms",
|
||||
"main",
|
||||
null,
|
||||
Map.of()
|
||||
);
|
||||
logStore.insertBufferedBatch(List.of(
|
||||
new BufferedLogEntry(tenantId, envSlug, "lc-agent-01", "lc-app", entry)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package com.cameleer.server.app.alerting;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Verifies the outbound connection allowed-environment guard end-to-end:
|
||||
* <ol>
|
||||
* <li>Rule in env-B referencing a connection restricted to env-A → 422.</li>
|
||||
* <li>Rule in env-A referencing the same connection → 201.</li>
|
||||
* <li>Narrowing the connection's allowed envs to env-C (removing env-A) while
|
||||
* a rule in env-A still references it → 409 via PUT /admin/outbound-connections/{id}.</li>
|
||||
* </ol>
|
||||
*/
|
||||
class OutboundConnectionAllowedEnvIT extends AbstractPostgresIT {
|
||||
|
||||
// AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks.
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
@Autowired private TestSecurityHelper securityHelper;
|
||||
@Autowired private ObjectMapper objectMapper;
|
||||
|
||||
@Value("${cameleer.server.tenant.id:default}")
|
||||
private String tenantId;
|
||||
|
||||
private String adminJwt;
|
||||
private String operatorJwt;
|
||||
|
||||
private UUID envIdA;
|
||||
private UUID envIdB;
|
||||
private UUID envIdC;
|
||||
private String envSlugA;
|
||||
private String envSlugB;
|
||||
private UUID connId;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
when(agentRegistryService.findAll()).thenReturn(List.of());
|
||||
|
||||
adminJwt = securityHelper.adminToken();
|
||||
operatorJwt = securityHelper.operatorToken();
|
||||
|
||||
jdbcTemplate.update("INSERT INTO users (user_id, provider, email) VALUES ('test-admin', 'test', 'adm@test.lc') ON CONFLICT (user_id) DO NOTHING");
|
||||
jdbcTemplate.update("INSERT INTO users (user_id, provider, email) VALUES ('test-operator', 'test', 'op@test.lc') ON CONFLICT (user_id) DO NOTHING");
|
||||
|
||||
envSlugA = "conn-env-a-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
envSlugB = "conn-env-b-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
String envSlugC = "conn-env-c-" + UUID.randomUUID().toString().substring(0, 6);
|
||||
envIdA = UUID.randomUUID();
|
||||
envIdB = UUID.randomUUID();
|
||||
envIdC = UUID.randomUUID();
|
||||
|
||||
jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdA, envSlugA, "A");
|
||||
jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdB, envSlugB, "B");
|
||||
jdbcTemplate.update("INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", envIdC, envSlugC, "C");
|
||||
|
||||
// Create outbound connection restricted to env-A
|
||||
String connBody = objectMapper.writeValueAsString(java.util.Map.of(
|
||||
"name", "env-a-only-conn-" + UUID.randomUUID().toString().substring(0, 6),
|
||||
"url", "https://httpbin.org/post",
|
||||
"method", "POST",
|
||||
"tlsTrustMode", "SYSTEM_DEFAULT",
|
||||
"auth", java.util.Map.of(),
|
||||
"allowedEnvironmentIds", List.of(envIdA.toString())
|
||||
));
|
||||
ResponseEntity<String> connResp = restTemplate.exchange(
|
||||
"/api/v1/admin/outbound-connections",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(connBody, securityHelper.authHeaders(adminJwt)),
|
||||
String.class);
|
||||
assertThat(connResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
connId = UUID.fromString(objectMapper.readTree(connResp.getBody()).path("id").asText());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
jdbcTemplate.update("DELETE FROM alert_rules WHERE environment_id IN (?, ?, ?)", envIdA, envIdB, envIdC);
|
||||
jdbcTemplate.update("DELETE FROM outbound_connections WHERE id = ?", connId);
|
||||
jdbcTemplate.update("DELETE FROM environments WHERE id IN (?, ?, ?)", envIdA, envIdB, envIdC);
|
||||
jdbcTemplate.update("DELETE FROM users WHERE user_id IN ('test-admin', 'test-operator')");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ruleInEnvB_referencingEnvAOnlyConnection_returns422() {
|
||||
String body = ruleBodyWithConnection("envb-rule", connId);
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlugB + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ruleInEnvA_referencingEnvAOnlyConnection_returns201() throws Exception {
|
||||
String body = ruleBodyWithConnection("enva-rule", connId);
|
||||
ResponseEntity<String> resp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlugA + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(body, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
|
||||
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void narrowingConnectionToEnvC_whileRuleInEnvA_references_returns409() throws Exception {
|
||||
// First create a rule in env-A that references the connection
|
||||
String ruleBody = ruleBodyWithConnection("narrowing-guard-rule", connId);
|
||||
ResponseEntity<String> ruleResp = restTemplate.exchange(
|
||||
"/api/v1/environments/" + envSlugA + "/alerts/rules",
|
||||
HttpMethod.POST,
|
||||
new HttpEntity<>(ruleBody, securityHelper.authHeaders(operatorJwt)),
|
||||
String.class);
|
||||
assertThat(ruleResp.getStatusCode()).isEqualTo(HttpStatus.CREATED);
|
||||
|
||||
// Now update the connection to only allow env-C (removing env-A)
|
||||
String updateBody = objectMapper.writeValueAsString(java.util.Map.of(
|
||||
"name", "env-a-only-conn-narrowed",
|
||||
"url", "https://httpbin.org/post",
|
||||
"method", "POST",
|
||||
"tlsTrustMode", "SYSTEM_DEFAULT",
|
||||
"auth", java.util.Map.of(),
|
||||
"allowedEnvironmentIds", List.of(envIdC.toString()) // removed env-A
|
||||
));
|
||||
|
||||
ResponseEntity<String> updateResp = restTemplate.exchange(
|
||||
"/api/v1/admin/outbound-connections/" + connId,
|
||||
HttpMethod.PUT,
|
||||
new HttpEntity<>(updateBody, securityHelper.authHeaders(adminJwt)),
|
||||
String.class);
|
||||
|
||||
// The guard should fire: env-A was removed but a rule in env-A still references it
|
||||
assertThat(updateResp.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static String ruleBodyWithConnection(String name, UUID connectionId) {
|
||||
return """
|
||||
{"name":"%s","severity":"WARNING","conditionKind":"ROUTE_METRIC",
|
||||
"condition":{"kind":"ROUTE_METRIC","scope":{},
|
||||
"metric":"ERROR_RATE","comparator":"GT","threshold":0.05,"windowSeconds":60},
|
||||
"webhooks":[{"outboundConnectionId":"%s"}]}
|
||||
""".formatted(name, connectionId);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.cameleer.server.app.alerting.controller;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.AlertInstance;
|
||||
import com.cameleer.server.core.alerting.AlertInstanceRepository;
|
||||
import com.cameleer.server.core.alerting.AlertReadRepository;
|
||||
@@ -30,7 +29,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AlertControllerIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.cameleer.server.app.alerting.controller;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.AlertInstance;
|
||||
import com.cameleer.server.core.alerting.AlertInstanceRepository;
|
||||
import com.cameleer.server.core.alerting.AlertNotification;
|
||||
@@ -32,7 +31,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AlertNotificationControllerIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.cameleer.server.app.alerting.controller;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.admin.AuditRepository;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -26,7 +25,6 @@ class AlertRuleControllerIT extends AbstractPostgresIT {
|
||||
|
||||
// ExchangeMatchEvaluator and LogPatternEvaluator depend on these concrete beans
|
||||
// (not the SearchIndex/LogIndex interfaces). Mock them so the context wires up.
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.cameleer.server.app.alerting.controller;
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.TestSecurityHelper;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -25,7 +24,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AlertSilenceControllerIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private TestRestTemplate restTemplate;
|
||||
|
||||
@@ -2,9 +2,7 @@ package com.cameleer.server.app.alerting.eval;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.agent.AgentInfo;
|
||||
import com.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer.server.core.agent.AgentState;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -35,11 +33,9 @@ class AlertEvaluatorJobIT extends AbstractPostgresIT {
|
||||
|
||||
// Replace the named beans so ExchangeMatchEvaluator / LogPatternEvaluator can wire their
|
||||
// concrete-type constructor args without duplicating the SearchIndex / LogIndex beans.
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
// Control agent state per test without timing sensitivity
|
||||
@MockBean AgentRegistryService agentRegistryService;
|
||||
|
||||
@Autowired private AlertEvaluatorJob job;
|
||||
@Autowired private AlertRuleRepository ruleRepo;
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.cameleer.server.app.alerting.notify;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.cameleer.server.core.http.TrustMode;
|
||||
import com.cameleer.server.core.outbound.OutboundAuth;
|
||||
@@ -36,9 +34,7 @@ import static org.mockito.Mockito.*;
|
||||
*/
|
||||
class NotificationDispatchJobIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
@MockBean AgentRegistryService agentRegistryService;
|
||||
|
||||
/** Mock the dispatcher — we control outcomes per test. */
|
||||
@MockBean WebhookDispatcher webhookDispatcher;
|
||||
|
||||
@@ -2,21 +2,17 @@ package com.cameleer.server.app.alerting.retention;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.agent.AgentRegistryService;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -34,9 +30,9 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
*/
|
||||
class AlertingRetentionJobIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
@MockBean AgentRegistryService agentRegistryService;
|
||||
// AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks.
|
||||
// Declare only the additional mock needed by this test.
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
@Autowired private AlertingRetentionJob job;
|
||||
@Autowired private AlertInstanceRepository instanceRepo;
|
||||
@@ -182,42 +178,43 @@ class AlertingRetentionJobIT extends AbstractPostgresIT {
|
||||
|
||||
private UUID seedResolvedInstance(Instant resolvedAt) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_instances
|
||||
(id, rule_id, rule_snapshot, environment_id, state, severity,
|
||||
fired_at, resolved_at, silenced, context, title, message,
|
||||
target_user_ids, target_group_ids, target_role_names)
|
||||
VALUES (?, ?, '{}'::jsonb, ?, 'RESOLVED'::alert_state_enum, 'WARNING'::severity_enum,
|
||||
?, ?, false, '{}'::jsonb, 'T', 'M',
|
||||
'{}', '{}', '{}')
|
||||
""",
|
||||
id, ruleId, envId, resolvedAt, resolvedAt);
|
||||
Timestamp ts = Timestamp.from(resolvedAt);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_instances" +
|
||||
" (id, rule_id, rule_snapshot, environment_id, state, severity," +
|
||||
" fired_at, resolved_at, silenced, context, title, message," +
|
||||
" target_user_ids, target_group_ids, target_role_names)" +
|
||||
" VALUES (?, ?, '{}'::jsonb, ?, 'RESOLVED'::alert_state_enum, 'WARNING'::severity_enum," +
|
||||
" ?, ?, false, '{}'::jsonb, 'T', 'M'," +
|
||||
" '{}'::text[], '{}'::uuid[], '{}'::text[])",
|
||||
id, ruleId, envId, ts, ts);
|
||||
return id;
|
||||
}
|
||||
|
||||
private UUID seedFiringInstance(Instant firedAt) {
|
||||
UUID id = UUID.randomUUID();
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_instances
|
||||
(id, rule_id, rule_snapshot, environment_id, state, severity,
|
||||
fired_at, silenced, context, title, message,
|
||||
target_user_ids, target_group_ids, target_role_names)
|
||||
VALUES (?, ?, '{}'::jsonb, ?, 'FIRING'::alert_state_enum, 'WARNING'::severity_enum,
|
||||
?, false, '{}'::jsonb, 'T', 'M',
|
||||
'{}', '{}', '{}')
|
||||
""",
|
||||
id, ruleId, envId, firedAt);
|
||||
Timestamp ts = Timestamp.from(firedAt);
|
||||
jdbcTemplate.update(
|
||||
"INSERT INTO alert_instances" +
|
||||
" (id, rule_id, rule_snapshot, environment_id, state, severity," +
|
||||
" fired_at, silenced, context, title, message," +
|
||||
" target_user_ids, target_group_ids, target_role_names)" +
|
||||
" VALUES (?, ?, '{}'::jsonb, ?, 'FIRING'::alert_state_enum, 'WARNING'::severity_enum," +
|
||||
" ?, false, '{}'::jsonb, 'T', 'M'," +
|
||||
" '{}'::text[], '{}'::uuid[], '{}'::text[])",
|
||||
id, ruleId, envId, ts);
|
||||
return id;
|
||||
}
|
||||
|
||||
private UUID seedNotification(UUID alertInstanceId, NotificationStatus status, Instant createdAt) {
|
||||
UUID id = UUID.randomUUID();
|
||||
Timestamp ts = Timestamp.from(createdAt);
|
||||
jdbcTemplate.update("""
|
||||
INSERT INTO alert_notifications
|
||||
(id, alert_instance_id, status, attempts, next_attempt_at, payload, created_at)
|
||||
VALUES (?, ?, ?::notification_status_enum, 0, ?, '{}'::jsonb, ?)
|
||||
""",
|
||||
id, alertInstanceId, status.name(), createdAt, createdAt);
|
||||
id, alertInstanceId, status.name(), ts, ts);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -19,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PostgresAlertInstanceRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private PostgresAlertInstanceRepository repo;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -19,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PostgresAlertNotificationRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private PostgresAlertNotificationRepository repo;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -16,7 +15,6 @@ import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
class PostgresAlertReadRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private PostgresAlertReadRepository repo;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.*;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -19,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PostgresAlertRuleRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private PostgresAlertRuleRepository repo;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import com.cameleer.server.core.alerting.AlertSilence;
|
||||
import com.cameleer.server.core.alerting.SilenceMatcher;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -19,7 +18,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PostgresAlertSilenceRepositoryIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private PostgresAlertSilenceRepository repo;
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.cameleer.server.app.alerting.storage;
|
||||
|
||||
import com.cameleer.server.app.AbstractPostgresIT;
|
||||
import com.cameleer.server.app.search.ClickHouseLogStore;
|
||||
import com.cameleer.server.app.search.ClickHouseSearchIndex;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -10,7 +9,6 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class V12MigrationIT extends AbstractPostgresIT {
|
||||
|
||||
@MockBean(name = "clickHouseSearchIndex") ClickHouseSearchIndex clickHouseSearchIndex;
|
||||
@MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore;
|
||||
|
||||
private java.util.UUID testEnvId;
|
||||
|
||||
Reference in New Issue
Block a user