From a46be5d2ef437ad43a6bb96db456ef39c04da92d Mon Sep 17 00:00:00 2001 From: zichun Date: Wed, 6 May 2026 19:59:43 +0800 Subject: [PATCH] feat(mod): GET /api/modules controller REQ-MOD-004 --- backend/pom.xml | 11 +++++++++++ backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java | 11 +++++++++++ backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 0 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index bdeb6a1..ffbee2a 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -110,6 +110,17 @@ org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test.java + **/*Tests.java + **/*IT.java + + + + + org.apache.maven.plugins maven-compiler-plugin ${java.version} diff --git a/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java b/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java index 0b177b3..12365c4 100644 --- a/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java +++ b/backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java @@ -2,13 +2,16 @@ package com.xly.erp.module.mod.controller; import com.xly.erp.common.response.ApiResponse; import com.xly.erp.module.mod.dto.ModuleCreateDTO; +import com.xly.erp.module.mod.dto.ModuleQueryDTO; import com.xly.erp.module.mod.dto.ModuleUpdateDTO; import com.xly.erp.module.mod.service.ModuleService; import com.xly.erp.module.mod.vo.ModuleDeleteResultVO; +import com.xly.erp.module.mod.vo.ModuleTreeNodeVO; import com.xly.erp.module.mod.vo.ModuleVO; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -16,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RestController @RequestMapping("/api/modules") @RequiredArgsConstructor @@ -40,4 +45,10 @@ public class ModuleController { public ApiResponse delete(@PathVariable Integer id) { return ApiResponse.ok(moduleService.delete(id)); } + + /** REQ-MOD-004 模块树查询 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('MOD:READ')") */ + @GetMapping + public ApiResponse> tree(@Valid ModuleQueryDTO query) { + return ApiResponse.ok(moduleService.tree(query)); + } } diff --git a/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java b/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java index 159146d..de75253 100644 --- a/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java +++ b/backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java @@ -19,6 +19,7 @@ import java.time.LocalDateTime; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -418,4 +419,113 @@ class ModuleControllerIT { assertThat(moduleMapper.selectById(id).getSProcedureName()).isEqualTo(origProc); } + + // ============================================================ + // REQ-MOD-004 GET 系列 + // ============================================================ + + @Test + void get_emptyKeyword_returnsAllUndeletedAsTree() throws Exception { + // 插入一个 root + 一个 child;不带 keyword 的 GET 应能看到二者 + Integer rootId = insertExisting("sp_get_root_" + System.nanoTime(), null); + Integer childId = insertExisting("sp_get_child_" + System.nanoTime(), rootId); + + mockMvc.perform(get("/api/modules")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data[?(@.iIncrement==" + rootId + ")]").exists()) + .andExpect(jsonPath("$.data[?(@.iIncrement==" + rootId + ")].children[?(@.iIncrement==" + childId + ")]").exists()); + } + + @Test + void get_keyword_filtersByModuleNameZhWithAncestors() throws Exception { + // grandparent("系统配置") -> parent("用户管理") -> child("登录认证") + ModuleEntity gp = new ModuleEntity(); + gp.setSDisplayType("前端业务"); gp.setSProcedureName("sp_get_kw_gp_" + System.nanoTime()); + gp.setSModuleType("MOD"); gp.setSManageDeptEn("IT"); gp.setBShowPermission(false); + gp.setSModuleNameZh("系统配置-keyword test"); gp.setIParentId(null); gp.setISortOrder(0); + gp.setBDeleted(false); gp.setTCreateDate(LocalDateTime.now()); + moduleMapper.insert(gp); + + ModuleEntity p = new ModuleEntity(); + p.setSDisplayType("前端业务"); p.setSProcedureName("sp_get_kw_p_" + System.nanoTime()); + p.setSModuleType("MOD"); p.setSManageDeptEn("IT"); p.setBShowPermission(false); + p.setSModuleNameZh("用户管理-keyword test"); p.setIParentId(gp.getIIncrement()); p.setISortOrder(0); + p.setBDeleted(false); p.setTCreateDate(LocalDateTime.now()); + moduleMapper.insert(p); + + ModuleEntity c = new ModuleEntity(); + c.setSDisplayType("前端业务"); c.setSProcedureName("sp_get_kw_c_" + System.nanoTime()); + c.setSModuleType("MOD"); c.setSManageDeptEn("IT"); c.setBShowPermission(false); + c.setSModuleNameZh("唯一登录认证关键词"); c.setIParentId(p.getIIncrement()); c.setISortOrder(0); + c.setBDeleted(false); c.setTCreateDate(LocalDateTime.now()); + moduleMapper.insert(c); + + mockMvc.perform(get("/api/modules").param("keyword", "唯一登录认证关键词")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + // 命中 child + 全部祖先:grandparent 在 root 数组中 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")]").exists()) + // grandparent.children 含 parent + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")].children[?(@.iIncrement==" + p.getIIncrement() + ")]").exists()) + // parent.children 含 child + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")].children[?(@.iIncrement==" + p.getIIncrement() + ")].children[?(@.iIncrement==" + c.getIIncrement() + ")]").exists()); + } + + @Test + void get_keywordNoMatch_returnsEmptyArray() throws Exception { + insertExisting("sp_get_nm_" + System.nanoTime(), null); + + mockMvc.perform(get("/api/modules").param("keyword", "绝对不存在的关键词xyz")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.data").isArray()) + .andExpect(jsonPath("$.data.length()").value(0)); + } + + @Test + void get_keywordTooLong_returns40010() throws Exception { + String longKw = "a".repeat(51); + mockMvc.perform(get("/api/modules").param("keyword", longKw)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(40010)); + } + + @Test + void get_softDeletedNotInResult() throws Exception { + Integer id = insertExisting("sp_get_sd_" + System.nanoTime(), null); + // 软删除该模块 + ModuleEntity patch = new ModuleEntity(); + patch.setIIncrement(id); + patch.setBDeleted(true); + moduleMapper.updateById(patch); + + mockMvc.perform(get("/api/modules")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")]").doesNotExist()); + } + + @Test + void get_responseExcludesInternalFields() throws Exception { + insertExisting("sp_get_priv_" + System.nanoTime(), null); + + mockMvc.perform(get("/api/modules")) + .andExpect(status().isOk()) + // 树节点不应包含内部字段 + .andExpect(jsonPath("$.data[0].sProcedureName").doesNotExist()) + .andExpect(jsonPath("$.data[0].sModuleType").doesNotExist()) + .andExpect(jsonPath("$.data[0].bShowPermission").doesNotExist()) + .andExpect(jsonPath("$.data[0].tCreateDate").doesNotExist()) + .andExpect(jsonPath("$.data[0].bDeleted").doesNotExist()); + } + + @Test + void get_leafNodeChildrenIsEmptyArrayNotNull() throws Exception { + Integer id = insertExisting("sp_get_leaf_" + System.nanoTime(), null); + + mockMvc.perform(get("/api/modules")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")].children").isArray()) + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")].children.length()").value(0)); + } } -- libgit2 0.22.2