feat(ft546): add isolated shadow process
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
package com.ruoyi.shadow;
|
||||
|
||||
import com.ruoyi.project.shadowtest.ForbiddenFactoryBean;
|
||||
import com.ruoyi.shadow.startup.ShadowStartupReport;
|
||||
import com.ruoyi.shadow.web.ShadowStatusController;
|
||||
import com.ruoyi.wcs.application.dispatch.DispatchCoordinator;
|
||||
import com.ruoyi.wcs.port.out.DeviceCommandGateway;
|
||||
import com.ruoyi.wcs.port.out.WmsNotificationGateway;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
class Ft546ShadowApplicationTest {
|
||||
private final WebApplicationContextRunner contextRunner =
|
||||
new WebApplicationContextRunner()
|
||||
.withInitializer(
|
||||
new ConfigDataApplicationContextInitializer())
|
||||
.withUserConfiguration(Ft546ShadowApplication.class);
|
||||
|
||||
@Test
|
||||
void explicitReadOnlyShadowConfigurationStartsTheNarrowContext() {
|
||||
configured(contextRunner).run(context -> {
|
||||
assertFalse(context.getStartupFailure() != null);
|
||||
assertEquals("127.0.0.1",
|
||||
context.getEnvironment().getProperty("server.address"));
|
||||
assertEquals("18091",
|
||||
context.getEnvironment().getProperty("server.port"));
|
||||
assertEquals(ShadowStartupReport.Status.STARTING,
|
||||
context.getBean(ShadowStartupReport.class).getStatus());
|
||||
assertNotNull(context.getBean(ShadowStartupReport.class)
|
||||
.getCheckedAt());
|
||||
assertNull(context.getBean(ShadowStartupReport.class)
|
||||
.getLastCompletedCycle());
|
||||
assertNull(context.getBean(ShadowStartupReport.class)
|
||||
.getSourceLagMillis());
|
||||
assertEquals(0L, context.getBean(ShadowStartupReport.class)
|
||||
.getEvidenceBacklog());
|
||||
assertEquals("PASS", context.getBean(ShadowStartupReport.class)
|
||||
.getStartupChecks().get("SHADOW_ENABLED"));
|
||||
assertEquals("PASS", context.getBean(ShadowStartupReport.class)
|
||||
.getStartupChecks().get("SIDE_EFFECT_BEANS_ABSENT"));
|
||||
assertEquals("PENDING", context.getBean(ShadowStartupReport.class)
|
||||
.getStartupChecks().get("SOURCE_PERMISSIONS_VERIFIED"));
|
||||
assertEquals("PENDING", context.getBean(ShadowStartupReport.class)
|
||||
.getStartupChecks().get("EVIDENCE_DATABASE_VERIFIED"));
|
||||
assertNotNull(context.getBean(DispatchCoordinator.class));
|
||||
assertNotNull(context.getBean(ShadowStatusController.class));
|
||||
assertTrue(context.getBeansOfType(
|
||||
DeviceCommandGateway.class).isEmpty());
|
||||
assertTrue(context.getBeansOfType(
|
||||
WmsNotificationGateway.class).isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void statusEndpointReturnsTheReadOnlyStartupState() {
|
||||
configured(contextRunner).run(context -> {
|
||||
MockMvc mvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
|
||||
mvc.perform(get("/shadow/status"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.status").value("STARTING"))
|
||||
.andExpect(jsonPath("$.checkedAt").exists())
|
||||
.andExpect(jsonPath("$.evidenceBacklog").value(0))
|
||||
.andExpect(jsonPath("$.startupChecks.SHADOW_ENABLED")
|
||||
.value("PASS"))
|
||||
.andExpect(jsonPath(
|
||||
"$.startupChecks.SIDE_EFFECT_BEANS_ABSENT")
|
||||
.value("PASS"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupFailsClosedUnlessEnabledProfileAndBothJdbcUrlsAreExplicit() {
|
||||
assertStartupFailure(contextRunner.withPropertyValues(
|
||||
"spring.profiles.active=shadow",
|
||||
"shadow.source.jdbc-url=jdbc:h2:mem:source",
|
||||
"shadow.evidence.jdbc-url=jdbc:h2:mem:evidence"),
|
||||
"SHADOW_MUST_BE_EXPLICITLY_ENABLED");
|
||||
assertStartupFailure(contextRunner.withPropertyValues(
|
||||
"shadow.enabled=true",
|
||||
"shadow.source.jdbc-url=jdbc:h2:mem:source",
|
||||
"shadow.evidence.jdbc-url=jdbc:h2:mem:evidence"),
|
||||
"SHADOW_PROFILE_MUST_BE_ACTIVE");
|
||||
assertStartupFailure(contextRunner.withPropertyValues(
|
||||
"spring.profiles.active=shadow",
|
||||
"shadow.enabled=true",
|
||||
"shadow.evidence.jdbc-url=jdbc:h2:mem:evidence"),
|
||||
"SHADOW_SOURCE_JDBC_URL_REQUIRED");
|
||||
assertStartupFailure(contextRunner.withPropertyValues(
|
||||
"spring.profiles.active=shadow",
|
||||
"shadow.enabled=true",
|
||||
"shadow.source.jdbc-url=jdbc:h2:mem:source"),
|
||||
"SHADOW_EVIDENCE_JDBC_URL_REQUIRED");
|
||||
assertStartupFailure(contextRunner.withPropertyValues(
|
||||
"spring.profiles.active=shadow",
|
||||
"shadow.enabled=true",
|
||||
"shadow.source.jdbc-url=jdbc:h2:mem:source",
|
||||
"shadow.evidence.jdbc-url=jdbc:h2:mem:evidence",
|
||||
"server.address=0.0.0.0"),
|
||||
"SHADOW_STATUS_ADDRESS_MUST_BE_LOOPBACK");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupRejectsAnyRealCommandGatewayBean() {
|
||||
assertStartupFailure(configured(contextRunner)
|
||||
.withUserConfiguration(
|
||||
ForbiddenGatewayConfiguration.class),
|
||||
"DEVICE_COMMAND_GATEWAY_FORBIDDEN_IN_SHADOW");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupRejectsAnyRealWmsGatewayBean() {
|
||||
assertStartupFailure(configured(contextRunner)
|
||||
.withUserConfiguration(
|
||||
ForbiddenWmsGatewayConfiguration.class),
|
||||
"WMS_NOTIFICATION_GATEWAY_FORBIDDEN_IN_SHADOW");
|
||||
}
|
||||
|
||||
@Test
|
||||
void startupRejectsAForbiddenFactoryBeanWithUnknownProductType() {
|
||||
assertStartupFailure(configured(contextRunner)
|
||||
.withUserConfiguration(
|
||||
ForbiddenFactoryBeanConfiguration.class),
|
||||
"FORBIDDEN_PRODUCTION_BEAN_IN_SHADOW:"
|
||||
+ ForbiddenFactoryBean.class.getName());
|
||||
}
|
||||
|
||||
private static WebApplicationContextRunner configured(
|
||||
WebApplicationContextRunner runner) {
|
||||
return runner.withPropertyValues(
|
||||
"spring.profiles.active=shadow",
|
||||
"shadow.enabled=true",
|
||||
"shadow.source.jdbc-url=jdbc:h2:mem:source",
|
||||
"shadow.evidence.jdbc-url=jdbc:h2:mem:evidence");
|
||||
}
|
||||
|
||||
private static void assertStartupFailure(
|
||||
WebApplicationContextRunner runner, String reasonCode) {
|
||||
runner.run(context -> {
|
||||
Throwable failure = context.getStartupFailure();
|
||||
assertNotNull(failure);
|
||||
assertTrue(messages(failure).contains(reasonCode),
|
||||
messages(failure));
|
||||
});
|
||||
}
|
||||
|
||||
private static String messages(Throwable failure) {
|
||||
StringBuilder messages = new StringBuilder();
|
||||
Throwable current = failure;
|
||||
while (current != null) {
|
||||
if (current.getMessage() != null) {
|
||||
messages.append(current.getMessage()).append('\n');
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return messages.toString();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ForbiddenGatewayConfiguration {
|
||||
@Bean
|
||||
DeviceCommandGateway forbiddenDeviceCommandGateway() {
|
||||
return envelope -> {
|
||||
throw new AssertionError("must never send in shadow test");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ForbiddenWmsGatewayConfiguration {
|
||||
@Bean
|
||||
WmsNotificationGateway forbiddenWmsNotificationGateway() {
|
||||
return notification -> {
|
||||
throw new AssertionError("must never notify in shadow test");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ForbiddenFactoryBeanConfiguration {
|
||||
@Bean
|
||||
ForbiddenFactoryBean forbiddenFactoryBean() {
|
||||
return new ForbiddenFactoryBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user