|
|
@@ -17,8 +17,11 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
/**
|
|
|
* LeshouyingProperties.validate() 的单测。
|
|
|
*
|
|
|
- * ★ 这是该方法**第一次**被测试覆盖:它挂 @PostConstruct,而全仓没有任何启动 Spring 上下文的测试
|
|
|
- * (无 @SpringBootTest),所以在此之前它从未在测试里跑过。这里直接调它。
|
|
|
+ * ★ 本类建立时(2026-09-22)全仓还没有任何启动 Spring 上下文的测试(无 @SpringBootTest)——
|
|
|
+ * 那时挂着 @PostConstruct 的 validate() 从未在测试里跑过,本类是它**第一处覆盖**:这里直接调它。
|
|
|
+ * ★ 现状已变,别再照旧读:{@code PaymentApplicationContextTest}({@code @SpringBootTest},
|
|
|
+ * {@code 2693ab6} 引入)会 refresh 整个容器 ⇒ 本 bean 被创建、validate() 随之执行;
|
|
|
+ * 但那条测试只断言装配成功,**不覆盖本方法的分支** —— 分支断言仍然只在本类。
|
|
|
*/
|
|
|
class LeshouyingPropertiesTest {
|
|
|
|
|
|
@@ -190,7 +193,9 @@ class LeshouyingPropertiesTest {
|
|
|
// ---- ★ 正对照:进件凭据**不得**进 validate() ----
|
|
|
|
|
|
/**
|
|
|
- * ★ <b>正对照</b>:本用例钉住 §0.9 的裁定 —— 进件凭据**不得**进 {@code validate()}。
|
|
|
+ * ★ <b>正对照</b>:本用例钉住「进件凭据**不得**进 {@code validate()}」这条裁定。
|
|
|
+ * 依据是 {@code Global Constraint 3}「凭据不进仓库」/ {@code Global Constraint 4}
|
|
|
+ * 「不留测试用假值」(spec §6.4 逐字),加上下面两条实测的代码事实。
|
|
|
*
|
|
|
* <p>它只写 {@code assertDoesNotThrow},看着像「没断言」,实际是**挡后来者**:
|
|
|
* <ul>
|
|
|
@@ -201,8 +206,17 @@ class LeshouyingPropertiesTest {
|
|
|
* <li>而 §6.4 又禁止我们往那份 yml 填假值(凭据不进仓库)。</li>
|
|
|
* </ul>
|
|
|
* 两条路都堵死 ⇒ 闸放在进件服务入口,而不是启动期。
|
|
|
- * <b>没有这条用例,上面那次「加进 validate()」全库不会有任何测试变红</b>
|
|
|
- * —— 本类的另外几条只造缺项、不看「四必填齐备时是否放行」。
|
|
|
+ *
|
|
|
+ * <p>★ <b>本条是这条裁定的正对照之一,不是唯一防线。</b>本类另有三条也在「四必填齐备」的
|
|
|
+ * 前提下断言 {@code validate()} 不抛 —— {@code validate_docTestConfigWithDevProfile_passes} /
|
|
|
+ * {@code validate_realConfigWithProdProfile_passes} / {@code validate_uploadMaxBytes_isNotInRequiredList};
|
|
|
+ * 两个 props 工厂({@code docTestProps} / {@code realProps})都不设这五个凭据字段(= null),
|
|
|
+ * 所以「加进 {@code validate()}」会把那三条一起打红(2026-09-23 变异实测:本类共 **5 红** ——
|
|
|
+ * 那三条 + 本条 + {@code validate_docTestConfigWithProdProfile_throws};最后一条红是因为
|
|
|
+ * 第一道闸先炸,异常消息从「文档测试配置」那句换成了「配置缺失」那句)。
|
|
|
+ * 那三条的**目的**是别的事(dev 放行 / 非文档测试参数放行 / uploadMaxBytes 不在必填清单),
|
|
|
+ * 只是恰好共用同一个前提;本条是唯一一条**专为这条裁定**写的,构造路径是
|
|
|
+ * 四必填填非文档测试值 + {@code dev}。
|
|
|
*/
|
|
|
@Test
|
|
|
void validate_passesWhenOnboardingCredentialsAreAbsent() {
|
|
|
@@ -236,6 +250,46 @@ class LeshouyingPropertiesTest {
|
|
|
"DOC_TEST_BASE_URL 与 application.yml 的 base-url 不一致");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * ★ 钉住五个进件键的「键名 → 环境变量名」这一对关系,**外加默认值必须为空**。
|
|
|
+ *
|
|
|
+ * <p><b>为什么值得一条用例</b>:{@code @ConfigurationProperties} 默认
|
|
|
+ * {@code ignoreUnknownFields = true}(本仓 {@code java/src} 没有关掉它),所以键名写错一个字母
|
|
|
+ * ({@code channel-id} → {@code chennel-id})不会报错,只会**静默失效** —— 该键被忽略、正主留空。
|
|
|
+ * 而闸({@code missingOnboardingCredentials()})的文案原样写着正确的环境变量名:运维照着设好了
|
|
|
+ * {@code LESHOUYING_CHANNEL_ID},闸仍报这一项缺失,**报错把人指向错误方向**。
|
|
|
+ * 这五个键名此前**零钉子**({@code docTestConstants_matchApplicationYml} 钉的是三个既有键)。
|
|
|
+ *
|
|
|
+ * <p><b>为什么断言的是「值恰好等于占位符」而不是「键存在」</b>:只查键在不在会丢掉另一半 ——
|
|
|
+ * 默认值必须留空({@code Global Constraint 4}:不留测试用假值)。填一个假值同样是静默的:
|
|
|
+ * 闸不再报缺失,进件却拿着假通道 id 打上游。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void onboardingYmlKeys_useTheirEnvVarPlaceholdersWithEmptyDefault() throws Exception {
|
|
|
+ String yml;
|
|
|
+ try (InputStream in = getClass().getResourceAsStream("/application.yml")) {
|
|
|
+ assertNotNull(in, "application.yml 必须在测试 classpath 上(src/main/resources)");
|
|
|
+ yml = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ String[][] keyToEnv = {
|
|
|
+ {"channel-id", "LESHOUYING_CHANNEL_ID"},
|
|
|
+ {"bd-id", "LESHOUYING_BD_ID"},
|
|
|
+ {"channel-type", "LESHOUYING_CHANNEL_TYPE"},
|
|
|
+ {"rate-code", "LESHOUYING_RATE_CODE"},
|
|
|
+ {"onboarding-notify-url", "LESHOUYING_ONBOARDING_NOTIFY_URL"},
|
|
|
+ };
|
|
|
+ for (String[] pair : keyToEnv) {
|
|
|
+ String key = pair[0];
|
|
|
+ String env = pair[1];
|
|
|
+ assertEquals("${" + env + ":}", ymlLine(yml, key),
|
|
|
+ "leshouying." + key + " 的值必须恰好是 ${" + env
|
|
|
+ + ":}(键名或环境变量名漂移都会让它在运行时静默失效)");
|
|
|
+ assertEquals("", placeholderDefault(yml, env),
|
|
|
+ env + " 的默认值必须留空(Global Constraint 4:不留测试用假值)");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/** 取 `${ENV_NAME:<默认值>}` 里的默认值。 */
|
|
|
private static String placeholderDefault(String yml, String envName) {
|
|
|
Matcher m = Pattern.compile("\\$\\{" + envName + ":([^}]*)\\}").matcher(yml);
|