develop: 开发员工管理模块(端到端验证)
This commit is contained in:
parent
0b5e437650
commit
661eb156b7
@ -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<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)
|
||||
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<String, Object> 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<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)
|
||||
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));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user