Commit a46be5d2ef437ad43a6bb96db456ef39c04da92d

Authored by zichun
1 parent f9a9070e

feat(mod): GET /api/modules controller REQ-MOD-004

- 新增 @GetMapping 树查询端点 + 7 个集成测试。
- 修复 maven-surefire-plugin 默认 includes 不含 *IT.java 的配置缺陷,
  让 ModuleMapperIT / ModuleControllerIT 也参与 mvn test 全量。
backend/pom.xml
@@ -110,6 +110,17 @@ @@ -110,6 +110,17 @@
110 </plugin> 110 </plugin>
111 <plugin> 111 <plugin>
112 <groupId>org.apache.maven.plugins</groupId> 112 <groupId>org.apache.maven.plugins</groupId>
  113 + <artifactId>maven-surefire-plugin</artifactId>
  114 + <configuration>
  115 + <includes>
  116 + <include>**/*Test.java</include>
  117 + <include>**/*Tests.java</include>
  118 + <include>**/*IT.java</include>
  119 + </includes>
  120 + </configuration>
  121 + </plugin>
  122 + <plugin>
  123 + <groupId>org.apache.maven.plugins</groupId>
113 <artifactId>maven-compiler-plugin</artifactId> 124 <artifactId>maven-compiler-plugin</artifactId>
114 <configuration> 125 <configuration>
115 <source>${java.version}</source> 126 <source>${java.version}</source>
backend/src/main/java/com/xly/erp/module/mod/controller/ModuleController.java
@@ -2,13 +2,16 @@ package com.xly.erp.module.mod.controller; @@ -2,13 +2,16 @@ package com.xly.erp.module.mod.controller;
2 2
3 import com.xly.erp.common.response.ApiResponse; 3 import com.xly.erp.common.response.ApiResponse;
4 import com.xly.erp.module.mod.dto.ModuleCreateDTO; 4 import com.xly.erp.module.mod.dto.ModuleCreateDTO;
  5 +import com.xly.erp.module.mod.dto.ModuleQueryDTO;
5 import com.xly.erp.module.mod.dto.ModuleUpdateDTO; 6 import com.xly.erp.module.mod.dto.ModuleUpdateDTO;
6 import com.xly.erp.module.mod.service.ModuleService; 7 import com.xly.erp.module.mod.service.ModuleService;
7 import com.xly.erp.module.mod.vo.ModuleDeleteResultVO; 8 import com.xly.erp.module.mod.vo.ModuleDeleteResultVO;
  9 +import com.xly.erp.module.mod.vo.ModuleTreeNodeVO;
8 import com.xly.erp.module.mod.vo.ModuleVO; 10 import com.xly.erp.module.mod.vo.ModuleVO;
9 import jakarta.validation.Valid; 11 import jakarta.validation.Valid;
10 import lombok.RequiredArgsConstructor; 12 import lombok.RequiredArgsConstructor;
11 import org.springframework.web.bind.annotation.DeleteMapping; 13 import org.springframework.web.bind.annotation.DeleteMapping;
  14 +import org.springframework.web.bind.annotation.GetMapping;
12 import org.springframework.web.bind.annotation.PathVariable; 15 import org.springframework.web.bind.annotation.PathVariable;
13 import org.springframework.web.bind.annotation.PostMapping; 16 import org.springframework.web.bind.annotation.PostMapping;
14 import org.springframework.web.bind.annotation.PutMapping; 17 import org.springframework.web.bind.annotation.PutMapping;
@@ -16,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestBody; @@ -16,6 +19,8 @@ import org.springframework.web.bind.annotation.RequestBody;
16 import org.springframework.web.bind.annotation.RequestMapping; 19 import org.springframework.web.bind.annotation.RequestMapping;
17 import org.springframework.web.bind.annotation.RestController; 20 import org.springframework.web.bind.annotation.RestController;
18 21
  22 +import java.util.List;
  23 +
19 @RestController 24 @RestController
20 @RequestMapping("/api/modules") 25 @RequestMapping("/api/modules")
21 @RequiredArgsConstructor 26 @RequiredArgsConstructor
@@ -40,4 +45,10 @@ public class ModuleController { @@ -40,4 +45,10 @@ public class ModuleController {
40 public ApiResponse<ModuleDeleteResultVO> delete(@PathVariable Integer id) { 45 public ApiResponse<ModuleDeleteResultVO> delete(@PathVariable Integer id) {
41 return ApiResponse.ok(moduleService.delete(id)); 46 return ApiResponse.ok(moduleService.delete(id));
42 } 47 }
  48 +
  49 + /** REQ-MOD-004 模块树查询 — REQ-USR-004 完成后追加 @PreAuthorize("hasAuthority('MOD:READ')") */
  50 + @GetMapping
  51 + public ApiResponse<List<ModuleTreeNodeVO>> tree(@Valid ModuleQueryDTO query) {
  52 + return ApiResponse.ok(moduleService.tree(query));
  53 + }
