test(alerting): decentralize @MockBean + add SpringContextSmokeIT (follow-up to #141) #142

Merged
hsiegeln merged 1 commits from fix/alerting-test-hygiene into main 2026-04-20 10:57:58 +02:00
8 changed files with 27 additions and 23 deletions

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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}.
* <p>
* 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;

View File

@@ -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;

View File

@@ -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;