updateDeptLogic_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. package dept
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "math"
  7. "perms-system-server/internal/consts"
  8. "perms-system-server/internal/loaders"
  9. "perms-system-server/internal/middleware"
  10. deptModel "perms-system-server/internal/model/dept"
  11. userModel "perms-system-server/internal/model/user"
  12. "perms-system-server/internal/response"
  13. "perms-system-server/internal/svc"
  14. "perms-system-server/internal/testutil"
  15. "perms-system-server/internal/testutil/ctxhelper"
  16. "perms-system-server/internal/testutil/mocks"
  17. "perms-system-server/internal/types"
  18. "testing"
  19. "time"
  20. "github.com/stretchr/testify/assert"
  21. "github.com/stretchr/testify/require"
  22. "go.uber.org/mock/gomock"
  23. )
  24. func TestUpdateDept_Normal(t *testing.T) {
  25. ctx := ctxhelper.SuperAdminCtx()
  26. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  27. conn := testutil.GetTestSqlConn()
  28. deptId, err := insertDeptRaw(ctx, svcCtx, 0, "upd_"+testutil.UniqueId(), "/")
  29. require.NoError(t, err)
  30. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  31. newName := "updated_" + testutil.UniqueId()
  32. l := NewUpdateDeptLogic(ctx, svcCtx)
  33. err = l.UpdateDept(&types.UpdateDeptReq{
  34. Id: deptId,
  35. Name: newName,
  36. Sort: 99,
  37. Remark: "updated remark",
  38. Status: 2,
  39. })
  40. require.NoError(t, err)
  41. d, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  42. require.NoError(t, err)
  43. assert.Equal(t, newName, d.Name)
  44. assert.Equal(t, int64(99), d.Sort)
  45. assert.Equal(t, "updated remark", d.Remark)
  46. assert.Equal(t, int64(2), d.Status)
  47. }
  48. // TC-0102: 不存在
  49. func TestUpdateDept_NotFound(t *testing.T) {
  50. ctx := ctxhelper.SuperAdminCtx()
  51. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  52. l := NewUpdateDeptLogic(ctx, svcCtx)
  53. err := l.UpdateDept(&types.UpdateDeptReq{
  54. Id: 999999999,
  55. Name: "ghost_" + testutil.UniqueId(),
  56. })
  57. require.Error(t, err)
  58. var ce *response.CodeError
  59. require.True(t, errors.As(err, &ce))
  60. assert.Equal(t, 404, ce.Code())
  61. assert.Equal(t, "部门不存在", ce.Error())
  62. }
  63. // TC-0101: 正常更新
  64. func TestUpdateDept_StatusZeroUnchanged(t *testing.T) {
  65. ctx := ctxhelper.SuperAdminCtx()
  66. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  67. conn := testutil.GetTestSqlConn()
  68. deptId, err := insertDeptRaw(ctx, svcCtx, 0, "s0_"+testutil.UniqueId(), "/")
  69. require.NoError(t, err)
  70. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  71. before, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  72. require.NoError(t, err)
  73. assert.Equal(t, int64(1), before.Status)
  74. l := NewUpdateDeptLogic(ctx, svcCtx)
  75. err = l.UpdateDept(&types.UpdateDeptReq{
  76. Id: deptId,
  77. Name: "changed_" + testutil.UniqueId(),
  78. Status: 0,
  79. })
  80. require.NoError(t, err)
  81. after, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  82. require.NoError(t, err)
  83. assert.Equal(t, int64(1), after.Status)
  84. }
  85. // TC-0103: DeptType NORMAL→DEV
  86. func TestUpdateDept_DeptType_NormalToDev(t *testing.T) {
  87. ctx := ctxhelper.SuperAdminCtx()
  88. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  89. conn := testutil.GetTestSqlConn()
  90. deptId, err := insertDeptRaw(ctx, svcCtx, 0, "dt_"+testutil.UniqueId(), "/")
  91. require.NoError(t, err)
  92. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  93. before, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  94. require.NoError(t, err)
  95. assert.Equal(t, "NORMAL", before.DeptType)
  96. l := NewUpdateDeptLogic(ctx, svcCtx)
  97. err = l.UpdateDept(&types.UpdateDeptReq{
  98. Id: deptId,
  99. Name: "dt_changed_" + testutil.UniqueId(),
  100. DeptType: "DEV",
  101. })
  102. require.NoError(t, err)
  103. after, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  104. require.NoError(t, err)
  105. assert.Equal(t, "DEV", after.DeptType)
  106. }
  107. // TC-0104: DeptType无效值返回错误
  108. func TestUpdateDept_DeptType_InvalidRejected(t *testing.T) {
  109. ctx := ctxhelper.SuperAdminCtx()
  110. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  111. conn := testutil.GetTestSqlConn()
  112. deptId, err := insertDeptRaw(ctx, svcCtx, 0, "dti_"+testutil.UniqueId(), "/")
  113. require.NoError(t, err)
  114. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  115. l := NewUpdateDeptLogic(ctx, svcCtx)
  116. err = l.UpdateDept(&types.UpdateDeptReq{
  117. Id: deptId,
  118. Name: "dti_changed_" + testutil.UniqueId(),
  119. DeptType: "INVALID_TYPE",
  120. })
  121. require.Error(t, err)
  122. var ce *response.CodeError
  123. require.True(t, errors.As(err, &ce))
  124. assert.Equal(t, 400, ce.Code())
  125. assert.Contains(t, ce.Error(), "部门类型无效")
  126. after, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  127. require.NoError(t, err)
  128. assert.Equal(t, "NORMAL", after.DeptType, "无效DeptType不应修改数据库")
  129. }
  130. // TC-0533: updateDept非超管拒绝
  131. func TestUpdateDept_NonSuperAdminRejected(t *testing.T) {
  132. ctx := ctxhelper.AdminCtx("test_product")
  133. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  134. l := NewUpdateDeptLogic(ctx, svcCtx)
  135. err := l.UpdateDept(&types.UpdateDeptReq{Id: 1, Name: "test"})
  136. require.Error(t, err)
  137. var ce *response.CodeError
  138. require.True(t, errors.As(err, &ce))
  139. assert.Equal(t, 403, ce.Code())
  140. }
  141. func superAdminCtx() context.Context {
  142. return middleware.WithUserDetails(context.Background(), &loaders.UserDetails{
  143. UserId: 1, Username: "su",
  144. IsSuperAdmin: true, MemberType: consts.MemberTypeSuperAdmin,
  145. Status: consts.StatusEnabled,
  146. })
  147. }
  148. // TC-0848: deptType 变更 → FindIdsByDeptId 恰好调用 1 次,返回 [100, 101];handler 返回 nil。
  149. // NORMAL→DEV 方向是放宽权限,不吊销会话;post-commit 仍需 FindIdsByDeptId 清 UD 聚合缓存。
  150. func TestUpdateDept_DeptTypeChanged_InvokesFindIdsOnce(t *testing.T) {
  151. ctrl := gomock.NewController(t)
  152. t.Cleanup(ctrl.Finish)
  153. deptMock := mocks.NewMockSysDeptModel(ctrl)
  154. userMock := mocks.NewMockSysUserModel(ctrl)
  155. // 老部门是 NORMAL,请求改成 DEV → deptTypeChanged=true 才能触发 FindIdsByDeptId。
  156. deptMock.EXPECT().FindOne(gomock.Any(), int64(77)).
  157. Return(&deptModel.SysDept{
  158. Id: 77, Name: "n", DeptType: consts.DeptTypeNormal,
  159. Status: consts.StatusEnabled, UpdateTime: 500,
  160. }, nil)
  161. runDeptTxInline(deptMock)
  162. deptMock.EXPECT().UpdateWithOptLockTx(gomock.Any(), gomock.Any(), gomock.Any(), int64(500)).Return(nil)
  163. deptMock.EXPECT().InvalidateDeptCache(gomock.Any(), int64(77))
  164. // 关键断言:post-commit 恰好调用 1 次;返回的切片会被继续塞进 CleanByUserIds(loader 内部走 Redis)。
  165. userMock.EXPECT().FindIdsByDeptId(gomock.Any(), int64(77)).
  166. Return([]int64{100, 101}, nil).Times(1)
  167. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
  168. Dept: deptMock, User: userMock,
  169. })
  170. err := NewUpdateDeptLogic(superAdminCtx(), svcCtx).UpdateDept(&types.UpdateDeptReq{
  171. Id: 77, Name: "n", Sort: 0, Remark: "", DeptType: consts.DeptTypeDev,
  172. })
  173. require.NoError(t, err,
  174. "正常场景 UpdateDept 必须返回 nil,且仅调用一次 FindIdsByDeptId")
  175. }
  176. // TC-0849: FindIdsByDeptId 返回 err —— handler 仍返回 nil(degraded 成功),旧缓存由 TTL 过期兜底。
  177. func TestUpdateDept_FindIdsByDeptIdError_DegradedSuccess(t *testing.T) {
  178. ctrl := gomock.NewController(t)
  179. t.Cleanup(ctrl.Finish)
  180. deptMock := mocks.NewMockSysDeptModel(ctrl)
  181. userMock := mocks.NewMockSysUserModel(ctrl)
  182. deptMock.EXPECT().FindOne(gomock.Any(), int64(88)).
  183. Return(&deptModel.SysDept{
  184. Id: 88, Name: "n", DeptType: consts.DeptTypeNormal,
  185. Status: consts.StatusEnabled, UpdateTime: 1000,
  186. }, nil)
  187. runDeptTxInline(deptMock)
  188. deptMock.EXPECT().UpdateWithOptLockTx(gomock.Any(), gomock.Any(), gomock.Any(), int64(1000)).Return(nil)
  189. deptMock.EXPECT().InvalidateDeptCache(gomock.Any(), int64(88))
  190. userMock.EXPECT().FindIdsByDeptId(gomock.Any(), int64(88)).
  191. Return(nil, errors.New("transient DB error"))
  192. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
  193. Dept: deptMock, User: userMock,
  194. })
  195. err := NewUpdateDeptLogic(superAdminCtx(), svcCtx).UpdateDept(&types.UpdateDeptReq{
  196. Id: 88, Name: "n", DeptType: consts.DeptTypeDev,
  197. })
  198. assert.NoError(t, err,
  199. "FindIdsByDeptId 失败不得映射 500;TTL 过期兜底,客户端不应重试整次 UpdateDept")
  200. }
  201. // seedDeptWithUser 建一个自定义 DeptType/Status 的部门,并挂一个新 sys_user 到该部门下。
  202. // 返回 (deptId, userId);统一 cleanup 由调用方负责,避免测试之间相互拖拽。
  203. func seedDeptWithUser(t *testing.T, svcCtx *svc.ServiceContext,
  204. tag, path, deptType string, status int64) (int64, int64) {
  205. t.Helper()
  206. bootstrap := ctxhelper.SuperAdminCtx()
  207. now := time.Now().Unix()
  208. dRes, err := svcCtx.SysDeptModel.Insert(bootstrap, &deptModel.SysDept{
  209. ParentId: 0, Name: tag + "_" + testutil.UniqueId(), Path: path, Sort: 0,
  210. DeptType: deptType, Status: status, CreateTime: now, UpdateTime: now,
  211. })
  212. require.NoError(t, err)
  213. deptId, _ := dRes.LastInsertId()
  214. uRes, err := svcCtx.SysUserModel.Insert(bootstrap, &userModel.SysUser{
  215. Username: tag + "_u_" + testutil.UniqueId(), Password: testutil.HashPassword("pw"),
  216. Avatar: sql.NullString{}, DeptId: deptId,
  217. IsSuperAdmin: 2, MustChangePassword: 2, Status: 1,
  218. CreateTime: now, UpdateTime: now,
  219. })
  220. require.NoError(t, err)
  221. userId, _ := uRes.LastInsertId()
  222. return deptId, userId
  223. }
  224. // TC-1174: UpdateDept DeptType: DEV → NORMAL 必须把该部门下**所有**在编 sys_user 的
  225. // tokenVersion 原子性 +1(签发层吊销),否则 5min TTL 内旧 access token 仍享有 DEV 全权。
  226. func TestUpdateDept_L_R16_2_DevTypeToNormal_RevokesAllMembers(t *testing.T) {
  227. ctx := ctxhelper.SuperAdminCtx()
  228. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  229. conn := testutil.GetTestSqlConn()
  230. deptId, userId := seedDeptWithUser(t, svcCtx, "l_r16_dev2norm", "/7100/",
  231. consts.DeptTypeDev, consts.StatusEnabled)
  232. t.Cleanup(func() {
  233. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  234. testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
  235. })
  236. before, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  237. require.NoError(t, err)
  238. prev := before.TokenVersion
  239. require.NoError(t,
  240. NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  241. Id: deptId, Name: "renamed_" + testutil.UniqueId(), DeptType: consts.DeptTypeNormal,
  242. }))
  243. after, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  244. require.NoError(t, err)
  245. assert.Equal(t, prev+1, after.TokenVersion,
  246. "DEV→NORMAL 让部门内所有人的 loadPerms 从全权分支掉到普通分支,tokenVersion 必须同事务 +1")
  247. }
  248. // TC-1175: DEV 部门 Status Enabled → Disabled —— 同样构成 DEV 全权收窄,tokenVersion 必须 +1。
  249. func TestUpdateDept_L_R16_2_DevStatusToDisabled_RevokesAllMembers(t *testing.T) {
  250. ctx := ctxhelper.SuperAdminCtx()
  251. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  252. conn := testutil.GetTestSqlConn()
  253. deptId, userId := seedDeptWithUser(t, svcCtx, "l_r16_dev_dis", "/7200/",
  254. consts.DeptTypeDev, consts.StatusEnabled)
  255. t.Cleanup(func() {
  256. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  257. testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
  258. })
  259. before, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  260. require.NoError(t, err)
  261. prev := before.TokenVersion
  262. require.NoError(t,
  263. NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  264. Id: deptId, Name: "dev_disabled_" + testutil.UniqueId(),
  265. DeptType: consts.DeptTypeDev, Status: consts.StatusDisabled,
  266. }))
  267. after, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  268. require.NoError(t, err)
  269. assert.Equal(t, prev+1, after.TokenVersion,
  270. "DEV 部门 Enabled→Disabled 与 DeptType DEV→NORMAL 同构,都让 loadPerms 全权分支失效")
  271. }
  272. // TC-1176: NORMAL 部门 Status Enabled → Disabled —— 业务语义是"冻结本部门所有活动",
  273. // 必须把部门内成员的 tokenVersion 同事务 +1,防止 5min TTL 窗口内旧 token 继续读写。
  274. func TestUpdateDept_L_R16_2_NormalStatusToDisabled_RevokesAllMembers(t *testing.T) {
  275. ctx := ctxhelper.SuperAdminCtx()
  276. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  277. conn := testutil.GetTestSqlConn()
  278. deptId, userId := seedDeptWithUser(t, svcCtx, "l_r16_norm_dis", "/7300/",
  279. consts.DeptTypeNormal, consts.StatusEnabled)
  280. t.Cleanup(func() {
  281. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  282. testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
  283. })
  284. before, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  285. require.NoError(t, err)
  286. prev := before.TokenVersion
  287. require.NoError(t,
  288. NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  289. Id: deptId, Name: "norm_disabled_" + testutil.UniqueId(),
  290. DeptType: consts.DeptTypeNormal, Status: consts.StatusDisabled,
  291. }))
  292. after, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  293. require.NoError(t, err)
  294. assert.Equal(t, prev+1, after.TokenVersion,
  295. "NORMAL 部门 Enabled→Disabled 是冻结动作,部门成员的 tokenVersion 必须 +1")
  296. }
  297. // TC-1177: NORMAL→DEV 是放宽权限的升级方向,tokenVersion 保持不变;把合法会话无故下线
  298. // 不仅没收益,还会让产品侧管理员需要重新登录 + 重新建会话,损害可用性。
  299. func TestUpdateDept_L_R16_2_NormalToDev_NoRevoke(t *testing.T) {
  300. ctx := ctxhelper.SuperAdminCtx()
  301. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  302. conn := testutil.GetTestSqlConn()
  303. deptId, userId := seedDeptWithUser(t, svcCtx, "l_r16_norm2dev", "/7400/",
  304. consts.DeptTypeNormal, consts.StatusEnabled)
  305. t.Cleanup(func() {
  306. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  307. testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
  308. })
  309. before, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  310. require.NoError(t, err)
  311. prev := before.TokenVersion
  312. require.NoError(t,
  313. NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  314. Id: deptId, Name: "norm2dev_" + testutil.UniqueId(), DeptType: consts.DeptTypeDev,
  315. }))
  316. after, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  317. require.NoError(t, err)
  318. assert.Equal(t, prev, after.TokenVersion,
  319. "NORMAL→DEV 是升权方向,不得把现存会话吊销;否则每次类型调整都会触发一次大规模强制下线")
  320. }
  321. // TC-1178: 部门 Status Disabled→Enabled(恢复启用)同样是升权方向,tokenVersion 必须保持不变。
  322. func TestUpdateDept_L_R16_2_StatusReEnable_NoRevoke(t *testing.T) {
  323. ctx := ctxhelper.SuperAdminCtx()
  324. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  325. conn := testutil.GetTestSqlConn()
  326. deptId, userId := seedDeptWithUser(t, svcCtx, "l_r16_reenable", "/7500/",
  327. consts.DeptTypeNormal, consts.StatusDisabled)
  328. t.Cleanup(func() {
  329. testutil.CleanTable(ctx, conn, "`sys_user`", userId)
  330. testutil.CleanTable(ctx, conn, "`sys_dept`", deptId)
  331. })
  332. before, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  333. require.NoError(t, err)
  334. prev := before.TokenVersion
  335. require.NoError(t,
  336. NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  337. Id: deptId, Name: "reenable_" + testutil.UniqueId(),
  338. DeptType: consts.DeptTypeNormal, Status: consts.StatusEnabled,
  339. }))
  340. after, err := svcCtx.SysUserModel.FindOne(ctx, userId)
  341. require.NoError(t, err)
  342. assert.Equal(t, prev, after.TokenVersion,
  343. "Disabled→Enabled 是恢复活动,不是收窄方向;tokenVersion 必须保持不变")
  344. }
  345. // deptType / status 都没变时,不应调 FindIdsByDeptId(避免无效缓存失效风暴);dept 自身行
  346. // 的 InvalidateDeptCache 仍需调用,因为 sys_dept 的 updateTime/name/remark/sort 已经被 UPDATE。
  347. func TestUpdateDept_NoEffectiveChange_SkipsFindIds(t *testing.T) {
  348. ctrl := gomock.NewController(t)
  349. t.Cleanup(ctrl.Finish)
  350. deptMock := mocks.NewMockSysDeptModel(ctrl)
  351. userMock := mocks.NewMockSysUserModel(ctrl)
  352. // 老部门 DEV,请求也是 DEV;status 未传 → 没有变更。
  353. deptMock.EXPECT().FindOne(gomock.Any(), int64(99)).
  354. Return(&deptModel.SysDept{
  355. Id: 99, Name: "x", DeptType: consts.DeptTypeDev,
  356. Status: consts.StatusEnabled, UpdateTime: 200,
  357. }, nil)
  358. runDeptTxInline(deptMock)
  359. deptMock.EXPECT().UpdateWithOptLockTx(gomock.Any(), gomock.Any(), gomock.Any(), int64(200)).Return(nil)
  360. deptMock.EXPECT().InvalidateDeptCache(gomock.Any(), int64(99))
  361. // 关键:没有任何 FindIdsByDeptId / FindIdsByDeptIdForShareTx EXPECT 即等价 Times(0)。
  362. svcCtx := mocks.NewMockServiceContext(mocks.MockModels{
  363. Dept: deptMock, User: userMock,
  364. })
  365. err := NewUpdateDeptLogic(superAdminCtx(), svcCtx).UpdateDept(&types.UpdateDeptReq{
  366. Id: 99, Name: "x", DeptType: consts.DeptTypeDev, Sort: 1,
  367. })
  368. require.NoError(t, err, "无变更时 UpdateDept 只做元字段更新,不得触发缓存清理风暴")
  369. }
  370. // TC-1203: UpdateDept Sort 超出范围 [-100000, 100000] 被拒绝。
  371. // 与 CreateDept 同口径校验,防极端 Sort 值破坏部门树排序稳定性。
  372. func TestUpdateDept_SortOutOfRange_Rejected(t *testing.T) {
  373. ctx := ctxhelper.SuperAdminCtx()
  374. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  375. conn := testutil.GetTestSqlConn()
  376. deptId, err := insertDeptRaw(ctx, svcCtx, 0, "sort_upd_"+testutil.UniqueId(), "/")
  377. require.NoError(t, err)
  378. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", deptId) })
  379. before, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  380. require.NoError(t, err)
  381. origSort := before.Sort
  382. for _, s := range []int64{100001, -100001, math.MaxInt64, math.MinInt64} {
  383. err := NewUpdateDeptLogic(ctx, svcCtx).UpdateDept(&types.UpdateDeptReq{
  384. Id: deptId, Name: "sort_test_" + testutil.UniqueId(), Sort: s,
  385. })
  386. require.Error(t, err, "Sort=%d 应被拒绝", s)
  387. var ce *response.CodeError
  388. require.True(t, errors.As(err, &ce), "Sort=%d 必须返回 CodeError", s)
  389. assert.Equal(t, 400, ce.Code(), "Sort=%d 应 400 拒绝", s)
  390. assert.Contains(t, ce.Error(), "排序值必须在 -100000 到 100000 之间")
  391. }
  392. // DB 中 Sort 值不得被改变。
  393. after, err := svcCtx.SysDeptModel.FindOne(ctx, deptId)
  394. require.NoError(t, err)
  395. assert.Equal(t, origSort, after.Sort, "Sort 越界被拒绝后 DB 值不得变化")
  396. }