staff_mgr/src/test/java/com/hr/staff/StaffApiIntegrationTest.java

261 lines
11 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.hr.staff;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hr.staff.security.JwtService;
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.http.MediaType;
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;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* 员工管理模块端到端集成测试H2 内存库 + MockMvc + JWT
*/
@SpringBootTest
@AutoConfigureMockMvc
class StaffApiIntegrationTest {
private static final AtomicInteger SEQ = new AtomicInteger(1);
private static final AtomicInteger ID_SEQ = new AtomicInteger(1);
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@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 uniqueNo() {
return "EMP" + String.format("%08d", SEQ.incrementAndGet());
}
/**
* 生成符合身份证号格式且每次调用都不重复的 18 位身份证号。
* 结构:地区码(6) + 出生日期(8) + 顺序码(3) + 校验位(1)。
*/
private String uniqueIdNumber() {
int n = ID_SEQ.getAndIncrement() % 1000;
return String.format("41012319900101%03d4", n);
}
private String maskedId(String idNumber) {
return idNumber.substring(0, 3)
+ "*".repeat(idNumber.length() - 6)
+ idNumber.substring(idNumber.length() - 3);
}
private Map<String, Object> createBody(String employeeNo, String idNumber) {
Map<String, Object> 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<String, Object> 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)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createBody(employeeNo, idNumber))))
.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();
}
@Test
void createListDetailFlow() throws Exception {
String admin = token("ADMIN", null);
String employeeNo = uniqueNo();
String idNumber = uniqueIdNumber();
long id = create(admin, employeeNo, idNumber);
mockMvc.perform(get("/api/v1/staff")
.header("Authorization", "Bearer " + admin)
.param("keyword", employeeNo))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.totalElements").value(1))
.andExpect(jsonPath("$.data.content[0].phone").value("138****8000"));
mockMvc.perform(get("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber)))
.andExpect(jsonPath("$.data.extra.technicalLevel").value("P6"))
.andExpect(jsonPath("$.data.emergencyPhone").value("13900139000"));
}
@Test
void duplicateEmployeeNoConflict() throws Exception {
String admin = token("ADMIN", null);
String employeeNo = uniqueNo();
create(admin, employeeNo);
mockMvc.perform(post("/api/v1/staff")
.header("Authorization", "Bearer " + admin)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createBody(employeeNo, uniqueIdNumber()))))
.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);
// 同部门员工:脱敏、敏感字段为 null
String sameDeptEmp = token("EMPLOYEE", 1001L);
mockMvc.perform(get("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + sameDeptEmp))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.idNumber").value(maskedId(idNumber)))
.andExpect(jsonPath("$.data.extra.technicalLevel").isEmpty())
.andExpect(jsonPath("$.data.emergencyPhone").value("139****9000"));
// 跨部门员工:禁止查看
String crossDeptEmp = token("EMPLOYEE", 1002L);
mockMvc.perform(get("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + crossDeptEmp))
.andExpect(status().isForbidden());
}
@Test
void updateAndChangeLog() throws Exception {
String admin = token("ADMIN", null);
String hr = token("HR", null);
String employeeNo = uniqueNo();
long id = create(admin, employeeNo);
Map<String, Object> update = new LinkedHashMap<>();
update.put("position", "高级Java开发工程师");
mockMvc.perform(put("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + hr)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(update)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.position").value("高级Java开发工程师"));
mockMvc.perform(get("/api/v1/staff/{id}/change-logs", id)
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.content[0].changeType").value("POSITION_CHANGE"))
.andExpect(jsonPath("$.data.content[0].newValue").value("高级Java开发工程师"));
}
@Test
void checkEmployeeNo() throws Exception {
String admin = token("ADMIN", null);
String employeeNo = uniqueNo();
create(admin, employeeNo);
mockMvc.perform(get("/api/v1/staff/check/employee-no")
.header("Authorization", "Bearer " + admin)
.param("employeeNo", employeeNo))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.exists").value(true));
mockMvc.perform(get("/api/v1/staff/check/employee-no")
.header("Authorization", "Bearer " + admin)
.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);
mockMvc.perform(delete("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.status").value("inactive"));
// 重复删除应失败
mockMvc.perform(delete("/api/v1/staff/{id}", id)
.header("Authorization", "Bearer " + admin))
.andExpect(status().isBadRequest());
}
@Test
void batchDeleteFlow() throws Exception {
String admin = token("ADMIN", null);
long id1 = create(admin, uniqueNo());
long id2 = create(admin, uniqueNo());
Map<String, Object> body = new LinkedHashMap<>();
body.put("ids", new long[]{id1, id2});
MvcResult result = mockMvc.perform(post("/api/v1/staff/batch-delete")
.header("Authorization", "Bearer " + admin)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(body)))
.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);
}
@Test
void auditLogsAndDepartments() throws Exception {
String admin = token("ADMIN", null);
create(admin, uniqueNo());
mockMvc.perform(get("/api/v1/staff/audit-logs")
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.totalElements").isNumber());
mockMvc.perform(get("/api/v1/staff/departments")
.header("Authorization", "Bearer " + admin))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.length()").value(3));
}
}