diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/alerting/config/AlertingBeanConfig.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/alerting/config/AlertingBeanConfig.java new file mode 100644 index 00000000..c14057eb --- /dev/null +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/alerting/config/AlertingBeanConfig.java @@ -0,0 +1,17 @@ +package com.cameleer.server.app.alerting.config; + +import com.cameleer.server.app.alerting.storage.PostgresAlertRuleRepository; +import com.cameleer.server.core.alerting.AlertRuleRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +public class AlertingBeanConfig { + + @Bean + public AlertRuleRepository alertRuleRepository(JdbcTemplate jdbc, ObjectMapper om) { + return new PostgresAlertRuleRepository(jdbc, om); + } +} diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/OutboundConnectionServiceImpl.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/OutboundConnectionServiceImpl.java index 6ce204c2..328a68e6 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/OutboundConnectionServiceImpl.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/OutboundConnectionServiceImpl.java @@ -1,5 +1,6 @@ package com.cameleer.server.app.outbound; +import com.cameleer.server.core.alerting.AlertRuleRepository; import com.cameleer.server.core.outbound.OutboundConnection; import com.cameleer.server.core.outbound.OutboundConnectionRepository; import com.cameleer.server.core.outbound.OutboundConnectionService; @@ -13,10 +14,15 @@ import java.util.UUID; public class OutboundConnectionServiceImpl implements OutboundConnectionService { private final OutboundConnectionRepository repo; + private final AlertRuleRepository ruleRepo; private final String tenantId; - public OutboundConnectionServiceImpl(OutboundConnectionRepository repo, String tenantId) { + public OutboundConnectionServiceImpl( + OutboundConnectionRepository repo, + AlertRuleRepository ruleRepo, + String tenantId) { this.repo = repo; + this.ruleRepo = ruleRepo; this.tenantId = tenantId; } @@ -91,8 +97,7 @@ public class OutboundConnectionServiceImpl implements OutboundConnectionService @Override public List rulesReferencing(UUID id) { - // Plan 01 stub. Plan 02 will wire this to AlertRuleRepository. - return List.of(); + return ruleRepo.findRuleIdsByOutboundConnectionId(id); } private void assertNameUnique(String name, UUID excludingId) { diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/config/OutboundBeanConfig.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/config/OutboundBeanConfig.java index a4e9d8c8..bea1fab5 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/config/OutboundBeanConfig.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/outbound/config/OutboundBeanConfig.java @@ -3,6 +3,7 @@ package com.cameleer.server.app.outbound.config; import com.cameleer.server.app.outbound.OutboundConnectionServiceImpl; import com.cameleer.server.app.outbound.crypto.SecretCipher; import com.cameleer.server.app.outbound.storage.PostgresOutboundConnectionRepository; +import com.cameleer.server.core.alerting.AlertRuleRepository; import com.cameleer.server.core.outbound.OutboundConnectionRepository; import com.cameleer.server.core.outbound.OutboundConnectionService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -29,7 +30,8 @@ public class OutboundBeanConfig { @Bean public OutboundConnectionService outboundConnectionService( OutboundConnectionRepository repo, + AlertRuleRepository ruleRepo, @Value("${cameleer.server.tenant.id:default}") String tenantId) { - return new OutboundConnectionServiceImpl(repo, tenantId); + return new OutboundConnectionServiceImpl(repo, ruleRepo, tenantId); } } diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/outbound/OutboundConnectionServiceRulesReferencingIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/outbound/OutboundConnectionServiceRulesReferencingIT.java new file mode 100644 index 00000000..4adc7c87 --- /dev/null +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/outbound/OutboundConnectionServiceRulesReferencingIT.java @@ -0,0 +1,79 @@ +package com.cameleer.server.app.outbound; + +import com.cameleer.server.app.AbstractPostgresIT; +import com.cameleer.server.app.alerting.storage.PostgresAlertRuleRepository; +import com.cameleer.server.core.alerting.*; +import com.cameleer.server.core.http.TrustMode; +import com.cameleer.server.core.outbound.*; +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.web.server.ResponseStatusException; + +import java.time.Instant; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class OutboundConnectionServiceRulesReferencingIT extends AbstractPostgresIT { + + @Autowired OutboundConnectionService service; + @Autowired OutboundConnectionRepository repo; + + private UUID envId; + private UUID connId; + private UUID ruleId; + private PostgresAlertRuleRepository ruleRepo; + + @BeforeEach + void seed() { + ruleRepo = new PostgresAlertRuleRepository(jdbcTemplate, new ObjectMapper()); + envId = UUID.randomUUID(); + jdbcTemplate.update( + "INSERT INTO environments (id, slug, display_name) VALUES (?, ?, ?)", + envId, "env-" + UUID.randomUUID(), "Test Env"); + jdbcTemplate.update( + "INSERT INTO users (user_id, provider, email) VALUES ('u-ref', 'local', 'a@b.test')" + + " ON CONFLICT (user_id) DO NOTHING"); + + var c = repo.save(new OutboundConnection( + UUID.randomUUID(), "default", "conn-" + UUID.randomUUID(), null, + "https://example.test", OutboundMethod.POST, + Map.of(), null, TrustMode.SYSTEM_DEFAULT, List.of(), null, + new OutboundAuth.None(), List.of(), + Instant.now(), "u-ref", Instant.now(), "u-ref")); + connId = c.id(); + + ruleId = UUID.randomUUID(); + var rule = new AlertRule( + ruleId, envId, "r", null, AlertSeverity.WARNING, true, + ConditionKind.AGENT_STATE, + new AgentStateCondition(new AlertScope(null, null, null), "DEAD", 60), + 60, 0, 60, "t", "m", + List.of(new WebhookBinding(UUID.randomUUID(), connId, null, Map.of())), + List.of(), Instant.now(), null, null, Map.of(), + Instant.now(), "u-ref", Instant.now(), "u-ref"); + ruleRepo.save(rule); + } + + @AfterEach + void cleanup() { + 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 = 'u-ref'"); + } + + @Test + void deleteConnectionReferencedByRuleReturns409() { + assertThat(service.rulesReferencing(connId)).hasSize(1); + assertThatThrownBy(() -> service.delete(connId, "u-ref")) + .isInstanceOf(ResponseStatusException.class) + .hasMessageContaining("referenced by rules"); + } +}