createDeptLogic_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. package dept
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. "perms-system-server/internal/consts"
  10. deptModel "perms-system-server/internal/model/dept"
  11. "perms-system-server/internal/response"
  12. "perms-system-server/internal/svc"
  13. "perms-system-server/internal/testutil"
  14. "perms-system-server/internal/testutil/ctxhelper"
  15. "perms-system-server/internal/types"
  16. "sync"
  17. "sync/atomic"
  18. "testing"
  19. "time"
  20. )
  21. func insertDeptRaw(ctx context.Context, svcCtx *svc.ServiceContext, parentId int64, name, path string) (int64, error) {
  22. now := time.Now().Unix()
  23. result, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
  24. ParentId: parentId,
  25. Name: name,
  26. Path: path,
  27. Sort: 0,
  28. DeptType: "NORMAL",
  29. Status: 1,
  30. CreateTime: now,
  31. UpdateTime: now,
  32. })
  33. if err != nil {
  34. return 0, err
  35. }
  36. id, _ := result.LastInsertId()
  37. d, err := svcCtx.SysDeptModel.FindOne(ctx, id)
  38. if err != nil {
  39. return id, err
  40. }
  41. d.Path = fmt.Sprintf("%s%d/", path, id)
  42. d.UpdateTime = now
  43. return id, svcCtx.SysDeptModel.Update(ctx, d)
  44. }
  45. // TC-0093: 父部门不存在
  46. func TestCreateDept_ParentNotFound(t *testing.T) {
  47. ctx := ctxhelper.SuperAdminCtx()
  48. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  49. l := NewCreateDeptLogic(ctx, svcCtx)
  50. resp, err := l.CreateDept(&types.CreateDeptReq{
  51. ParentId: 999999999,
  52. Name: "orphan_" + testutil.UniqueId(),
  53. })
  54. assert.Nil(t, resp)
  55. require.Error(t, err)
  56. var ce *response.CodeError
  57. require.True(t, errors.As(err, &ce))
  58. assert.Equal(t, 404, ce.Code())
  59. assert.Contains(t, ce.Error(), "父部门不存在")
  60. }
  61. // TC-0091: 创建顶级部门
  62. func TestCreateDept_TopLevel_ViaRawInsert(t *testing.T) {
  63. ctx := context.Background()
  64. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  65. conn := testutil.GetTestSqlConn()
  66. name := "dept_top_" + testutil.UniqueId()
  67. id, err := insertDeptRaw(ctx, svcCtx, 0, name, "/")
  68. require.NoError(t, err)
  69. require.Greater(t, id, int64(0))
  70. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", id) })
  71. dept, err := svcCtx.SysDeptModel.FindOne(ctx, id)
  72. require.NoError(t, err)
  73. assert.Equal(t, name, dept.Name)
  74. assert.Equal(t, int64(0), dept.ParentId)
  75. assert.Equal(t, fmt.Sprintf("/%d/", id), dept.Path)
  76. assert.Equal(t, int64(1), dept.Status)
  77. }
  78. // TC-0092: 创建子部门
  79. func TestCreateDept_Child_ViaRawInsert(t *testing.T) {
  80. ctx := context.Background()
  81. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  82. conn := testutil.GetTestSqlConn()
  83. parentId, err := insertDeptRaw(ctx, svcCtx, 0, "par_"+testutil.UniqueId(), "/")
  84. require.NoError(t, err)
  85. parentDept, _ := svcCtx.SysDeptModel.FindOne(ctx, parentId)
  86. childId, err := insertDeptRaw(ctx, svcCtx, parentId, "child_"+testutil.UniqueId(), parentDept.Path)
  87. require.NoError(t, err)
  88. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", childId, parentId) })
  89. child, err := svcCtx.SysDeptModel.FindOne(ctx, childId)
  90. require.NoError(t, err)
  91. assert.Equal(t, parentId, child.ParentId)
  92. assert.Equal(t, fmt.Sprintf("/%d/%d/", parentId, childId), child.Path)
  93. }
  94. // TC-0099: 多层嵌套(5层)
  95. func TestCreateDept_MultiLevel(t *testing.T) {
  96. ctx := context.Background()
  97. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  98. conn := testutil.GetTestSqlConn()
  99. l1Id, err := insertDeptRaw(ctx, svcCtx, 0, "L1_"+testutil.UniqueId(), "/")
  100. require.NoError(t, err)
  101. l1, _ := svcCtx.SysDeptModel.FindOne(ctx, l1Id)
  102. l2Id, err := insertDeptRaw(ctx, svcCtx, l1Id, "L2_"+testutil.UniqueId(), l1.Path)
  103. require.NoError(t, err)
  104. l2, _ := svcCtx.SysDeptModel.FindOne(ctx, l2Id)
  105. l3Id, err := insertDeptRaw(ctx, svcCtx, l2Id, "L3_"+testutil.UniqueId(), l2.Path)
  106. require.NoError(t, err)
  107. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", l3Id, l2Id, l1Id) })
  108. d3, err := svcCtx.SysDeptModel.FindOne(ctx, l3Id)
  109. require.NoError(t, err)
  110. assert.Equal(t, fmt.Sprintf("/%d/%d/%d/", l1Id, l2Id, l3Id), d3.Path)
  111. }
  112. // TC-0099: 多层嵌套(5层)
  113. func TestCreateDept_FiveLevelNesting(t *testing.T) {
  114. ctx := context.Background()
  115. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  116. conn := testutil.GetTestSqlConn()
  117. var ids []int64
  118. parentId := int64(0)
  119. parentPath := "/"
  120. for level := 1; level <= 5; level++ {
  121. id, err := insertDeptRaw(ctx, svcCtx, parentId, fmt.Sprintf("L%d_%s", level, testutil.UniqueId()), parentPath)
  122. require.NoError(t, err)
  123. ids = append(ids, id)
  124. dept, err := svcCtx.SysDeptModel.FindOne(ctx, id)
  125. require.NoError(t, err)
  126. parentId = id
  127. parentPath = dept.Path
  128. }
  129. t.Cleanup(func() {
  130. for i := len(ids) - 1; i >= 0; i-- {
  131. testutil.CleanTable(ctx, conn, "`sys_dept`", ids[i])
  132. }
  133. })
  134. deepest, err := svcCtx.SysDeptModel.FindOne(ctx, ids[4])
  135. require.NoError(t, err)
  136. expected := fmt.Sprintf("/%d/%d/%d/%d/%d/", ids[0], ids[1], ids[2], ids[3], ids[4])
  137. assert.Equal(t, expected, deepest.Path)
  138. }
  139. // TC-0094: 不传DeptType默认NORMAL
  140. func TestCreateDept_DefaultDeptType(t *testing.T) {
  141. ctx := ctxhelper.SuperAdminCtx()
  142. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  143. conn := testutil.GetTestSqlConn()
  144. l := NewCreateDeptLogic(ctx, svcCtx)
  145. resp, err := l.CreateDept(&types.CreateDeptReq{
  146. ParentId: 0,
  147. Name: "deftype_" + testutil.UniqueId(),
  148. })
  149. require.NoError(t, err)
  150. require.NotNil(t, resp)
  151. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", resp.Id) })
  152. d, err := svcCtx.SysDeptModel.FindOne(ctx, resp.Id)
  153. require.NoError(t, err)
  154. assert.Equal(t, "NORMAL", d.DeptType)
  155. assert.Contains(t, d.Path, fmt.Sprintf("/%d/", resp.Id))
  156. }
  157. // TC-0095: 传DeptType=DEV
  158. func TestCreateDept_DevDeptType(t *testing.T) {
  159. ctx := ctxhelper.SuperAdminCtx()
  160. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  161. conn := testutil.GetTestSqlConn()
  162. l := NewCreateDeptLogic(ctx, svcCtx)
  163. resp, err := l.CreateDept(&types.CreateDeptReq{
  164. ParentId: 0,
  165. Name: "devtype_" + testutil.UniqueId(),
  166. DeptType: "DEV",
  167. })
  168. require.NoError(t, err)
  169. require.NotNil(t, resp)
  170. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", resp.Id) })
  171. d, err := svcCtx.SysDeptModel.FindOne(ctx, resp.Id)
  172. require.NoError(t, err)
  173. assert.Equal(t, "DEV", d.DeptType)
  174. assert.Contains(t, d.Path, fmt.Sprintf("/%d/", resp.Id))
  175. }
  176. // TC-0100: 通过Logic创建+验证Path
  177. func TestCreateDept_ViaLogic_PathCorrect(t *testing.T) {
  178. ctx := ctxhelper.SuperAdminCtx()
  179. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  180. conn := testutil.GetTestSqlConn()
  181. l := NewCreateDeptLogic(ctx, svcCtx)
  182. parentResp, err := l.CreateDept(&types.CreateDeptReq{
  183. ParentId: 0,
  184. Name: "parent_" + testutil.UniqueId(),
  185. })
  186. require.NoError(t, err)
  187. childResp, err := l.CreateDept(&types.CreateDeptReq{
  188. ParentId: parentResp.Id,
  189. Name: "child_" + testutil.UniqueId(),
  190. })
  191. require.NoError(t, err)
  192. t.Cleanup(func() {
  193. testutil.CleanTable(ctx, conn, "`sys_dept`", childResp.Id, parentResp.Id)
  194. })
  195. parent, err := svcCtx.SysDeptModel.FindOne(ctx, parentResp.Id)
  196. require.NoError(t, err)
  197. assert.Equal(t, fmt.Sprintf("/%d/", parentResp.Id), parent.Path)
  198. child, err := svcCtx.SysDeptModel.FindOne(ctx, childResp.Id)
  199. require.NoError(t, err)
  200. assert.Equal(t, fmt.Sprintf("/%d/%d/", parentResp.Id, childResp.Id), child.Path)
  201. }
  202. // TC-0532: createDept非超管拒绝
  203. func TestCreateDept_NonSuperAdminRejected(t *testing.T) {
  204. ctx := ctxhelper.AdminCtx("test_product")
  205. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  206. l := NewCreateDeptLogic(ctx, svcCtx)
  207. _, err := l.CreateDept(&types.CreateDeptReq{Name: "test", Sort: 1})
  208. require.Error(t, err)
  209. var ce *response.CodeError
  210. require.True(t, errors.As(err, &ce))
  211. assert.Equal(t, 403, ce.Code())
  212. }
  213. var _ = deptModel.ErrNotFound
  214. func insertDeptWithStatus(t *testing.T, ctx context.Context, svcCtx *svc.ServiceContext, name, path string, status int64) int64 {
  215. t.Helper()
  216. now := time.Now().Unix()
  217. res, err := svcCtx.SysDeptModel.Insert(ctx, &deptModel.SysDept{
  218. ParentId: 0, Name: name + "_" + testutil.UniqueId(),
  219. Path: path, Sort: 0, DeptType: "NORMAL", Remark: "",
  220. Status: status, CreateTime: now, UpdateTime: now,
  221. })
  222. require.NoError(t, err)
  223. id, _ := res.LastInsertId()
  224. return id
  225. }
  226. // TC-1084: 父部门已禁用(Status=2)时 CreateDept 必须 400 拒绝
  227. // 修复前:事务内只查 id,Status=2 的父同样放行 → 子部门以 Enabled 状态挂到禁用父上。
  228. func TestCreateDept_ParentDisabled_RejectedAt400(t *testing.T) {
  229. ctx := ctxhelper.SuperAdminCtx()
  230. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  231. conn := testutil.GetTestSqlConn()
  232. // 直接以 Status=Disabled 插入父部门,模拟"父部门先被禁用后 CreateDept 才到"的时序终态
  233. parentId := insertDeptWithStatus(t, ctx, svcCtx, "r12_2_par_disabled", "/", consts.StatusDisabled)
  234. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", parentId) })
  235. resp, err := NewCreateDeptLogic(ctx, svcCtx).CreateDept(&types.CreateDeptReq{
  236. ParentId: parentId,
  237. Name: "child_" + testutil.UniqueId(),
  238. })
  239. assert.Nil(t, resp,
  240. "父部门已禁用时 CreateDept 不得返回子部门 id —— 返回非空即意味着事务已提交,"+
  241. "DB 中出现挂在禁用父下的 Enabled 子部门")
  242. require.Error(t, err)
  243. var ce *response.CodeError
  244. require.True(t, errors.As(err, &ce))
  245. assert.Equal(t, 400, ce.Code(), "禁用父下创建子部门是业务约束冲突而非鉴权/未找到")
  246. assert.Contains(t, ce.Error(), "父部门已被禁用",
  247. "错误消息必须明确指向'父部门已被禁用',方便运营定位;"+
  248. "不允许降级为泛用的'部门不存在'")
  249. // DB 侧兜底断言:子部门绝不应落库
  250. var cnt int64
  251. require.NoError(t, conn.QueryRowCtx(ctx, &cnt,
  252. "SELECT COUNT(*) FROM `sys_dept` WHERE `parentId` = ?", parentId))
  253. assert.Equal(t, int64(0), cnt,
  254. "失败路径必须保证事务整体回滚,DB 中禁用父下不能有任何遗留子行")
  255. }
  256. // TC-1085: 父部门启用时 CreateDept 走锁视图读到 Path 并组装子 Path
  257. // 正向路径:父 Enabled → 子成功创建,且 parentPath 来自事务内 snapshot。
  258. func TestCreateDept_ParentEnabled_UsesTxSnapshotPath(t *testing.T) {
  259. ctx := ctxhelper.SuperAdminCtx()
  260. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  261. conn := testutil.GetTestSqlConn()
  262. parentId, err := insertDeptRaw(context.Background(), svcCtx,
  263. 0, "r12_2_par_ok_"+testutil.UniqueId(), "/")
  264. require.NoError(t, err)
  265. parent, err := svcCtx.SysDeptModel.FindOne(ctx, parentId)
  266. require.NoError(t, err)
  267. resp, err := NewCreateDeptLogic(ctx, svcCtx).CreateDept(&types.CreateDeptReq{
  268. ParentId: parentId,
  269. Name: "r12_2_child_" + testutil.UniqueId(),
  270. })
  271. require.NoError(t, err)
  272. require.NotNil(t, resp)
  273. childId := resp.Id
  274. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", childId, parentId) })
  275. child, err := svcCtx.SysDeptModel.FindOne(ctx, childId)
  276. require.NoError(t, err)
  277. assert.Equal(t, fmt.Sprintf("%s%d/", parent.Path, childId), child.Path,
  278. "子 Path 应当在 parent.Path(事务内 snapshot)基础上拼接自己的 id,"+
  279. "证明 parentPath 走的是修复后事务内的视图而非空字符串")
  280. assert.Equal(t, int64(consts.StatusEnabled), child.Status,
  281. "启用父下的新子部门默认 Enabled")
  282. }
  283. // TC-1086: CreateDept × UpdateDept(Status=Disabled) 并发:无"挂在已禁用父下的 Enabled 子"
  284. // 并发交错:
  285. //
  286. // A) CreateDept 先拿到 sys_dept[parent] 的 S 锁 → UpdateDept 的 X 锁阻塞;
  287. // CreateDept 插入子、提交;此时父仍 Enabled,合法。
  288. // UpdateDept 随后拿到 X 锁 → 将父改 Disabled 提交;此时子已在,但那一瞬子是 Enabled
  289. // (这是 UpdateDept 的契约:仅改父自己,不会级联冻结子树。本 TC 不把这个当 bug,因为
  290. // 这就是修复后认可的语义——"禁用父后由运营决定是否禁用子",而本轮修复要消灭的只是
  291. // "禁用发生在前、子部门 Create 在后仍然挂上"的时序 bug。)
  292. // B) UpdateDept 先提交,父变 Disabled → CreateDept 的 FindOneForShareTx 在 S 锁视图里
  293. // 看到 Status=Disabled → 400,子部门不落库。
  294. //
  295. // 断言:任一轮成功的 CreateDept 必须伴随 "create 时刻父仍 Enabled";一切失败的 CreateDept
  296. // 必须是 400 "父部门已被禁用,无法创建子部门",不得出现 500 /静默吞错 /部分落库。
  297. func TestCreateDept_Vs_DisableParent_NoSilentChildUnderDisabled(t *testing.T) {
  298. ctx := ctxhelper.SuperAdminCtx()
  299. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  300. conn := testutil.GetTestSqlConn()
  301. const rounds = 6
  302. for round := 0; round < rounds; round++ {
  303. parentId, err := insertDeptRaw(context.Background(), svcCtx,
  304. 0, "r12_2_race_par_"+testutil.UniqueId(), "/")
  305. require.NoError(t, err)
  306. var (
  307. wg sync.WaitGroup
  308. childId atomic.Int64
  309. createErr atomic.Value
  310. disableOK atomic.Bool
  311. )
  312. start := make(chan struct{})
  313. wg.Add(2)
  314. go func() {
  315. defer wg.Done()
  316. <-start
  317. resp, err := NewCreateDeptLogic(ctx, svcCtx).CreateDept(&types.CreateDeptReq{
  318. ParentId: parentId,
  319. Name: "r12_2_race_child_" + testutil.UniqueId(),
  320. })
  321. if err != nil {
  322. createErr.Store(err)
  323. return
  324. }
  325. if resp != nil {
  326. childId.Store(resp.Id)
  327. }
  328. }()
  329. go func() {
  330. defer wg.Done()
  331. <-start
  332. // 直接用原生 UPDATE 模拟并发的"禁用父部门"操作,避免引入 UpdateDept Logic 的
  333. // 上游鉴权/PathRewrite 噪声;真实 UpdateDept 禁用路径最终落到 DB 也是同一句 UPDATE。
  334. _, err := conn.ExecCtx(context.Background(),
  335. "UPDATE `sys_dept` SET `status`=?, `updateTime`=? WHERE `id`=?",
  336. consts.StatusDisabled, time.Now().Unix(), parentId)
  337. if err == nil {
  338. disableOK.Store(true)
  339. }
  340. }()
  341. close(start)
  342. wg.Wait()
  343. require.True(t, disableOK.Load(),
  344. "前置:禁用父的裸 UPDATE 必须成功,否则本轮测试不等价于并发语义")
  345. var parentStatus int64
  346. require.NoError(t, conn.QueryRowCtx(context.Background(), &parentStatus,
  347. "SELECT `status` FROM `sys_dept` WHERE `id` = ?", parentId))
  348. require.Equal(t, int64(consts.StatusDisabled), parentStatus,
  349. "前置:本轮终态父必为 Disabled(直读 DB 绕过 CachedConn 可能的过期缓存)")
  350. if cid := childId.Load(); cid > 0 {
  351. // CreateDept 成功 → 说明在 FindOneForShareTx 那一刻,父仍是 Enabled。
  352. // 本 TC 不限制此路径(这是合法的时序:先创建,后禁用),但子部门一旦落库就必须是 Enabled,
  353. // 且 Path 来自事务内 snapshot(写入后才被禁用父"覆盖"是业务意图)。
  354. testutil.CleanTable(ctx, conn, "`sys_dept`", cid)
  355. } else {
  356. // CreateDept 失败路径:必须是 400 "父部门已被禁用"。非此即代表修复没到位,
  357. // 或把 write skew 暴露成了 500。
  358. if raw := createErr.Load(); raw != nil {
  359. var ce *response.CodeError
  360. require.True(t, errors.As(raw.(error), &ce),
  361. "CreateDept 在并发禁用场景下只能抛 response.CodeError,不得是裸 err")
  362. assert.Equal(t, 400, ce.Code(),
  363. "并发禁用父时 CreateDept 必须 400(父已禁用),不得泄漏为 500/404")
  364. assert.Contains(t, ce.Error(), "父部门已被禁用",
  365. "错误消息必须是'父部门已被禁用',便于前端精确提示;"+
  366. "不是'父部门不存在'(DeleteDept 那条路径)")
  367. }
  368. // DB 侧兜底:子部门绝不应落库
  369. var cnt int64
  370. require.NoError(t, conn.QueryRowCtx(context.Background(), &cnt,
  371. "SELECT COUNT(*) FROM `sys_dept` WHERE `parentId` = ?", parentId))
  372. assert.Equal(t, int64(0), cnt,
  373. "失败轮次 DB 不得残留子行;若 > 0 证明事务只做了 parent S 锁校验却 "+
  374. "没把 InsertWithTx 所在事务整体回滚")
  375. }
  376. testutil.CleanTable(ctx, conn, "`sys_dept`", parentId)
  377. }
  378. }
  379. // TC-1202: CreateDept Sort 超出范围 [-100000, 100000] 被拒绝。
  380. // Sort 只在同级部门间相对有效,用不到 int64 的极端值;把合法区间固定为 [-100000, 100000]
  381. // 防止前端偶发把 math.MaxInt64 之类的值透传到 DB 触发"排序溢出"的 edge case。
  382. func TestCreateDept_SortOutOfRange_Rejected(t *testing.T) {
  383. ctx := ctxhelper.SuperAdminCtx()
  384. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  385. outOfRange := []int64{100001, -100001, math.MaxInt64, math.MinInt64}
  386. for _, s := range outOfRange {
  387. resp, err := NewCreateDeptLogic(ctx, svcCtx).CreateDept(&types.CreateDeptReq{
  388. Name: "sort_test_" + testutil.UniqueId(), Sort: s,
  389. })
  390. require.Error(t, err, "Sort=%d 应被拒绝", s)
  391. assert.Nil(t, resp)
  392. var ce *response.CodeError
  393. require.True(t, errors.As(err, &ce), "Sort=%d 必须返回 CodeError", s)
  394. assert.Equal(t, 400, ce.Code(), "Sort=%d 应 400 拒绝", s)
  395. assert.Contains(t, ce.Error(), "排序值必须在 -100000 到 100000 之间",
  396. "Sort=%d 的错误消息与 UpdateDept 校验文案不一致", s)
  397. }
  398. }
  399. // TC-1202 (正向): Sort 在 [-100000, 100000] 边界内应放行。
  400. func TestCreateDept_SortBoundaryValid(t *testing.T) {
  401. ctx := ctxhelper.SuperAdminCtx()
  402. svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
  403. conn := testutil.GetTestSqlConn()
  404. for _, s := range []int64{-100000, 0, 100000} {
  405. resp, err := NewCreateDeptLogic(ctx, svcCtx).CreateDept(&types.CreateDeptReq{
  406. Name: "sort_valid_" + testutil.UniqueId(), Sort: s,
  407. })
  408. require.NoError(t, err, "Sort=%d 应被放行", s)
  409. require.NotNil(t, resp)
  410. require.Greater(t, resp.Id, int64(0))
  411. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_dept`", resp.Id) })
  412. }
  413. }