From d6086ec7b08fc9a02bb4c6af876e6af1792a9276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sat, 5 Sep 2026 00:34:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B6=E5=B0=BE=E5=86=92=E7=83=9F:=20?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=20Spring=20=E8=A3=85=E9=85=8D=E5=88=87?= =?UTF-8?q?=E7=89=87=EF=BC=88ConfigWiringSmokeTest=EF=BC=89=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=A8=A1=E5=9D=9710/11=20=E6=96=B0=E5=A2=9E=20@Config?= =?UTF-8?q?urationProperties/@Component/=E5=8F=AF=E9=80=89=20MeterRegistry?= =?UTF-8?q?=20=E5=9C=A8=E7=9C=9F=E5=AE=9E=E5=AE=B9=E5=99=A8=E8=A3=85?= =?UTF-8?q?=E9=85=8D=E9=AA=8C=E8=AF=81=20+=208=20=E6=9D=A1=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为上线前排除 bean 装配/属性绑定类启动崩溃;全量真实启动需在非生产环境(本配置默认值会连生产 MySQL/Redis)执行 --- .../aiimage/config/ConfigWiringSmokeTest.java | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 backend-java/src/test/java/com/nanri/aiimage/config/ConfigWiringSmokeTest.java diff --git a/backend-java/src/test/java/com/nanri/aiimage/config/ConfigWiringSmokeTest.java b/backend-java/src/test/java/com/nanri/aiimage/config/ConfigWiringSmokeTest.java new file mode 100644 index 00000000..b6b58b18 --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/config/ConfigWiringSmokeTest.java @@ -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()); + } + } +}