43 } 54 }
backend/src/test/java/com/xly/erp/module/mod/controller/ModuleControllerIT.java
@@ -19,6 +19,7 @@ import java.time.LocalDateTime; @@ -19,6 +19,7 @@ import java.time.LocalDateTime;
19 19
20 import static org.assertj.core.api.Assertions.assertThat; 20 import static org.assertj.core.api.Assertions.assertThat;
21 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; 21 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
  22 +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 23 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
23 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 24 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
24 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 25 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -418,4 +419,113 @@ class ModuleControllerIT { @@ -418,4 +419,113 @@ class ModuleControllerIT {
418 419
419 assertThat(moduleMapper.selectById(id).getSProcedureName()).isEqualTo(origProc); 420 assertThat(moduleMapper.selectById(id).getSProcedureName()).isEqualTo(origProc);
420 } 421 }
  422 +
  423 + // ============================================================
  424 + // REQ-MOD-004 GET 系列
  425 + // ============================================================
  426 +
  427 + @Test
  428 + void get_emptyKeyword_returnsAllUndeletedAsTree() throws Exception {
  429 + // 插入一个 root + 一个 child;不带 keyword 的 GET 应能看到二者
  430 + Integer rootId = insertExisting("sp_get_root_" + System.nanoTime(), null);
  431 + Integer childId = insertExisting("sp_get_child_" + System.nanoTime(), rootId);
  432 +
  433 + mockMvc.perform(get("/api/modules"))
  434 + .andExpect(status().isOk())
  435 + .andExpect(jsonPath("$.code").value(200))
  436 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + rootId + ")]").exists())
  437 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + rootId + ")].children[?(@.iIncrement==" + childId + ")]").exists());
  438 + }
  439 +
  440 + @Test
  441 + void get_keyword_filtersByModuleNameZhWithAncestors() throws Exception {
  442 + // grandparent("系统配置") -> parent("用户管理") -> child("登录认证")
  443 + ModuleEntity gp = new ModuleEntity();
  444 + gp.setSDisplayType("前端业务"); gp.setSProcedureName("sp_get_kw_gp_" + System.nanoTime());
  445 + gp.setSModuleType("MOD"); gp.setSManageDeptEn("IT"); gp.setBShowPermission(false);
  446 + gp.setSModuleNameZh("系统配置-keyword test"); gp.setIParentId(null); gp.setISortOrder(0);
  447 + gp.setBDeleted(false); gp.setTCreateDate(LocalDateTime.now());
  448 + moduleMapper.insert(gp);
  449 +
  450 + ModuleEntity p = new ModuleEntity();
  451 + p.setSDisplayType("前端业务"); p.setSProcedureName("sp_get_kw_p_" + System.nanoTime());
  452 + p.setSModuleType("MOD"); p.setSManageDeptEn("IT"); p.setBShowPermission(false);
  453 + p.setSModuleNameZh("用户管理-keyword test"); p.setIParentId(gp.getIIncrement()); p.setISortOrder(0);
  454 + p.setBDeleted(false); p.setTCreateDate(LocalDateTime.now());
  455 + moduleMapper.insert(p);
  456 +
  457 + ModuleEntity c = new ModuleEntity();
  458 + c.setSDisplayType("前端业务"); c.setSProcedureName("sp_get_kw_c_" + System.nanoTime());
  459 + c.setSModuleType("MOD"); c.setSManageDeptEn("IT"); c.setBShowPermission(false);
  460 + c.setSModuleNameZh("唯一登录认证关键词"); c.setIParentId(p.getIIncrement()); c.setISortOrder(0);
  461 + c.setBDeleted(false); c.setTCreateDate(LocalDateTime.now());
  462 + moduleMapper.insert(c);
  463 +
  464 + mockMvc.perform(get("/api/modules").param("keyword", "唯一登录认证关键词"))
  465 + .andExpect(status().isOk())
  466 + .andExpect(jsonPath("$.code").value(200))
  467 + // 命中 child + 全部祖先:grandparent 在 root 数组中
  468 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")]").exists())
  469 + // grandparent.children 含 parent
  470 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")].children[?(@.iIncrement==" + p.getIIncrement() + ")]").exists())
  471 + // parent.children 含 child
  472 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + gp.getIIncrement() + ")].children[?(@.iIncrement==" + p.getIIncrement() + ")].children[?(@.iIncrement==" + c.getIIncrement() + ")]").exists());
  473 + }
  474 +
  475 + @Test
  476 + void get_keywordNoMatch_returnsEmptyArray() throws Exception {
  477 + insertExisting("sp_get_nm_" + System.nanoTime(), null);
  478 +
  479 + mockMvc.perform(get("/api/modules").param("keyword", "绝对不存在的关键词xyz"))
  480 + .andExpect(status().isOk())
  481 + .andExpect(jsonPath("$.code").value(200))
  482 + .andExpect(jsonPath("$.data").isArray())
  483 + .andExpect(jsonPath("$.data.length()").value(0));
  484 + }
  485 +
  486 + @Test
  487 + void get_keywordTooLong_returns40010() throws Exception {
  488 + String longKw = "a".repeat(51);
  489 + mockMvc.perform(get("/api/modules").param("keyword", longKw))
  490 + .andExpect(status().isOk())
  491 + .andExpect(jsonPath("$.code").value(40010));
  492 + }
  493 +
  494 + @Test
  495 + void get_softDeletedNotInResult() throws Exception {
  496 + Integer id = insertExisting("sp_get_sd_" + System.nanoTime(), null);
  497 + // 软删除该模块
  498 + ModuleEntity patch = new ModuleEntity();
  499 + patch.setIIncrement(id);
  500 + patch.setBDeleted(true);
  501 + moduleMapper.updateById(patch);
  502 +
  503 + mockMvc.perform(get("/api/modules"))
  504 + .andExpect(status().isOk())
  505 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")]").doesNotExist());
  506 + }
  507 +
  508 + @Test
  509 + void get_responseExcludesInternalFields() throws Exception {
  510 + insertExisting("sp_get_priv_" + System.nanoTime(), null);
  511 +
  512 + mockMvc.perform(get("/api/modules"))
  513 + .andExpect(status().isOk())
  514 + // 树节点不应包含内部字段
  515 + .andExpect(jsonPath("$.data[0].sProcedureName").doesNotExist())
  516 + .andExpect(jsonPath("$.data[0].sModuleType").doesNotExist())
  517 + .andExpect(jsonPath("$.data[0].bShowPermission").doesNotExist())
  518 + .andExpect(jsonPath("$.data[0].tCreateDate").doesNotExist())
  519 + .andExpect(jsonPath("$.data[0].bDeleted").doesNotExist());
  520 + }
  521 +
  522 + @Test
  523 + void get_leafNodeChildrenIsEmptyArrayNotNull() throws Exception {
  524 + Integer id = insertExisting("sp_get_leaf_" + System.nanoTime(), null);
  525 +
  526 + mockMvc.perform(get("/api/modules"))
  527 + .andExpect(status().isOk())
  528 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")].children").isArray())
  529 + .andExpect(jsonPath("$.data[?(@.iIncrement==" + id + ")].children.length()").value(0));
  530 + }
421 } 531 }