|
|
@@ -559,14 +559,13 @@ class LeshouyingOnboardingControllerTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * ★ 边界①:{@code level} 白名单 = {@code {"1","2","3"}},其余 ⇒ 400,且**一次都不触达服务**。
|
|
|
- *
|
|
|
- * <p>「其余」**包含整个参数不在** —— 2026-09-23 实测:本仓 {@code GlobalExceptionHandler}
|
|
|
- * 是全仓唯一的 advice,它**没有** {@code MissingServletRequestParameterException} 分支
|
|
|
- * ⇒ 若把 {@code level} 声明成必填,缺失落进兜底的 {@code Exception} 分支,得到
|
|
|
- * <b>HTTP 500 + 业务码 500「服务器内部错误」</b>(读数见 task-9a-report.md)
|
|
|
- * —— 那是「请重试」的语义,不是 400。所以 {@code level} 声明为 {@code required = false},
|
|
|
- * 缺失与非法走**同一条**白名单判断。
|
|
|
+ * ★ 边界①:{@code level} 白名单 = {@code {"1","2","3"}},**值**不在其中 ⇒ 400,
|
|
|
+ * 且**一次都不触达服务**。
|
|
|
+ *
|
|
|
+ * <p>与 {@link #areasEndpoint_rejectsAbsentLevel_withoutTouchingService} 是**一对**:
|
|
|
+ * 本条钉「值不合法」,那条钉「参数整个缺席」。分开写不是排版偏好 —— 白名单判据有**两半**
|
|
|
+ * ({@code level == null} 与 {@code !ALLOWED_LEVELS.contains(level)}),一条用例里塞两种
|
|
|
+ * 形态的话,变异只能证明「合起来有用」,分别摘掉哪一半都还能绿。
|
|
|
*/
|
|
|
@Test
|
|
|
void areasEndpoint_rejectsLevelOutsideWhitelist_withoutTouchingService() throws Exception {
|
|
|
@@ -576,10 +575,6 @@ class LeshouyingOnboardingControllerTest {
|
|
|
.andExpect(status().isOk())
|
|
|
.andExpect(jsonPath("$.code").value(400));
|
|
|
}
|
|
|
- // 整个参数不在:同样是「其余」
|
|
|
- mockMvc.perform(get(AREAS_PATH).param("parentCode", "1"))
|
|
|
- .andExpect(status().isOk())
|
|
|
- .andExpect(jsonPath("$.code").value(400));
|
|
|
|
|
|
// 反例先行 ⇒ 这是一条**无歧义**的零接触读数
|
|
|
verifyNoInteractions(onboardingService, merchantService);
|
|
|
@@ -591,6 +586,41 @@ class LeshouyingOnboardingControllerTest {
|
|
|
verify(onboardingService).areaList("3", "2");
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * ★ 边界①的**另一半**:{@code level} **整个参数不在** ⇒ 400,且**不得触达服务**
|
|
|
+ * (裁定 R-T9a-b)。
|
|
|
+ *
|
|
|
+ * <p>【为什么不交给 {@code @RequestParam} 的必填】2026-09-23 实测:本仓
|
|
|
+ * {@code GlobalExceptionHandler} 是**全仓唯一**的 advice,它**没有**
|
|
|
+ * {@code MissingServletRequestParameterException} 分支 ⇒ 必填参数缺失落进兜底的
|
|
|
+ * {@code Exception} 分支,得到 <b>HTTP 500 + 业务码 500「服务器内部错误」</b>
|
|
|
+ * (探针读数见 task-9a-report.md §一),那是「请重试」的语义,不是 400。
|
|
|
+ * 所以 {@code level} 声明为 {@code required = false},缺席与非法走**同一条**白名单判断
|
|
|
+ * —— **不**为缺席另写一条规则。
|
|
|
+ *
|
|
|
+ * <p>【这个 400 是**可归因**的】本请求**给了**合法的 {@code parentCode="1"} ⇒ 边界②的谓词
|
|
|
+ * {@code !"1".equals(null) && isBlank("1")} 求值为 {@code true && false} = 假、不可能抛
|
|
|
+ * ⇒ 这个 400 只可能出自边界①。若两个参数都不给,400 就有了两个可能来源,这条用例
|
|
|
+ * 会失去归因能力(那种形态的「谁先谁后」实测不可观测,见 task-9a-report.md §四(3))。
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void areasEndpoint_rejectsAbsentLevel_withoutTouchingService() throws Exception {
|
|
|
+ login(TENANT, false);
|
|
|
+ mockMvc.perform(get(AREAS_PATH).param("parentCode", "1"))
|
|
|
+ .andExpect(status().isOk())
|
|
|
+ .andExpect(jsonPath("$.code").value(400));
|
|
|
+
|
|
|
+ // 反例先行 ⇒ 这是一条**无歧义**的零接触读数
|
|
|
+ verifyNoInteractions(onboardingService, merchantService);
|
|
|
+
|
|
|
+ // 正对照:同形请求(同样只带 parentCode 的位置)+ 合法 level ⇒ 受理
|
|
|
+ // (否则上面的 400 可能只是「压根没路由到这个方法」)
|
|
|
+ when(onboardingService.areaList("2", "1")).thenReturn(List.of());
|
|
|
+ mockMvc.perform(get(AREAS_PATH).param("level", "2").param("parentCode", "1"))
|
|
|
+ .andExpect(jsonPath("$.code").value(0));
|
|
|
+ verify(onboardingService).areaList("2", "1");
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* ★ 边界②:{@code level} ≠ {@code "1"} 而 {@code parentCode} 缺失 / 空白(空串、纯空白)⇒ 400,
|
|
|
* 且**不得触达服务**。
|