From 661eb156b702301b2aa8c2c2c08f9693a699331b Mon Sep 17 00:00:00 2001 From: Pipeline Agent Date: Sat, 15 Aug 2026 01:20:44 +0800 Subject: [PATCH] =?UTF-8?q?develop:=20=E5=BC=80=E5=8F=91=E5=91=98=E5=B7=A5?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=EF=BC=88=E7=AB=AF=E5=88=B0?= =?UTF-8?q?=E7=AB=AF=E9=AA=8C=E8=AF=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/hr/staff/StaffApiIntegrationTest.java | 270 ++++++++---------- 1 file changed, 120 insertions(+), 150 deletions(-) diff --git a/src/test/java/com/hr/staff/StaffApiIntegrationTest.java b/src/test/java/com/hr/staff/StaffApiIntegrationTest.java index 9fbe959..92c4bd3 100644 --- a/src/test/java/com/hr/staff/StaffApiIntegrationTest.java +++ b/src/test/java/com/hr/staff/StaffApiIntegrationTest.java @@ -1,22 +1,20 @@ package com.hr.staff; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.hr.staff.repository.StaffEmployeeRepository; +import com.hr.staff.security.AuthUser; import com.hr.staff.security.JwtService; +import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; 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.http.MediaType; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -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; @@ -29,231 +27,203 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. */ @SpringBootTest @AutoConfigureMockMvc +@ActiveProfiles("dev") +@TestMethodOrder(MethodOrderer.MethodName.class) class StaffApiIntegrationTest { - private static final AtomicInteger SEQ = new AtomicInteger(1); - private static final AtomicInteger ID_SEQ = new AtomicInteger(1); + @Autowired + private MockMvc mockMvc; @Autowired - MockMvc mockMvc; + private JwtService jwtService; @Autowired - ObjectMapper objectMapper; + private StaffEmployeeRepository employeeRepository; - @Autowired - JwtService jwtService; - - private String token(String role, Long deptId) { - return jwtService.generate(9000L + SEQ.get(), role.toLowerCase() + "_user", Set.of(role), deptId); + private String adminToken() { + return "Bearer " + jwtService.generateToken(new AuthUser(1L, "admin", Set.of("ADMIN"), 1003L)); } - private String uniqueNo() { - return "EMP" + String.format("%08d", SEQ.incrementAndGet()); + private String hrToken() { + return "Bearer " + jwtService.generateToken(new AuthUser(2L, "hr", Set.of("HR"), 1003L)); } - /** - * 生成符合身份证号格式且每次调用都不重复的 18 位身份证号。 - * 结构:地区码(6) + 出生日期(8) + 顺序码(3) + 校验位(1)。 - */ - private String uniqueIdNumber() { - int n = ID_SEQ.getAndIncrement() % 1000; - return String.format("41012319900101%03d4", n); + private String employeeToken(Long departmentId) { + return "Bearer " + jwtService.generateToken(new AuthUser(10L, "employee", Set.of("EMPLOYEE"), departmentId)); } - private String maskedId(String idNumber) { - return idNumber.substring(0, 3) - + "*".repeat(idNumber.length() - 6) - + idNumber.substring(idNumber.length() - 3); + private String createBody(String employeeNo, String name, String idNumber, Long departmentId) { + return """ + { + "employeeNo": "%s", + "name": "%s", + "idNumber": "%s", + "gender": 1, + "departmentId": %d, + "position": "Java开发工程师", + "hireDate": "2024-01-10", + "phone": "13800138000", + "email": "zhangsan@example.com", + "emergencyContact": "张父", + "emergencyPhone": "13900139000", + "status": "active", + "extra": { + "education": "本科", + "technicalLevel": "P6", + "salaryGrade": "S3", + "address": "北京市朝阳区" + } + } + """.formatted(employeeNo, name, idNumber, departmentId); } - private Map createBody(String employeeNo, String idNumber) { - Map body = new LinkedHashMap<>(); - body.put("employeeNo", employeeNo); - body.put("name", "张三"); - body.put("idNumber", idNumber); - body.put("gender", 1); - body.put("departmentId", 1001L); - body.put("position", "Java开发工程师"); - body.put("hireDate", "2024-01-10"); - body.put("phone", "13800138000"); - body.put("email", "zhangsan@example.com"); - body.put("emergencyContact", "张父"); - body.put("emergencyPhone", "13900139000"); - Map extra = new LinkedHashMap<>(); - extra.put("education", "本科"); - extra.put("technicalLevel", "P6"); - extra.put("salaryGrade", "S3"); - extra.put("address", "北京市朝阳区"); - body.put("extra", extra); - return body; - } - - private long create(String token, String employeeNo) throws Exception { - return create(token, employeeNo, uniqueIdNumber()); - } - - private long create(String token, String employeeNo, String idNumber) throws Exception { - MvcResult result = mockMvc.perform(post("/api/v1/staff") - .header("Authorization", "Bearer " + token) + private void createEmployee(String employeeNo, String name, String idNumber, Long departmentId) throws Exception { + mockMvc.perform(post("/api/v1/staff") + .header("Authorization", adminToken()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(createBody(employeeNo, idNumber)))) + .content(createBody(employeeNo, name, idNumber, departmentId))) .andExpect(status().isCreated()) - .andExpect(jsonPath("$.code").value(201)) - .andExpect(jsonPath("$.data.employeeNo").value(employeeNo)) - .andReturn(); - return objectMapper.readTree(result.getResponse().getContentAsString()) - .path("data").path("id").asLong(); + .andExpect(jsonPath("$.code").value(201)); + } + + private Long employeeId(String employeeNo) { + return employeeRepository.findByEmployeeNo(employeeNo).orElseThrow().getId(); } @Test - void createListDetailFlow() throws Exception { - String admin = token("ADMIN", null); - String employeeNo = uniqueNo(); - String idNumber = uniqueIdNumber(); - long id = create(admin, employeeNo, idNumber); + void test01_createListAndDetail() throws Exception { + createEmployee("EMP20240001", "张三", "110101199001011234", 1001L); mockMvc.perform(get("/api/v1/staff") - .header("Authorization", "Bearer " + admin) - .param("keyword", employeeNo)) + .header("Authorization", adminToken()) + .param("page", "1").param("size", "20")) .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.totalElements").value(1)) + .andExpect(jsonPath("$.data.content[0].employeeNo").value("EMP20240001")) .andExpect(jsonPath("$.data.content[0].phone").value("138****8000")); - mockMvc.perform(get("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + admin)) + Long id = employeeId("EMP20240001"); + mockMvc.perform(get("/api/v1/staff/" + id).header("Authorization", adminToken())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber))) + .andExpect(jsonPath("$.data.idNumber").value("110************234")) + .andExpect(jsonPath("$.data.phone").value("138****8000")) + .andExpect(jsonPath("$.data.emergencyPhone").value("13900139000")) .andExpect(jsonPath("$.data.extra.technicalLevel").value("P6")) - .andExpect(jsonPath("$.data.emergencyPhone").value("13900139000")); + .andExpect(jsonPath("$.data.extra.salaryGrade").value("S3")); } @Test - void duplicateEmployeeNoConflict() throws Exception { - String admin = token("ADMIN", null); - String employeeNo = uniqueNo(); - create(admin, employeeNo); - + void test02_duplicateEmployeeNoReturns409() throws Exception { + createEmployee("EMP20240002", "李四", "110101199001011235", 1001L); mockMvc.perform(post("/api/v1/staff") - .header("Authorization", "Bearer " + admin) + .header("Authorization", adminToken()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(createBody(employeeNo, uniqueIdNumber())))) + .content(createBody("EMP20240002", "王五", "110101199001011236", 1002L))) .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value(409)); } @Test - void employeeMaskingAndDataScope() throws Exception { - String admin = token("ADMIN", null); - String employeeNo = uniqueNo(); - String idNumber = uniqueIdNumber(); - long id = create(admin, employeeNo, idNumber); + void test03_employeeDataPermissionAndMasking() throws Exception { + createEmployee("EMP20240003", "赵六", "110101199001011237", 1001L); + Long id = employeeId("EMP20240003"); - // 同部门员工:脱敏、敏感字段为 null - String sameDeptEmp = token("EMPLOYEE", 1001L); - mockMvc.perform(get("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + sameDeptEmp)) + // 本部门员工可查看,但敏感字段脱敏 + mockMvc.perform(get("/api/v1/staff/" + id).header("Authorization", employeeToken(1001L))) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber))) - .andExpect(jsonPath("$.data.extra.technicalLevel").isEmpty()) - .andExpect(jsonPath("$.data.emergencyPhone").value("139****9000")); + .andExpect(jsonPath("$.data.idNumber").value("110************237")) + .andExpect(jsonPath("$.data.phone").value("138****8000")) + .andExpect(jsonPath("$.data.emergencyPhone").value("139****9000")) + .andExpect(jsonPath("$.data.extra.technicalLevel").doesNotExist()); - // 跨部门员工:禁止查看 - String crossDeptEmp = token("EMPLOYEE", 1002L); - mockMvc.perform(get("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + crossDeptEmp)) - .andExpect(status().isForbidden()); + // 跨部门员工无权限 + mockMvc.perform(get("/api/v1/staff/" + id).header("Authorization", employeeToken(1002L))) + .andExpect(status().isForbidden()) + .andExpect(jsonPath("$.code").value(403)); } @Test - void updateAndChangeLog() throws Exception { - String admin = token("ADMIN", null); - String hr = token("HR", null); - String employeeNo = uniqueNo(); - long id = create(admin, employeeNo); + void test04_updateGeneratesChangeLog() throws Exception { + createEmployee("EMP20240004", "孙七", "110101199001011238", 1001L); + Long id = employeeId("EMP20240004"); - Map update = new LinkedHashMap<>(); - update.put("position", "高级Java开发工程师"); - mockMvc.perform(put("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + hr) + mockMvc.perform(put("/api/v1/staff/" + id) + .header("Authorization", adminToken()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(update))) + .content(""" + {"position": "高级Java开发工程师", "extra": {"technicalLevel": "P7"}} + """)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.position").value("高级Java开发工程师")); - mockMvc.perform(get("/api/v1/staff/{id}/change-logs", id) - .header("Authorization", "Bearer " + admin)) + mockMvc.perform(get("/api/v1/staff/" + id + "/change-logs") + .header("Authorization", adminToken())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.content[0].changeType").value("POSITION_CHANGE")) - .andExpect(jsonPath("$.data.content[0].newValue").value("高级Java开发工程师")); + .andExpect(jsonPath("$.data.totalElements").value(2)) + .andExpect(jsonPath("$.data.content[0].changeType").value("LEVEL_CHANGE")) + .andExpect(jsonPath("$.data.content[1].changeType").value("POSITION_CHANGE")); } @Test - void checkEmployeeNo() throws Exception { - String admin = token("ADMIN", null); - String employeeNo = uniqueNo(); - create(admin, employeeNo); + void test05_checkEmployeeNo() throws Exception { + createEmployee("EMP20240005", "周八", "110101199001011239", 1001L); mockMvc.perform(get("/api/v1/staff/check/employee-no") - .header("Authorization", "Bearer " + admin) - .param("employeeNo", employeeNo)) + .header("Authorization", adminToken()) + .param("employeeNo", "EMP20240005")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.exists").value(true)); mockMvc.perform(get("/api/v1/staff/check/employee-no") - .header("Authorization", "Bearer " + admin) + .header("Authorization", adminToken()) .param("employeeNo", "NOT_EXIST")) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.exists").value(false)); } @Test - void deleteFlow() throws Exception { - String admin = token("ADMIN", null); - String employeeNo = uniqueNo(); - long id = create(admin, employeeNo); + void test06_logicalDeleteAndRepeatProtection() throws Exception { + createEmployee("EMP20240006", "吴九", "110101199001011240", 1001L); + Long id = employeeId("EMP20240006"); - mockMvc.perform(delete("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + admin)) + mockMvc.perform(delete("/api/v1/staff/" + id).header("Authorization", adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.status").value("inactive")); - // 重复删除应失败 - mockMvc.perform(delete("/api/v1/staff/{id}", id) - .header("Authorization", "Bearer " + admin)) - .andExpect(status().isBadRequest()); + mockMvc.perform(delete("/api/v1/staff/" + id).header("Authorization", adminToken())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); } @Test - void batchDeleteFlow() throws Exception { - String admin = token("ADMIN", null); - long id1 = create(admin, uniqueNo()); - long id2 = create(admin, uniqueNo()); + void test07_batchDelete() throws Exception { + createEmployee("EMP20240007", "郑十", "110101199001011241", 1001L); + createEmployee("EMP20240008", "钱一", "110101199001011242", 1001L); + Long id1 = employeeId("EMP20240007"); + Long id2 = employeeId("EMP20240008"); - Map body = new LinkedHashMap<>(); - body.put("ids", new long[]{id1, id2}); - MvcResult result = mockMvc.perform(post("/api/v1/staff/batch-delete") - .header("Authorization", "Bearer " + admin) + mockMvc.perform(post("/api/v1/staff/batch-delete") + .header("Authorization", adminToken()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(body))) + .content("{\"ids\": [" + id1 + ", " + id2 + "]}")) .andExpect(status().isOk()) - .andReturn(); - JsonNode data = objectMapper.readTree(result.getResponse().getContentAsString()).path("data"); - assertThat(data.path("successCount").asInt()).isEqualTo(2); - assertThat(data.path("failedList").size()).isEqualTo(0); + .andExpect(jsonPath("$.data.successCount").value(2)) + .andExpect(jsonPath("$.data.failedList.length()").value(0)); } @Test - void auditLogsAndDepartments() throws Exception { - String admin = token("ADMIN", null); - create(admin, uniqueNo()); + void test08_auditLogsAndDepartments() throws Exception { + createEmployee("EMP20240009", "冯二", "110101199001011243", 1001L); mockMvc.perform(get("/api/v1/staff/audit-logs") - .header("Authorization", "Bearer " + admin)) + .header("Authorization", adminToken()) + .param("operation", "CREATE")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.totalElements").isNumber()); + .andExpect(jsonPath("$.data.totalElements").value(1)) + .andExpect(jsonPath("$.data.content[0].operation").value("CREATE")); - mockMvc.perform(get("/api/v1/staff/departments") - .header("Authorization", "Bearer " + admin)) + mockMvc.perform(get("/api/v1/staff/departments").header("Authorization", adminToken())) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.length()").value(3)); }