From 94e941b02649b0dcadfe7ae9806758b523f19ce4 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Mon, 20 Apr 2026 10:51:46 +0200 Subject: [PATCH] test(alerting): decentralize @MockBean from AbstractPostgresIT + add SpringContextSmokeIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #141. AbstractPostgresIT centrally declared three @MockBean fields (clickHouseSearchIndex, clickHouseLogStore, agentRegistryService), which meant EVERY IT ran against mocks instead of the real Spring context. That masked the production crashloop — the real bean graph was never exercised by CI. - Remove the three @MockBean fields from AbstractPostgresIT. - Move @MockBean declarations onto only the specific ITs that stub method behavior (verified by grepping for when/verify calls). - ITs that don't stub CH behavior now inject the real beans. - Add SpringContextSmokeIT — @SpringBootTest with no mocks, void contextLoads(). Fails fast on declared-type / autowire-type mismatches like the one #141 fixed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../cameleer/server/app/AbstractPostgresIT.java | 9 --------- .../server/app/SpringContextSmokeIT.java | 16 ++++++++++++++++ .../app/alerting/AlertingEnvIsolationIT.java | 3 ++- .../app/alerting/AlertingFullLifecycleIT.java | 2 -- .../alerting/OutboundConnectionAllowedEnvIT.java | 3 ++- .../app/alerting/eval/AlertEvaluatorJobIT.java | 13 +++++-------- .../notify/NotificationDispatchJobIT.java | 2 ++ .../retention/AlertingRetentionJobIT.java | 2 -- 8 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 cameleer-server-app/src/test/java/com/cameleer/server/app/SpringContextSmokeIT.java diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractPostgresIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractPostgresIT.java index 0b4cd474..e3596e81 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractPostgresIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/AbstractPostgresIT.java @@ -1,10 +1,7 @@ package com.cameleer.server.app; -import com.cameleer.server.app.search.ClickHouseSearchIndex; -import com.cameleer.server.core.agent.AgentRegistryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.DynamicPropertyRegistry; @@ -17,12 +14,6 @@ import org.testcontainers.containers.PostgreSQLContainer; @ActiveProfiles("test") public abstract class AbstractPostgresIT { - // Mocked infrastructure beans required by the full application context. - // ClickHouseSearchIndex is not available in test without explicit ClickHouse wiring, - // and AgentRegistryService requires in-memory state that tests manage directly. - @MockBean(name = "clickHouseSearchIndex") protected ClickHouseSearchIndex clickHouseSearchIndex; - @MockBean protected AgentRegistryService agentRegistryService; - static final PostgreSQLContainer postgres; static final ClickHouseContainer clickhouse; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/SpringContextSmokeIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/SpringContextSmokeIT.java new file mode 100644 index 00000000..7a6f3a86 --- /dev/null +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/SpringContextSmokeIT.java @@ -0,0 +1,16 @@ +package com.cameleer.server.app; + +import org.junit.jupiter.api.Test; + +/** + * Asserts the full production Spring context loads without any @MockBean + * replacements. This is the regression test for the #141 crashloop — declared + * bean types must match autowire expectations end-to-end. + */ +class SpringContextSmokeIT extends AbstractPostgresIT { + + @Test + void contextLoads() { + // no-op: @SpringBootTest refresh is the assertion + } +} diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingEnvIsolationIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingEnvIsolationIT.java index 3473f733..3ca8d312 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingEnvIsolationIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingEnvIsolationIT.java @@ -3,6 +3,7 @@ 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.agent.AgentRegistryService; import com.cameleer.server.core.alerting.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -26,7 +27,7 @@ import static org.mockito.Mockito.when; */ class AlertingEnvIsolationIT extends AbstractPostgresIT { - // AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks. + @MockBean AgentRegistryService agentRegistryService; @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; @Autowired private TestRestTemplate restTemplate; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingFullLifecycleIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingFullLifecycleIT.java index 27514002..2fa3c742 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingFullLifecycleIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/AlertingFullLifecycleIT.java @@ -49,8 +49,6 @@ import static org.assertj.core.api.Assertions.assertThat; @TestInstance(Lifecycle.PER_CLASS) class AlertingFullLifecycleIT extends AbstractPostgresIT { - // AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks. - // Replace the alertingClock bean so we can control time in re-notify test @MockBean(name = "alertingClock") Clock alertingClock; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/OutboundConnectionAllowedEnvIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/OutboundConnectionAllowedEnvIT.java index 65268ba7..f4452ed6 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/OutboundConnectionAllowedEnvIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/OutboundConnectionAllowedEnvIT.java @@ -3,6 +3,7 @@ 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.agent.AgentRegistryService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.AfterEach; @@ -31,7 +32,7 @@ import static org.mockito.Mockito.when; */ class OutboundConnectionAllowedEnvIT extends AbstractPostgresIT { - // AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks. + @MockBean AgentRegistryService agentRegistryService; @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; @Autowired private TestRestTemplate restTemplate; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/eval/AlertEvaluatorJobIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/eval/AlertEvaluatorJobIT.java index bb123843..51f3d4f4 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/eval/AlertEvaluatorJobIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/eval/AlertEvaluatorJobIT.java @@ -3,6 +3,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.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; @@ -23,19 +24,15 @@ import static org.mockito.Mockito.when; * Integration test for {@link AlertEvaluatorJob}. *

