收尾冒烟: 离线 Spring 装配切片(ConfigWiringSmokeTest)——模块10/11 新增 @ConfigurationProperties/@Component/可选 MeterRegistry 在真实容器装配验证 + 8 条测试
为上线前排除 bean 装配/属性绑定类启动崩溃;全量真实启动需在非生产环境(本配置默认值会连生产 MySQL/Redis)执行
This commit is contained in:
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user