sysDeptModel_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. package dept
  2. import (
  3. "context"
  4. "errors"
  5. "github.com/stretchr/testify/assert"
  6. "github.com/stretchr/testify/require"
  7. "github.com/zeromicro/go-zero/core/stores/sqlx"
  8. "perms-system-server/internal/testutil"
  9. "sync"
  10. "sync/atomic"
  11. "testing"
  12. "time"
  13. )
  14. func TestSysDeptModel_CRUD(t *testing.T) {
  15. ctx := context.Background()
  16. conn := testutil.GetTestSqlConn()
  17. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  18. now := time.Now().Unix()
  19. data := &SysDept{
  20. ParentId: 0,
  21. Name: "dept_crud_" + testutil.UniqueId(),
  22. Path: "/crud/" + testutil.UniqueId() + "/",
  23. Sort: 10,
  24. Remark: "r",
  25. Status: 1,
  26. CreateTime: now,
  27. UpdateTime: now,
  28. }
  29. res, err := m.Insert(ctx, data)
  30. require.NoError(t, err)
  31. id, err := res.LastInsertId()
  32. require.NoError(t, err)
  33. tbl := m.TableName()
  34. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  35. found, err := m.FindOne(ctx, id)
  36. require.NoError(t, err)
  37. require.NotNil(t, found)
  38. assert.Equal(t, id, found.Id)
  39. assert.Equal(t, data.Name, found.Name)
  40. found.Remark = "updated"
  41. found.UpdateTime = now + 1
  42. require.NoError(t, m.Update(ctx, found))
  43. after, err := m.FindOne(ctx, id)
  44. require.NoError(t, err)
  45. assert.Equal(t, "updated", after.Remark)
  46. require.NoError(t, m.Delete(ctx, id))
  47. _, err = m.FindOne(ctx, id)
  48. require.Error(t, err)
  49. assert.True(t, errors.Is(err, ErrNotFound))
  50. }
  51. // TC-0442: FindAll 排序 sort asc, id asc
  52. func TestSysDeptModel_FindAll_OrderBySortAscIdAsc(t *testing.T) {
  53. ctx := context.Background()
  54. conn := testutil.GetTestSqlConn()
  55. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  56. base := testutil.UniqueId()
  57. now := time.Now().Unix()
  58. rows := []*SysDept{
  59. {ParentId: 0, Name: "a_" + base, Path: "/fa/" + base + "/a/", Sort: 30, Status: 1, CreateTime: now, UpdateTime: now},
  60. {ParentId: 0, Name: "b_" + base, Path: "/fa/" + base + "/b/", Sort: 10, Status: 1, CreateTime: now, UpdateTime: now},
  61. {ParentId: 0, Name: "c_" + base, Path: "/fa/" + base + "/c/", Sort: 20, Status: 1, CreateTime: now, UpdateTime: now},
  62. }
  63. require.NoError(t, m.BatchInsert(ctx, rows))
  64. all, err := m.FindAll(ctx)
  65. require.NoError(t, err)
  66. nameSet := map[string]struct{}{rows[0].Name: {}, rows[1].Name: {}, rows[2].Name: {}}
  67. var picked []*SysDept
  68. var ids []int64
  69. for i := range all {
  70. if _, ok := nameSet[all[i].Name]; ok {
  71. picked = append(picked, all[i])
  72. ids = append(ids, all[i].Id)
  73. }
  74. }
  75. tbl := m.TableName()
  76. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, ids...) })
  77. require.Len(t, picked, 3)
  78. for i := 1; i < len(picked); i++ {
  79. prev, cur := picked[i-1], picked[i]
  80. if prev.Sort == cur.Sort {
  81. assert.Less(t, prev.Id, cur.Id)
  82. } else {
  83. assert.Less(t, prev.Sort, cur.Sort)
  84. }
  85. }
  86. assert.Equal(t, int64(10), picked[0].Sort)
  87. assert.Equal(t, int64(20), picked[1].Sort)
  88. assert.Equal(t, int64(30), picked[2].Sort)
  89. }
  90. // TC-0336: 批量 Insert/Delete
  91. func TestSysDeptModel_BatchInsert_BatchDelete(t *testing.T) {
  92. ctx := context.Background()
  93. conn := testutil.GetTestSqlConn()
  94. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  95. now := time.Now().Unix()
  96. tag := testutil.UniqueId()
  97. batch := []*SysDept{
  98. {ParentId: 0, Name: "b1_" + tag, Path: "/bi/" + tag + "/1/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now},
  99. {ParentId: 0, Name: "b2_" + tag, Path: "/bi/" + tag + "/2/", Sort: 2, Status: 1, CreateTime: now, UpdateTime: now},
  100. }
  101. require.NoError(t, m.BatchInsert(ctx, batch))
  102. all, err := m.FindAll(ctx)
  103. require.NoError(t, err)
  104. wanted := map[string]struct{}{batch[0].Name: {}, batch[1].Name: {}}
  105. var ids []int64
  106. for _, row := range all {
  107. if _, ok := wanted[row.Name]; ok {
  108. ids = append(ids, row.Id)
  109. }
  110. }
  111. require.Len(t, ids, 2)
  112. tbl := m.TableName()
  113. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, ids...) })
  114. require.NoError(t, m.BatchDelete(ctx, ids))
  115. for _, id := range ids {
  116. _, err := m.FindOne(ctx, id)
  117. require.Error(t, err)
  118. assert.True(t, errors.Is(err, ErrNotFound))
  119. }
  120. }
  121. // TC-0327: 事务内 Insert/Update
  122. func TestSysDeptModel_TransactCtx_InsertWithTx_UpdateWithTx(t *testing.T) {
  123. ctx := context.Background()
  124. conn := testutil.GetTestSqlConn()
  125. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  126. now := time.Now().Unix()
  127. d := &SysDept{
  128. ParentId: 0, Name: "tx_" + testutil.UniqueId(),
  129. Path: "/tx/" + testutil.UniqueId() + "/",
  130. Sort: 1, Remark: "before", Status: 1,
  131. CreateTime: now, UpdateTime: now,
  132. }
  133. var finalId int64
  134. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  135. res, err := m.InsertWithTx(c, s, d)
  136. if err != nil {
  137. return err
  138. }
  139. lid, err := res.LastInsertId()
  140. if err != nil {
  141. return err
  142. }
  143. finalId = lid
  144. d.Id = finalId
  145. d.Remark = "after_tx"
  146. d.UpdateTime = now + 2
  147. return m.UpdateWithTx(c, s, d)
  148. })
  149. require.NoError(t, err)
  150. tbl := m.TableName()
  151. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, finalId) })
  152. out, err := m.FindOne(ctx, finalId)
  153. require.NoError(t, err)
  154. assert.Equal(t, "after_tx", out.Remark)
  155. }
  156. // TC-0333: 表名
  157. func TestSysDeptModel_TableName(t *testing.T) {
  158. conn := testutil.GetTestSqlConn()
  159. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  160. assert.Equal(t, "`sys_dept`", m.TableName())
  161. }
  162. // TC-0319: FindOne 不存在
  163. func TestSysDeptModel_FindOne_NotFound(t *testing.T) {
  164. conn := testutil.GetTestSqlConn()
  165. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  166. _, err := m.FindOne(context.Background(), 999999999999)
  167. require.ErrorIs(t, err, ErrNotFound)
  168. }
  169. // TC-0326: Update 不存在行不报错
  170. func TestSysDeptModel_Update_NonExistentRow_NoError(t *testing.T) {
  171. conn := testutil.GetTestSqlConn()
  172. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  173. err := m.Update(context.Background(), &SysDept{
  174. Id: 999999999999, Name: "ghost", Path: "/x/", Status: 1,
  175. CreateTime: time.Now().Unix(), UpdateTime: time.Now().Unix(),
  176. })
  177. require.NoError(t, err)
  178. }
  179. // TC-0329: Delete 不存在行不报错
  180. func TestSysDeptModel_Delete_NonExistentRow_NoError(t *testing.T) {
  181. conn := testutil.GetTestSqlConn()
  182. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  183. err := m.Delete(context.Background(), 999999999999)
  184. require.NoError(t, err)
  185. }
  186. // TC-0334: BatchInsert 空
  187. func TestSysDeptModel_BatchInsert_Empty(t *testing.T) {
  188. conn := testutil.GetTestSqlConn()
  189. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  190. require.NoError(t, m.BatchInsert(context.Background(), nil))
  191. require.NoError(t, m.BatchInsert(context.Background(), []*SysDept{}))
  192. }
  193. // TC-0353: BatchDelete 空
  194. func TestSysDeptModel_BatchDelete_Empty(t *testing.T) {
  195. conn := testutil.GetTestSqlConn()
  196. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  197. require.NoError(t, m.BatchDelete(context.Background(), nil))
  198. require.NoError(t, m.BatchDelete(context.Background(), []int64{}))
  199. }
  200. // TC-0316: 事务回滚无数据
  201. func TestSysDeptModel_InsertWithTx_Rollback(t *testing.T) {
  202. ctx := context.Background()
  203. conn := testutil.GetTestSqlConn()
  204. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  205. now := time.Now().Unix()
  206. uniq := "txrb_" + testutil.UniqueId()
  207. d := &SysDept{
  208. ParentId: 0, Name: uniq, Path: "/" + uniq + "/",
  209. Sort: 1, Status: 1, CreateTime: now, UpdateTime: now,
  210. }
  211. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  212. if _, e := m.InsertWithTx(c, s, d); e != nil {
  213. return e
  214. }
  215. return errors.New("force rollback")
  216. })
  217. require.Error(t, err)
  218. all, err := m.FindAll(ctx)
  219. require.NoError(t, err)
  220. for _, row := range all {
  221. assert.NotEqual(t, uniq, row.Name)
  222. }
  223. }
  224. // TC-0330: 事务内删除
  225. func TestSysDeptModel_DeleteWithTx(t *testing.T) {
  226. ctx := context.Background()
  227. conn := testutil.GetTestSqlConn()
  228. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  229. now := time.Now().Unix()
  230. d := &SysDept{
  231. ParentId: 0, Name: "deltx_" + testutil.UniqueId(),
  232. Path: "/deltx/" + testutil.UniqueId() + "/",
  233. Sort: 1, Status: 1, CreateTime: now, UpdateTime: now,
  234. }
  235. res, err := m.Insert(ctx, d)
  236. require.NoError(t, err)
  237. id, _ := res.LastInsertId()
  238. tbl := m.TableName()
  239. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  240. err = m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  241. return m.DeleteWithTx(c, s, id)
  242. })
  243. require.NoError(t, err)
  244. _, err = m.FindOne(ctx, id)
  245. require.ErrorIs(t, err, ErrNotFound)
  246. }
  247. // TC-0332: TransactCtx fn 返回错误时回滚
  248. func TestSysDeptModel_TransactCtx_Rollback(t *testing.T) {
  249. ctx := context.Background()
  250. conn := testutil.GetTestSqlConn()
  251. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  252. now := time.Now().Unix()
  253. uniq := "txrb2_" + testutil.UniqueId()
  254. d := &SysDept{
  255. ParentId: 0, Name: uniq, Path: "/" + uniq + "/",
  256. Sort: 1, Status: 1, CreateTime: now, UpdateTime: now,
  257. }
  258. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  259. if _, e := m.InsertWithTx(c, s, d); e != nil {
  260. return e
  261. }
  262. return errors.New("force rollback")
  263. })
  264. require.Error(t, err)
  265. require.Contains(t, err.Error(), "force rollback")
  266. all, err := m.FindAll(ctx)
  267. require.NoError(t, err)
  268. for _, row := range all {
  269. assert.NotEqual(t, uniq, row.Name)
  270. }
  271. }
  272. // TC-0343: BatchUpdate 空
  273. func TestSysDeptModel_BatchUpdate_Empty(t *testing.T) {
  274. conn := testutil.GetTestSqlConn()
  275. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  276. require.NoError(t, m.BatchUpdate(context.Background(), nil))
  277. require.NoError(t, m.BatchUpdate(context.Background(), []*SysDept{}))
  278. }
  279. // TC-0345: BatchUpdate 多条
  280. func TestSysDeptModel_BatchUpdate_Multi(t *testing.T) {
  281. ctx := context.Background()
  282. conn := testutil.GetTestSqlConn()
  283. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  284. now := time.Now().Unix()
  285. tag := testutil.UniqueId()
  286. d1 := &SysDept{ParentId: 0, Name: "bu1_" + tag, Path: "/bu/" + tag + "/1/", Sort: 1, Remark: "r1", Status: 1, CreateTime: now, UpdateTime: now}
  287. d2 := &SysDept{ParentId: 0, Name: "bu2_" + tag, Path: "/bu/" + tag + "/2/", Sort: 2, Remark: "r2", Status: 1, CreateTime: now, UpdateTime: now}
  288. r1, err := m.Insert(ctx, d1)
  289. require.NoError(t, err)
  290. id1, _ := r1.LastInsertId()
  291. r2, err := m.Insert(ctx, d2)
  292. require.NoError(t, err)
  293. id2, _ := r2.LastInsertId()
  294. tbl := m.TableName()
  295. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) })
  296. now2 := time.Now().Unix()
  297. upd := []*SysDept{
  298. {Id: id1, ParentId: 0, Name: "bu1_upd", Path: d1.Path, Sort: 10, Remark: "updated", Status: 1, CreateTime: now, UpdateTime: now2},
  299. {Id: id2, ParentId: 0, Name: "bu2_upd", Path: d2.Path, Sort: 20, Remark: "updated", Status: 2, CreateTime: now, UpdateTime: now2},
  300. }
  301. require.NoError(t, m.BatchUpdate(ctx, upd))
  302. g1, err := m.FindOne(ctx, id1)
  303. require.NoError(t, err)
  304. assert.Equal(t, "bu1_upd", g1.Name)
  305. assert.Equal(t, int64(10), g1.Sort)
  306. g2, err := m.FindOne(ctx, id2)
  307. require.NoError(t, err)
  308. assert.Equal(t, "bu2_upd", g2.Name)
  309. assert.Equal(t, int64(2), g2.Status)
  310. }
  311. // TC-0335: BatchInsert 单条
  312. func TestSysDeptModel_BatchInsert_Single(t *testing.T) {
  313. ctx := context.Background()
  314. conn := testutil.GetTestSqlConn()
  315. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  316. now := time.Now().Unix()
  317. uniq := "bis_" + testutil.UniqueId()
  318. d := &SysDept{ParentId: 0, Name: uniq, Path: "/" + uniq + "/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now}
  319. require.NoError(t, m.BatchInsert(ctx, []*SysDept{d}))
  320. all, err := m.FindAll(ctx)
  321. require.NoError(t, err)
  322. var id int64
  323. for _, row := range all {
  324. if row.Name == uniq {
  325. id = row.Id
  326. break
  327. }
  328. }
  329. require.NotZero(t, id)
  330. tbl := m.TableName()
  331. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  332. }
  333. // TC-0341: BatchInsertWithTx 正常
  334. func TestSysDeptModel_BatchInsertWithTx_Normal(t *testing.T) {
  335. ctx := context.Background()
  336. conn := testutil.GetTestSqlConn()
  337. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  338. now := time.Now().Unix()
  339. tag := testutil.UniqueId()
  340. batch := []*SysDept{
  341. {ParentId: 0, Name: "bitx1_" + tag, Path: "/bitx/" + tag + "/1/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now},
  342. {ParentId: 0, Name: "bitx2_" + tag, Path: "/bitx/" + tag + "/2/", Sort: 2, Status: 1, CreateTime: now, UpdateTime: now},
  343. }
  344. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  345. return m.BatchInsertWithTx(c, s, batch)
  346. })
  347. require.NoError(t, err)
  348. all, err := m.FindAll(ctx)
  349. require.NoError(t, err)
  350. wanted := map[string]struct{}{batch[0].Name: {}, batch[1].Name: {}}
  351. var ids []int64
  352. for _, row := range all {
  353. if _, ok := wanted[row.Name]; ok {
  354. ids = append(ids, row.Id)
  355. }
  356. }
  357. require.Len(t, ids, 2)
  358. tbl := m.TableName()
  359. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, ids...) })
  360. }
  361. // TC-0340: BatchInsertWithTx 空
  362. func TestSysDeptModel_BatchInsertWithTx_Empty(t *testing.T) {
  363. conn := testutil.GetTestSqlConn()
  364. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  365. err := m.TransactCtx(context.Background(), func(c context.Context, s sqlx.Session) error {
  366. return m.BatchInsertWithTx(c, s, nil)
  367. })
  368. require.NoError(t, err)
  369. }
  370. // TC-0342: BatchInsertWithTx 回滚
  371. func TestSysDeptModel_BatchInsertWithTx_Rollback(t *testing.T) {
  372. ctx := context.Background()
  373. conn := testutil.GetTestSqlConn()
  374. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  375. now := time.Now().Unix()
  376. uniq := "rbn_" + testutil.UniqueId()
  377. batch := []*SysDept{
  378. {ParentId: 0, Name: uniq, Path: "/rbn/" + uniq + "/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now},
  379. }
  380. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  381. if e := m.BatchInsertWithTx(c, s, batch); e != nil {
  382. return e
  383. }
  384. return errors.New("force rollback")
  385. })
  386. require.Error(t, err)
  387. all, err := m.FindAll(ctx)
  388. require.NoError(t, err)
  389. for _, row := range all {
  390. assert.NotEqual(t, uniq, row.Name)
  391. }
  392. }
  393. // TC-0349: BatchUpdateWithTx 正常
  394. func TestSysDeptModel_BatchUpdateWithTx_Normal(t *testing.T) {
  395. ctx := context.Background()
  396. conn := testutil.GetTestSqlConn()
  397. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  398. now := time.Now().Unix()
  399. tag := testutil.UniqueId()
  400. d1 := &SysDept{ParentId: 0, Name: "butx1_" + tag, Path: "/butx/" + tag + "/1/", Sort: 1, Remark: "old", Status: 1, CreateTime: now, UpdateTime: now}
  401. d2 := &SysDept{ParentId: 0, Name: "butx2_" + tag, Path: "/butx/" + tag + "/2/", Sort: 2, Remark: "old", Status: 1, CreateTime: now, UpdateTime: now}
  402. r1, err := m.Insert(ctx, d1)
  403. require.NoError(t, err)
  404. id1, _ := r1.LastInsertId()
  405. r2, err := m.Insert(ctx, d2)
  406. require.NoError(t, err)
  407. id2, _ := r2.LastInsertId()
  408. tbl := m.TableName()
  409. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) })
  410. now2 := time.Now().Unix()
  411. err = m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  412. return m.BatchUpdateWithTx(c, s, []*SysDept{
  413. {Id: id1, ParentId: 0, Name: "butx1_new", Path: d1.Path, Sort: 10, Remark: "new", Status: 1, CreateTime: now, UpdateTime: now2},
  414. {Id: id2, ParentId: 0, Name: "butx2_new", Path: d2.Path, Sort: 20, Remark: "new", Status: 1, CreateTime: now, UpdateTime: now2},
  415. })
  416. })
  417. require.NoError(t, err)
  418. g1, err := m.FindOne(ctx, id1)
  419. require.NoError(t, err)
  420. assert.Equal(t, "butx1_new", g1.Name)
  421. assert.Equal(t, int64(10), g1.Sort)
  422. g2, err := m.FindOne(ctx, id2)
  423. require.NoError(t, err)
  424. assert.Equal(t, "butx2_new", g2.Name)
  425. }
  426. // TC-0348: BatchUpdateWithTx 空
  427. func TestSysDeptModel_BatchUpdateWithTx_Empty(t *testing.T) {
  428. conn := testutil.GetTestSqlConn()
  429. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  430. err := m.TransactCtx(context.Background(), func(c context.Context, s sqlx.Session) error {
  431. return m.BatchUpdateWithTx(c, s, nil)
  432. })
  433. require.NoError(t, err)
  434. }
  435. // TC-0354: BatchDelete 单条
  436. func TestSysDeptModel_BatchDelete_Single(t *testing.T) {
  437. ctx := context.Background()
  438. conn := testutil.GetTestSqlConn()
  439. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  440. now := time.Now().Unix()
  441. d := &SysDept{ParentId: 0, Name: "bds_" + testutil.UniqueId(), Path: "/bds/" + testutil.UniqueId() + "/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now}
  442. res, err := m.Insert(ctx, d)
  443. require.NoError(t, err)
  444. id, _ := res.LastInsertId()
  445. tbl := m.TableName()
  446. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  447. require.NoError(t, m.BatchDelete(ctx, []int64{id}))
  448. _, err = m.FindOne(ctx, id)
  449. require.ErrorIs(t, err, ErrNotFound)
  450. }
  451. // TC-0356: BatchDelete 包含不存在 id
  452. func TestSysDeptModel_BatchDelete_ContainsNonExist(t *testing.T) {
  453. ctx := context.Background()
  454. conn := testutil.GetTestSqlConn()
  455. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  456. now := time.Now().Unix()
  457. d := &SysDept{ParentId: 0, Name: "bdne_" + testutil.UniqueId(), Path: "/bdne/" + testutil.UniqueId() + "/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now}
  458. res, err := m.Insert(ctx, d)
  459. require.NoError(t, err)
  460. id, _ := res.LastInsertId()
  461. tbl := m.TableName()
  462. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  463. require.NoError(t, m.BatchDelete(ctx, []int64{id, 999999999}))
  464. _, err = m.FindOne(ctx, id)
  465. require.ErrorIs(t, err, ErrNotFound)
  466. }
  467. // TC-0358: BatchDeleteWithTx 正常
  468. func TestSysDeptModel_BatchDeleteWithTx_Normal(t *testing.T) {
  469. ctx := context.Background()
  470. conn := testutil.GetTestSqlConn()
  471. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  472. now := time.Now().Unix()
  473. tag := testutil.UniqueId()
  474. d1 := &SysDept{ParentId: 0, Name: "bdtx1_" + tag, Path: "/bdtx/" + tag + "/1/", Sort: 1, Status: 1, CreateTime: now, UpdateTime: now}
  475. d2 := &SysDept{ParentId: 0, Name: "bdtx2_" + tag, Path: "/bdtx/" + tag + "/2/", Sort: 2, Status: 1, CreateTime: now, UpdateTime: now}
  476. r1, err := m.Insert(ctx, d1)
  477. require.NoError(t, err)
  478. id1, _ := r1.LastInsertId()
  479. r2, err := m.Insert(ctx, d2)
  480. require.NoError(t, err)
  481. id2, _ := r2.LastInsertId()
  482. tbl := m.TableName()
  483. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) })
  484. err = m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  485. return m.BatchDeleteWithTx(c, s, []int64{id1, id2})
  486. })
  487. require.NoError(t, err)
  488. _, err = m.FindOne(ctx, id1)
  489. require.ErrorIs(t, err, ErrNotFound)
  490. _, err = m.FindOne(ctx, id2)
  491. require.ErrorIs(t, err, ErrNotFound)
  492. }
  493. // TC-0357: BatchDeleteWithTx 空
  494. func TestSysDeptModel_BatchDeleteWithTx_Empty(t *testing.T) {
  495. conn := testutil.GetTestSqlConn()
  496. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  497. err := m.TransactCtx(context.Background(), func(c context.Context, s sqlx.Session) error {
  498. return m.BatchDeleteWithTx(c, s, nil)
  499. })
  500. require.NoError(t, err)
  501. }
  502. // TC-0323: 事务内 FindOne
  503. func TestSysDeptModel_FindOneWithTx_InsertThenFind(t *testing.T) {
  504. ctx := context.Background()
  505. conn := testutil.GetTestSqlConn()
  506. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  507. now := time.Now().Unix()
  508. var foundInTx *SysDept
  509. var insertedId int64
  510. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  511. res, err := m.InsertWithTx(c, s, &SysDept{
  512. ParentId: 0, Name: "ftx_" + testutil.UniqueId(), Path: "/ftx/" + testutil.UniqueId() + "/",
  513. Sort: 1, DeptType: "NORMAL", Status: 1, CreateTime: now, UpdateTime: now,
  514. })
  515. if err != nil {
  516. return err
  517. }
  518. insertedId, _ = res.LastInsertId()
  519. foundInTx, err = m.FindOneWithTx(c, s, insertedId)
  520. return err
  521. })
  522. require.NoError(t, err)
  523. tbl := m.TableName()
  524. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, insertedId) })
  525. require.NotNil(t, foundInTx)
  526. assert.Equal(t, insertedId, foundInTx.Id)
  527. }
  528. // TC-0322: FindOneWithTx 不存在
  529. func TestSysDeptModel_FindOneWithTx_NotFound(t *testing.T) {
  530. ctx := context.Background()
  531. conn := testutil.GetTestSqlConn()
  532. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  533. err := m.TransactCtx(ctx, func(c context.Context, s sqlx.Session) error {
  534. _, err := m.FindOneWithTx(c, s, 999999999999)
  535. return err
  536. })
  537. require.ErrorIs(t, err, ErrNotFound)
  538. }
  539. func TestSysDeptModel_UpdateWithOptLock_ConcurrentSingleWinner(t *testing.T) {
  540. ctx := context.Background()
  541. conn := testutil.GetTestSqlConn()
  542. m := NewSysDeptModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  543. base := time.Now().Unix()
  544. row := &SysDept{
  545. ParentId: 0,
  546. Name: "dept_optlock_" + testutil.UniqueId(),
  547. Path: "/optlock/" + testutil.UniqueId() + "/",
  548. Sort: 10,
  549. Remark: "orig",
  550. Status: 1,
  551. CreateTime: base,
  552. UpdateTime: base,
  553. }
  554. res, err := m.Insert(ctx, row)
  555. require.NoError(t, err)
  556. id, err := res.LastInsertId()
  557. require.NoError(t, err)
  558. tbl := m.TableName()
  559. t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) })
  560. const workers = 10
  561. var (
  562. wg sync.WaitGroup
  563. success int32
  564. conflicts int32
  565. other int32
  566. start = make(chan struct{})
  567. )
  568. for i := 0; i < workers; i++ {
  569. wg.Add(1)
  570. go func(idx int) {
  571. defer wg.Done()
  572. <-start
  573. err := m.UpdateWithOptLock(ctx, &SysDept{
  574. Id: id,
  575. ParentId: 0,
  576. Name: row.Name,
  577. Path: row.Path,
  578. Sort: int64(idx),
  579. Remark: "w" + testutil.UniqueId(),
  580. DeptType: "NORMAL",
  581. Status: 1,
  582. CreateTime: base,
  583. UpdateTime: base + int64(idx+1),
  584. }, base)
  585. switch {
  586. case err == nil:
  587. atomic.AddInt32(&success, 1)
  588. case errors.Is(err, ErrUpdateConflict):
  589. atomic.AddInt32(&conflicts, 1)
  590. default:
  591. atomic.AddInt32(&other, 1)
  592. t.Errorf("unexpected error: %v", err)
  593. }
  594. }(i)
  595. }
  596. close(start)
  597. wg.Wait()
  598. assert.Equal(t, int32(1), atomic.LoadInt32(&success),
  599. "10 个并发写必须且仅有 1 个成功, 实际 %d", success)
  600. assert.Equal(t, int32(workers-1), atomic.LoadInt32(&conflicts),
  601. "其余 goroutine 必须全部得到 ErrUpdateConflict (无声覆盖即 BUG)")
  602. assert.Equal(t, int32(0), atomic.LoadInt32(&other),
  603. "不应出现除成功/冲突外的其他错误")
  604. after, err := m.FindOne(ctx, id)
  605. require.NoError(t, err)
  606. assert.NotEqual(t, base, after.UpdateTime,
  607. "成功的那一个必须把 UpdateTime 推进, DB 里不允许停留在初值")
  608. assert.Equal(t, "orig", row.Remark)
  609. assert.NotEqual(t, "orig", after.Remark, "胜出者必须把 Remark 更新为 w*")
  610. }