* Uses real Postgres (Testcontainers) for the full claim→persist pipeline. - * {@code ClickHouseSearchIndex} and {@code ClickHouseLogStore} are mocked so - * {@code ExchangeMatchEvaluator} and {@code LogPatternEvaluator} wire up even - * though those concrete types are not directly registered as Spring beans. + * {@code ClickHouseLogStore} is mocked so {@code LogPatternEvaluator} wires up + * without requiring real ClickHouse query behaviour. * {@code AgentRegistryService} is mocked so tests can control which agents * are DEAD without depending on in-memory timing. */ 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 = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; - - // Control agent state per test without timing sensitivity + @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; + @MockBean AgentRegistryService agentRegistryService; @Autowired private AlertEvaluatorJob job; @Autowired private AlertRuleRepository ruleRepo; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/notify/NotificationDispatchJobIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/notify/NotificationDispatchJobIT.java index 2edd7941..b843fc4b 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/notify/NotificationDispatchJobIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/notify/NotificationDispatchJobIT.java @@ -2,6 +2,7 @@ package com.cameleer.server.app.alerting.notify; import com.cameleer.server.app.AbstractPostgresIT; import com.cameleer.server.app.search.ClickHouseLogStore; +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; @@ -35,6 +36,7 @@ import static org.mockito.Mockito.*; class NotificationDispatchJobIT extends AbstractPostgresIT { @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; + @MockBean AgentRegistryService agentRegistryService; /** Mock the dispatcher — we control outcomes per test. */ @MockBean WebhookDispatcher webhookDispatcher; diff --git a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/retention/AlertingRetentionJobIT.java b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/retention/AlertingRetentionJobIT.java index 2000d9ed..32c865e3 100644 --- a/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/retention/AlertingRetentionJobIT.java +++ b/cameleer-server-app/src/test/java/com/cameleer/server/app/alerting/retention/AlertingRetentionJobIT.java @@ -30,8 +30,6 @@ import static org.assertj.core.api.Assertions.assertThat; */ class AlertingRetentionJobIT extends AbstractPostgresIT { - // AbstractPostgresIT already declares clickHouseSearchIndex + agentRegistryService mocks. - // Declare only the additional mock needed by this test. @MockBean(name = "clickHouseLogStore") ClickHouseLogStore clickHouseLogStore; @Autowired private AlertingRetentionJob job; -- 2.49.1