收尾冒烟: 离线 Spring 装配切片(ConfigWiringSmokeTest)——模块10/11 新增 @ConfigurationProperties/@Component/可选 MeterRegistry 在真实容器装配验证 + 8 条测试

为上线前排除 bean 装配/属性绑定类启动崩溃;全量真实启动需在非生产环境(本配置默认值会连生产 MySQL/Redis)执行
This commit is contained in:
2026-09-05 00:34:45 +08:00
parent 9d172ac170
commit d6086ec7b0
@@ -0,0 +1,122 @@
package com.nanri.aiimage.config;
import com.nanri.aiimage.metrics.TaskObservabilityMetrics;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 启动冒烟(收尾验证前置):离线 Spring 装配切片。
*
* 在真实 Spring 容器里装配模块 10/11 新增的配置组件,验证:@ConfigurationProperties 绑定默认值、
* @Component 自动注入、可选 MeterRegistry(ObjectProvider) 路径、@ConfigurationProperties 注册
* 均正常——上线前先排除"bean 装配/属性绑定"类启动崩溃。
*
* 注意:不加载 DataSource/Redis/MinIO 等(会连生产),全量真实启动需在非生产环境执行。
*/
class ConfigWiringSmokeTest {
@Configuration
@EnableConfigurationProperties({HttpClientProperties.class, ZiniaoProperties.class,
SimilarAsinProperties.class})
@Import({LlmHttpConfigResolver.class, BrandCheckHttpConfigResolver.class,
ZiniaoHttpConfigResolver.class, ImageDownloadHttpConfigResolver.class,
IdempotentRetryGuard.class})
static class Slice {
}
private static AnnotationConfigApplicationContext context() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(Slice.class);
ctx.refresh();
return ctx;
}
@Test
void configPropertiesBeansBindable() {
try (AnnotationConfigApplicationContext ctx = context()) {
HttpClientProperties http = ctx.getBean(HttpClientProperties.class);
assertNotNull(http);
assertEquals(10_000L, http.effectiveConnectTimeoutMillis(), "命名空间默认 connect 10s 应绑定");
assertEquals(3, http.effectiveMaxRetries());
assertNotNull(ctx.getBean(ZiniaoProperties.class));
assertNotNull(ctx.getBean(SimilarAsinProperties.class));
}
}
@Test
void resolversAreSpringBeansAndInjectable() {
try (AnnotationConfigApplicationContext ctx = context()) {
assertNotNull(ctx.getBean(LlmHttpConfigResolver.class));
assertNotNull(ctx.getBean(BrandCheckHttpConfigResolver.class));
assertNotNull(ctx.getBean(ZiniaoHttpConfigResolver.class));
assertNotNull(ctx.getBean(ImageDownloadHttpConfigResolver.class));
}
}
@Test
void resolversReadBoundConfig() {
try (AnnotationConfigApplicationContext ctx = context()) {
ZiniaoHttpConfigResolver ziniao = ctx.getBean(ZiniaoHttpConfigResolver.class);
// 模块未显式配置 read 秒数(默认 0) → 回退命名空间 60s
assertEquals(60_000L, ziniao.readTimeoutMillis());
assertEquals(10_000L, ziniao.connectTimeoutMillis());
ImageDownloadHttpConfigResolver image = ctx.getBean(ImageDownloadHttpConfigResolver.class);
// SimilarAsinProperties.imageDownloadTimeoutSeconds 默认 5s → 模块优先
assertEquals(5_000L, image.readTimeoutMillis());
}
}
@Test
void retryGuardWiresWithOptionalRegistry() {
try (AnnotationConfigApplicationContext ctx = context()) {
IdempotentRetryGuard guard = ctx.getBean(IdempotentRetryGuard.class);
assertNotNull(guard);
assertTrue(guard.isIdempotent("GET", false), "GET 应视为幂等");
assertEquals(500L, guard.backoffMillis(1), "守卫读取命名空间默认基础退避 500ms");
}
}
@Test
void observabilityRecorderIsPlainInstantiable() {
TaskObservabilityMetrics metrics = new TaskObservabilityMetrics("server-110", null);
assertEquals("server-110", metrics.instanceId());
// registry=null 时不抛错(无监控栈降级)
metrics.taskCreated("similarasin");
metrics.fileJobState("similarasin", "success");
}
@Test
void containerClosesCleanly() {
AnnotationConfigApplicationContext ctx = context();
ctx.close();
assertTrue(true);
}
@Test
void sliceRegistersExactlyExpectedConfigBeans() {
try (AnnotationConfigApplicationContext ctx = context()) {
String[] names = ctx.getBeanDefinitionNames();
assertTrue(names.length >= 8, "装配 bean 数量应充足");
assertNotNull(ctx.getBean(HttpClientProperties.class));
assertNotNull(ctx.getBean(IdempotentRetryGuard.class));
}
}
@Test
void defaultPropsMatchSpecBaseline() {
try (AnnotationConfigApplicationContext ctx = context()) {
HttpClientProperties http = ctx.getBean(HttpClientProperties.class);
assertSame(http, ctx.getBean(HttpClientProperties.class), "命名空间属性 bean 单例");
assertEquals(90_000L, http.effectiveCallTimeoutMillis());
assertEquals(500L, http.getBaseRetryDelayMillis());
}
}
}