UsrUserUpdateIT.java
13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package com.xly.erp.modules.usr;
import static org.assertj.core.api.Assertions.assertThat;
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;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xly.erp.common.security.JwtUtil;
import com.xly.erp.modules.usr.entity.UsrPermission;
import com.xly.erp.modules.usr.entity.UsrUser;
import com.xly.erp.modules.usr.entity.UsrUserPermission;
import com.xly.erp.modules.usr.mapper.UsrPermissionMapper;
import com.xly.erp.modules.usr.mapper.UsrUserMapper;
import com.xly.erp.modules.usr.mapper.UsrUserPermissionMapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
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.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
/**
* REQ-USR-002 T5:修改用户端到端验收回归(spec § 7)。
*
* <p>@SpringBootTest + 真实 MockMvc 安全链 + test profile 连测试库(Flyway 已 apply V1)。
* 认证态通过真实 JwtUtil 签发 token 走 JwtAuthenticationFilter 注入;真实库读写做端到端确认。
* 每个用例自管理 fixture 清理(命名前缀 {@code it2_user_} / {@code IT2_PERM_})。</p>
*
* <p>边界说明(spec § 7):AC4「禁用实时生效」、AC5「角色变更实时生效」依赖 REQ-USR-004 登录 /
* REQ-USR-003 查询接口,尚未实现;本 IT 以「PUT iIsVoid=1 / 改 sUserType 后 selectById
* 读回库内 iIsVoid==1 / sUserType 为新值」做后端落库层等价验证,登录 / 查询联动留待对应 REQ
* 的 IT 覆盖。</p>
*/
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class UsrUserUpdateIT {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UsrUserMapper usrUserMapper;
@Autowired
private UsrUserPermissionMapper usrUserPermissionMapper;
@Autowired
private UsrPermissionMapper usrPermissionMapper;
private static final String CALLER = "it2_admin";
@AfterEach
void cleanup() {
usrUserMapper.delete(Wrappers.<UsrUser>lambdaQuery()
.likeRight(UsrUser::getSUserName, "it2_user_"));
usrPermissionMapper.delete(Wrappers.<UsrPermission>lambdaQuery()
.likeRight(UsrPermission::getSPermissionCode, "IT2_PERM_"));
}
private String adminToken() {
return "Bearer " + jwtUtil.generateToken(CALLER, "超级管理员");
}
private String normalToken() {
return "Bearer " + jwtUtil.generateToken("it2_normal", "普通用户");
}
private Map<String, Object> createBody(String userName) {
Map<String, Object> body = new HashMap<>();
body.put("sUserName", userName);
body.put("sLanguage", "中文");
return body;
}
private int createUser(String userName) throws Exception {
MvcResult result = mockMvc.perform(post("/api/usr/users")
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(createBody(userName))))
.andExpect(jsonPath("$.code").value(0))
.andReturn();
Map<?, ?> resp = objectMapper.readValue(result.getResponse().getContentAsString(), Map.class);
return (Integer) ((Map<?, ?>) resp.get("data")).get("id");
}
private int insertPermissionFixture(String codeSuffix) {
UsrPermission perm = new UsrPermission();
perm.setSPermissionName("IT2权限" + codeSuffix);
perm.setSPermissionCode("IT2_PERM_" + codeSuffix);
usrPermissionMapper.insert(perm);
return perm.getIIncrement();
}
// ---------------- AC1:基本信息修改落库,且身份 / 密码 / 审计列不变 ----------------
@Test
void ac1UpdateBasicInfoPersists() throws Exception {
int id = createUser("it2_user_ac1");
UsrUser before = usrUserMapper.selectById(id);
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "超级管理员");
body.put("sLanguage", "英文");
body.put("iCanModifyBill", 1);
body.put("sUserNo", "NO-AC1");
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.id").value(id));
UsrUser after = usrUserMapper.selectById(id);
assertThat(after.getSUserType()).isEqualTo("超级管理员");
assertThat(after.getSLanguage()).isEqualTo("英文");
assertThat(after.getICanModifyBill()).isEqualTo(1);
assertThat(after.getSUserNo()).isEqualTo("NO-AC1");
// 身份 / 密码 / 审计列字节级不变。
assertThat(after.getSUserName()).isEqualTo(before.getSUserName());
assertThat(after.getSPassword()).isEqualTo(before.getSPassword());
assertThat(after.getSCreator()).isEqualTo(before.getSCreator());
assertThat(after.getTCreateDate()).isEqualTo(before.getTCreateDate());
}
// ---------------- AC2:目标用户不存在 → 40401,无写入 ----------------
@Test
void ac2UpdateNonExistentReturns40401() throws Exception {
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "普通用户");
body.put("sLanguage", "中文");
mockMvc.perform(put("/api/usr/users/2000000001")
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(40401));
assertThat(usrUserMapper.selectById(2000000001)).isNull();
}
// ---------------- AC3:非法参数(不存在职员)→ 40001,整体回滚无副作用 ----------------
@Test
void ac3InvalidParamRollsBack() throws Exception {
int id = createUser("it2_user_ac3");
UsrUser before = usrUserMapper.selectById(id);
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "超级管理员");
body.put("sLanguage", "英文");
body.put("iEmployeeId", 2000000002); // 不存在的职员
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(40001));
UsrUser after = usrUserMapper.selectById(id);
// 目标用户行各列与调用前一致(无副作用)。
assertThat(after.getSUserType()).isEqualTo(before.getSUserType());
assertThat(after.getSLanguage()).isEqualTo(before.getSLanguage());
assertThat(after.getIEmployeeId()).isEqualTo(before.getIEmployeeId());
}
// ---------------- AC6:权限组全量覆盖 / 清空 / 不改 ----------------
@Test
void ac6PermissionOverwrite() throws Exception {
int id = createUser("it2_user_ac6");
int permA = insertPermissionFixture("A");
int permB = insertPermissionFixture("B");
int permC = insertPermissionFixture("C");
// 预置该用户授权为 {a, c}
usrUserPermissionMapper.insert(new UsrUserPermission(id, permA));
usrUserPermissionMapper.insert(new UsrUserPermission(id, permC));
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "普通用户");
body.put("sLanguage", "中文");
body.put("permissionIds", List.of(permA, permB, permA)); // 去重后 {a, b}
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(jsonPath("$.code").value(0));
List<UsrUserPermission> grants = usrUserPermissionMapper.selectList(
Wrappers.<UsrUserPermission>lambdaQuery().eq(UsrUserPermission::getIUserId, id));
assertThat(grants).extracting(UsrUserPermission::getIPermissionId)
.containsExactlyInAnyOrder(permA, permB); // c 被删、b 新增、a 去重一次
// 传 [] → 清空全部授权
Map<String, Object> clearBody = new HashMap<>();
clearBody.put("sUserType", "普通用户");
clearBody.put("sLanguage", "中文");
clearBody.put("permissionIds", List.of());
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(clearBody)))
.andExpect(jsonPath("$.code").value(0));
long afterClear = usrUserPermissionMapper.selectCount(
Wrappers.<UsrUserPermission>lambdaQuery().eq(UsrUserPermission::getIUserId, id));
assertThat(afterClear).isZero();
// 预置一条授权后,不传 permissionIds → 授权不变
usrUserPermissionMapper.insert(new UsrUserPermission(id, permA));
Map<String, Object> noPermBody = new HashMap<>();
noPermBody.put("sUserType", "普通用户");
noPermBody.put("sLanguage", "中文");
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(noPermBody)))
.andExpect(jsonPath("$.code").value(0));
long afterNoChange = usrUserPermissionMapper.selectCount(
Wrappers.<UsrUserPermission>lambdaQuery().eq(UsrUserPermission::getIUserId, id));
assertThat(afterNoChange).isEqualTo(1L);
}
// ---------------- AC7:非管理员 / 无 token 被拦截,目标行未被修改 ----------------
@Test
void ac7NonAdminAndNoTokenBlocked() throws Exception {
int id = createUser("it2_user_ac7");
UsrUser before = usrUserMapper.selectById(id);
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "超级管理员");
body.put("sLanguage", "英文");
// 普通用户 token → 40301
mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", normalToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(jsonPath("$.code").value(40301));
// 无 token → 401(安全链拦截)
mockMvc.perform(put("/api/usr/users/" + id)
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(status().isUnauthorized());
UsrUser after = usrUserMapper.selectById(id);
assertThat(after.getSUserType()).isEqualTo(before.getSUserType());
assertThat(after.getSLanguage()).isEqualTo(before.getSLanguage());
}
// ---------------- AC8:密码不变且不出现在响应;iIsVoid 落库(AC4/AC5 落库层等价验证) ----------------
@Test
void ac8PasswordUnchangedAndAbsentFromResponse() throws Exception {
int id = createUser("it2_user_ac8");
UsrUser before = usrUserMapper.selectById(id);
Map<String, Object> body = new HashMap<>();
body.put("sUserType", "超级管理员"); // AC5 角色变更落库层等价验证
body.put("sLanguage", "中文");
body.put("iIsVoid", 1); // AC4 禁用落库层等价验证
MvcResult result = mockMvc.perform(put("/api/usr/users/" + id)
.header("Authorization", adminToken())
.contentType("application/json")
.content(objectMapper.writeValueAsString(body)))
.andExpect(jsonPath("$.code").value(0))
.andExpect(jsonPath("$.data.id").value(id))
.andExpect(jsonPath("$.data.sPassword").doesNotExist())
.andExpect(jsonPath("$.data.password").doesNotExist())
.andReturn();
String responseBody = result.getResponse().getContentAsString();
assertThat(responseBody.toLowerCase()).doesNotContain("password");
UsrUser after = usrUserMapper.selectById(id);
// 密码列字节级不变。
assertThat(after.getSPassword()).isEqualTo(before.getSPassword());
// AC4 / AC5 落库层等价验证:禁用状态 / 角色变更读回库内为新值。
assertThat(after.getIIsVoid()).isEqualTo(1);
assertThat(after.getSUserType()).isEqualTo("超级管理员");
}
}