|
|
@@ -0,0 +1,114 @@
|
|
|
+package com.etotem.cfc.integration.controller;
|
|
|
+
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
|
|
+import org.springframework.boot.test.context.SpringBootTest;
|
|
|
+import org.springframework.test.web.servlet.MockMvc;
|
|
|
+
|
|
|
+import static org.hamcrest.Matchers.nullValue;
|
|
|
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
|
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
|
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 契约级单测:dimension.registry.mapLegacyCode
|
|
|
+ *
|
|
|
+ * <p>溯源·需求:统一维度体系 PRD §4 P0-3「编码映射层」(4 套历史编码 → 统一 code,双向可回查)。
|
|
|
+ * 溯源·设计:统一维度体系设计文档 §4 映射层设计 + §5.1 GET /api/dimension-registry/map?legacy= → Result<String>。
|
|
|
+ *
|
|
|
+ * <p>契约要点(以设计文档为准):
|
|
|
+ * <ul>
|
|
|
+ * <li>GET /api/dimension-registry/map?legacy=growth → Result.success("body.growth")(旧健康 7 维 → body.*)</li>
|
|
|
+ * <li>入参本身已是统一 code(如 body)→ 原样返回</li>
|
|
|
+ * <li>无法解析的 legacy → Result.success(null)(data 为 null,code=200,不抛异常)</li>
|
|
|
+ * <li>空/缺失 legacy 参数 → 服务层返回 null(空串入参)或 Spring 400(缺失入参)</li>
|
|
|
+ * <li>权限:该接口为注册中心只读取数入口,设计上供小程序(parent/child)与管理端(admin/teacher)共用,
|
|
|
+ * 无角色限定(角色/权限控制在此键「不适用」),故无 token 匿名请求也应能到达控制器(200)。</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p>本测试不假定实现类包路径(只经 HTTP 契约驱动),RED 阶段(cfclub 未合入实现)预期整体失败/404,
|
|
|
+ * 合入 feature/unified-dimension-p0 后应转绿。测试经 MockMvc 全栈调用,不依赖真实外部服务(无微信/第三方)。
|
|
|
+ */
|
|
|
+@SpringBootTest
|
|
|
+@AutoConfigureMockMvc
|
|
|
+class DimensionRegistryMapContractTest {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private MockMvc mockMvc;
|
|
|
+
|
|
|
+ private static final String MAP_PATH = "/api/dimension-registry/map";
|
|
|
+
|
|
|
+ /** 正常路径:健康 7 维 legacy code → body.* 统一 code(PRD P0-3 / 设计 §4 示例映射)。 */
|
|
|
+ @Test
|
|
|
+ void map_healthSevenLegacyCodes_resolveToBodySubCode() throws Exception {
|
|
|
+ assertMap("growth", "body.growth");
|
|
|
+ assertMap("sleep", "body.sleep");
|
|
|
+ assertMap("vision", "body.vision");
|
|
|
+ assertMap("immunity", "body.immunity");
|
|
|
+ assertMap("nutrition", "body.nutrition");
|
|
|
+ assertMap("gut", "body.gut");
|
|
|
+ assertMap("exercise", "body.exercise");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 正常路径:能量五维 root code(body/mind/wisdom/action/wealth)与统一 code 同码,原样返回。 */
|
|
|
+ @Test
|
|
|
+ void map_energyRootCodes_passthroughAsIs() throws Exception {
|
|
|
+ assertMap("body", "body");
|
|
|
+ assertMap("mind", "mind");
|
|
|
+ assertMap("wisdom", "wisdom");
|
|
|
+ assertMap("action", "action");
|
|
|
+ assertMap("wealth", "wealth");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 关键边界:已入参为统一子 code(body.growth),应原样返回(幂等)。 */
|
|
|
+ @Test
|
|
|
+ void map_unifiedSubCode_passthroughAsIs() throws Exception {
|
|
|
+ assertMap("body.growth", "body.growth");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 关键边界:未知 legacy code → Result.success(null),data=null 且 code=200(不抛异常)。 */
|
|
|
+ @Test
|
|
|
+ void map_unknownLegacy_returnsNullData() throws Exception {
|
|
|
+ mockMvc.perform(get(MAP_PATH).param("legacy", "unknown_legacy_xyz"))
|
|
|
+ .andExpect(status().isOk())
|
|
|
+ .andExpect(jsonPath("$.code").value(200))
|
|
|
+ .andExpect(jsonPath("$.data").value(nullValue()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 关键边界:空串 legacy → 服务层视为不可解析,返回 data=null(而非 500)。 */
|
|
|
+ @Test
|
|
|
+ void map_emptyLegacy_returnsNullData() throws Exception {
|
|
|
+ mockMvc.perform(get(MAP_PATH).param("legacy", ""))
|
|
|
+ .andExpect(status().isOk())
|
|
|
+ .andExpect(jsonPath("$.code").value(200))
|
|
|
+ .andExpect(jsonPath("$.data").value(nullValue()));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 关键边界:缺失 legacy 参数 → Spring 参数绑定失败,返回 400。 */
|
|
|
+ @Test
|
|
|
+ void map_missingLegacy_returnsBadRequest() throws Exception {
|
|
|
+ mockMvc.perform(get(MAP_PATH))
|
|
|
+ .andExpect(status().isBadRequest());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 权限/角色:map 为注册中心只读取数入口,无角色限定(设计上 parent/child/teacher/admin 共用)。
|
|
|
+ * 匿名(无 Authorization token)也应能到达控制器得到 200,证明接口未被 admin-only 门禁挡住。
|
|
|
+ * (角色/权限控制在此能力键不适用——接口本身不做 role 区分,故以「匿名可达」作为权限契约断言。)
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ void map_withoutAuthToken_isReachable() throws Exception {
|
|
|
+ mockMvc.perform(get(MAP_PATH).param("legacy", "growth"))
|
|
|
+ .andExpect(status().isOk())
|
|
|
+ .andExpect(jsonPath("$.code").value(200))
|
|
|
+ .andExpect(jsonPath("$.data").value("body.growth"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertMap(String legacy, String expectedUnified) throws Exception {
|
|
|
+ mockMvc.perform(get(MAP_PATH).param("legacy", legacy))
|
|
|
+ .andExpect(status().isOk())
|
|
|
+ .andExpect(jsonPath("$.code").value(200))
|
|
|
+ .andExpect(jsonPath("$.data").value(expectedUnified));
|
|
|
+ }
|
|
|
+}
|