sysProductMemberModel_test.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131
  1. package productmember
  2. import (
  3. "context"
  4. "errors"
  5. "github.com/go-sql-driver/mysql"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. "github.com/zeromicro/go-zero/core/stores/sqlx"
  9. "math/rand"
  10. "perms-system-server/internal/consts"
  11. "perms-system-server/internal/testutil"
  12. "testing"
  13. "time"
  14. )
  15. func randProductMemberUserId() int64 {
  16. return int64(900000 + rand.Intn(100000))
  17. }
  18. // TC-0310: 正常插入
  19. func TestSysProductMemberModel_CRUD(t *testing.T) {
  20. ctx := context.Background()
  21. conn := testutil.GetTestSqlConn()
  22. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  23. pc := "t_pm_" + testutil.UniqueId()
  24. userId := randProductMemberUserId()
  25. ts := time.Now().Unix()
  26. data := &SysProductMember{
  27. ProductCode: pc,
  28. UserId: userId,
  29. MemberType: "MEMBER",
  30. Status: 1,
  31. CreateTime: ts,
  32. UpdateTime: ts,
  33. }
  34. res, err := m.Insert(ctx, data)
  35. if err != nil {
  36. t.Fatalf("Insert: %v", err)
  37. }
  38. id, err := res.LastInsertId()
  39. if err != nil {
  40. t.Fatalf("LastInsertId: %v", err)
  41. }
  42. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  43. got, err := m.FindOne(ctx, id)
  44. if err != nil {
  45. t.Fatalf("FindOne: %v", err)
  46. }
  47. if got.ProductCode != pc || got.UserId != userId {
  48. t.Fatalf("FindOne mismatch: %+v", got)
  49. }
  50. byPair, err := m.FindOneByProductCodeUserId(ctx, pc, userId)
  51. if err != nil {
  52. t.Fatalf("FindOneByProductCodeUserId: %v", err)
  53. }
  54. if byPair.Id != id {
  55. t.Fatalf("FindOneByProductCodeUserId id want %d got %d", id, byPair.Id)
  56. }
  57. newTs := ts + 1
  58. got.Status = 2
  59. got.UpdateTime = newTs
  60. if err := m.Update(ctx, got); err != nil {
  61. t.Fatalf("Update: %v", err)
  62. }
  63. updated, err := m.FindOne(ctx, id)
  64. if err != nil {
  65. t.Fatalf("FindOne after update: %v", err)
  66. }
  67. if updated.Status != 2 || updated.UpdateTime != newTs {
  68. t.Fatalf("after Update: %+v", updated)
  69. }
  70. if err := m.Delete(ctx, id); err != nil {
  71. t.Fatalf("Delete: %v", err)
  72. }
  73. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  74. t.Fatalf("after Delete want ErrNotFound got %v", err)
  75. }
  76. }
  77. // TC-0475: 正常分页
  78. func TestSysProductMemberModel_FindListByProductCode(t *testing.T) {
  79. ctx := context.Background()
  80. conn := testutil.GetTestSqlConn()
  81. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  82. pc := "t_pm_page_" + testutil.UniqueId()
  83. ts := time.Now().Unix()
  84. var ids []int64
  85. for i := 0; i < 5; i++ {
  86. res, err := m.Insert(ctx, &SysProductMember{
  87. ProductCode: pc,
  88. UserId: randProductMemberUserId(),
  89. MemberType: "MEMBER",
  90. Status: 1,
  91. CreateTime: ts,
  92. UpdateTime: ts,
  93. })
  94. if err != nil {
  95. t.Fatalf("Insert: %v", err)
  96. }
  97. id, _ := res.LastInsertId()
  98. ids = append(ids, id)
  99. }
  100. defer func() {
  101. for _, id := range ids {
  102. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  103. }
  104. }()
  105. list, total, err := m.FindListByProductCode(ctx, pc, 1, 2)
  106. if err != nil {
  107. t.Fatalf("page1: %v", err)
  108. }
  109. if total != 5 || len(list) != 2 {
  110. t.Fatalf("page1 total=%d len=%d", total, len(list))
  111. }
  112. list2, total2, err := m.FindListByProductCode(ctx, pc, 2, 2)
  113. if err != nil {
  114. t.Fatalf("page2: %v", err)
  115. }
  116. if total2 != 5 || len(list2) != 2 {
  117. t.Fatalf("page2 total=%d len=%d", total2, len(list2))
  118. }
  119. list3, total3, err := m.FindListByProductCode(ctx, pc, 3, 2)
  120. if err != nil {
  121. t.Fatalf("page3: %v", err)
  122. }
  123. if total3 != 5 || len(list3) != 1 {
  124. t.Fatalf("page3 total=%d len=%d", total3, len(list3))
  125. }
  126. }
  127. // TC-0477: [REMOVED] FindMapByProductCodeUserIds 作为僵尸接口已在 中被剥离;
  128. // 上层 UserListLogic 改走 FindListByProductMembers 合并查询(见 mock 测试注释)。
  129. // 这里保留 stub 以保持 TC 编号可追溯。
  130. // TC-0336: 多条记录(3条)
  131. func TestSysProductMemberModel_BatchInsert(t *testing.T) {
  132. ctx := context.Background()
  133. conn := testutil.GetTestSqlConn()
  134. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  135. pc := "t_pm_bi_" + testutil.UniqueId()
  136. u1, u2 := randProductMemberUserId(), randProductMemberUserId()
  137. ts := time.Now().Unix()
  138. list := []*SysProductMember{
  139. {Id: 930000001, ProductCode: pc, UserId: u1, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts},
  140. {Id: 930000002, ProductCode: pc, UserId: u2, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts},
  141. }
  142. if err := m.BatchInsert(ctx, list); err != nil {
  143. t.Fatalf("BatchInsert: %v", err)
  144. }
  145. var rows []struct {
  146. Id int64 `db:"id"`
  147. }
  148. q := "SELECT `id` FROM `sys_product_member` WHERE `productCode` = ? ORDER BY `id`"
  149. if err := conn.QueryRowsCtx(ctx, &rows, q, pc); err != nil {
  150. t.Fatalf("query: %v", err)
  151. }
  152. defer func() {
  153. for _, r := range rows {
  154. testutil.CleanTable(ctx, conn, "sys_product_member", r.Id)
  155. }
  156. }()
  157. if len(rows) != 2 {
  158. t.Fatalf("want 2 rows got %d", len(rows))
  159. }
  160. }
  161. // TC-0312: 唯一索引冲突
  162. func TestSysProductMemberModel_DuplicateConstraint(t *testing.T) {
  163. ctx := context.Background()
  164. conn := testutil.GetTestSqlConn()
  165. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  166. pc := "t_pm_dup_" + testutil.UniqueId()
  167. userId := randProductMemberUserId()
  168. ts := time.Now().Unix()
  169. res, err := m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts})
  170. if err != nil {
  171. t.Fatalf("Insert: %v", err)
  172. }
  173. id, _ := res.LastInsertId()
  174. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  175. _, err = m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts})
  176. if err == nil {
  177. t.Fatal("second Insert want error")
  178. }
  179. var me *mysql.MySQLError
  180. if !errors.As(err, &me) || me.Number != 1062 {
  181. t.Fatalf("want duplicate key 1062, got %v", err)
  182. }
  183. }
  184. // TC-0319: 记录不存在
  185. func TestSysProductMemberModel_FindOne_NotFound(t *testing.T) {
  186. conn := testutil.GetTestSqlConn()
  187. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  188. _, err := m.FindOne(context.Background(), 999999999999)
  189. if err != ErrNotFound {
  190. t.Fatalf("want ErrNotFound got %v", err)
  191. }
  192. }
  193. // TC-0392: FindOneByProductCodeUserId
  194. func TestSysProductMemberModel_FindOneByProductCodeUserId_NotFound(t *testing.T) {
  195. conn := testutil.GetTestSqlConn()
  196. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  197. _, err := m.FindOneByProductCodeUserId(context.Background(), "notexist_"+testutil.UniqueId(), 999999999)
  198. if err != ErrNotFound {
  199. t.Fatalf("want ErrNotFound got %v", err)
  200. }
  201. }
  202. // TC-0476: 空结果
  203. func TestSysProductMemberModel_FindListByProductCode_Empty(t *testing.T) {
  204. conn := testutil.GetTestSqlConn()
  205. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  206. list, total, err := m.FindListByProductCode(context.Background(), "empty_"+testutil.UniqueId(), 1, 10)
  207. if err != nil {
  208. t.Fatalf("err: %v", err)
  209. }
  210. if total != 0 || len(list) != 0 {
  211. t.Fatalf("want empty got total=%d len=%d", total, len(list))
  212. }
  213. }
  214. // TC-0334: 空列表
  215. func TestSysProductMemberModel_BatchInsert_Empty(t *testing.T) {
  216. conn := testutil.GetTestSqlConn()
  217. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  218. if err := m.BatchInsert(context.Background(), nil); err != nil {
  219. t.Fatalf("nil: %v", err)
  220. }
  221. if err := m.BatchInsert(context.Background(), []*SysProductMember{}); err != nil {
  222. t.Fatalf("empty: %v", err)
  223. }
  224. }
  225. // TC-0353: 空ids
  226. func TestSysProductMemberModel_BatchDelete_Empty(t *testing.T) {
  227. conn := testutil.GetTestSqlConn()
  228. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  229. if err := m.BatchDelete(context.Background(), nil); err != nil {
  230. t.Fatalf("nil: %v", err)
  231. }
  232. if err := m.BatchDelete(context.Background(), []int64{}); err != nil {
  233. t.Fatalf("empty: %v", err)
  234. }
  235. }
  236. // TC-0314: 事务内插入
  237. func TestSysProductMemberModel_InsertWithTx_Normal(t *testing.T) {
  238. ctx := context.Background()
  239. conn := testutil.GetTestSqlConn()
  240. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  241. pc := "t_pm_itx_" + testutil.UniqueId()
  242. userId := randProductMemberUserId()
  243. ts := time.Now().Unix()
  244. var insertedId int64
  245. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  246. res, err := m.InsertWithTx(c, session, &SysProductMember{
  247. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  248. })
  249. if err != nil {
  250. return err
  251. }
  252. insertedId, _ = res.LastInsertId()
  253. return nil
  254. })
  255. if err != nil {
  256. t.Fatalf("TransactCtx: %v", err)
  257. }
  258. defer testutil.CleanTable(ctx, conn, "sys_product_member", insertedId)
  259. got, err := m.FindOne(ctx, insertedId)
  260. if err != nil {
  261. t.Fatalf("FindOne: %v", err)
  262. }
  263. if got.ProductCode != pc || got.UserId != userId {
  264. t.Fatalf("mismatch: %+v", got)
  265. }
  266. }
  267. // TC-0316: 事务回滚后无数据
  268. func TestSysProductMemberModel_InsertWithTx_Rollback(t *testing.T) {
  269. ctx := context.Background()
  270. conn := testutil.GetTestSqlConn()
  271. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  272. pc := "t_pm_irb_" + testutil.UniqueId()
  273. userId := randProductMemberUserId()
  274. ts := time.Now().Unix()
  275. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  276. _, err := m.InsertWithTx(c, session, &SysProductMember{
  277. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  278. })
  279. if err != nil {
  280. return err
  281. }
  282. return errors.New("rollback")
  283. })
  284. if err == nil || err.Error() != "rollback" {
  285. t.Fatalf("want rollback error got %v", err)
  286. }
  287. _, err = m.FindOneByProductCodeUserId(ctx, pc, userId)
  288. if err != ErrNotFound {
  289. t.Fatalf("after rollback want ErrNotFound got %v", err)
  290. }
  291. }
  292. // TC-0326: 记录不存在
  293. func TestSysProductMemberModel_Update_NotFound(t *testing.T) {
  294. conn := testutil.GetTestSqlConn()
  295. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  296. ts := time.Now().Unix()
  297. err := m.Update(context.Background(), &SysProductMember{
  298. Id: 999999999, ProductCode: "nope", UserId: 1, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  299. })
  300. if err != ErrNotFound {
  301. t.Fatalf("want ErrNotFound got %v", err)
  302. }
  303. }
  304. // TC-0327: 事务内更新
  305. func TestSysProductMemberModel_UpdateWithTx(t *testing.T) {
  306. ctx := context.Background()
  307. conn := testutil.GetTestSqlConn()
  308. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  309. pc := "t_pm_utx_" + testutil.UniqueId()
  310. userId := randProductMemberUserId()
  311. ts := time.Now().Unix()
  312. res, err := m.Insert(ctx, &SysProductMember{
  313. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  314. })
  315. if err != nil {
  316. t.Fatalf("Insert: %v", err)
  317. }
  318. id, _ := res.LastInsertId()
  319. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  320. newTs := ts + 100
  321. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  322. return m.UpdateWithTx(c, session, &SysProductMember{
  323. Id: id, ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 2, CreateTime: ts, UpdateTime: newTs,
  324. })
  325. })
  326. if err != nil {
  327. t.Fatalf("UpdateWithTx: %v", err)
  328. }
  329. got, err := m.FindOne(ctx, id)
  330. if err != nil {
  331. t.Fatalf("FindOne: %v", err)
  332. }
  333. if got.MemberType != "ADMIN" || got.Status != 2 || got.UpdateTime != newTs {
  334. t.Fatalf("mismatch: %+v", got)
  335. }
  336. }
  337. // TC-0329: 记录不存在
  338. func TestSysProductMemberModel_Delete_NotFound(t *testing.T) {
  339. conn := testutil.GetTestSqlConn()
  340. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  341. err := m.Delete(context.Background(), 999999999)
  342. if err != ErrNotFound {
  343. t.Fatalf("want ErrNotFound got %v", err)
  344. }
  345. }
  346. // TC-0330: 事务内删除
  347. func TestSysProductMemberModel_DeleteWithTx(t *testing.T) {
  348. ctx := context.Background()
  349. conn := testutil.GetTestSqlConn()
  350. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  351. pc := "t_pm_dtx_" + testutil.UniqueId()
  352. userId := randProductMemberUserId()
  353. ts := time.Now().Unix()
  354. res, err := m.Insert(ctx, &SysProductMember{
  355. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  356. })
  357. if err != nil {
  358. t.Fatalf("Insert: %v", err)
  359. }
  360. id, _ := res.LastInsertId()
  361. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  362. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  363. return m.DeleteWithTx(c, session, id)
  364. })
  365. if err != nil {
  366. t.Fatalf("DeleteWithTx: %v", err)
  367. }
  368. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  369. t.Fatalf("after DeleteWithTx want ErrNotFound got %v", err)
  370. }
  371. }
  372. // TC-0331: 正常事务
  373. func TestSysProductMemberModel_TransactCtx_CommitAndRollback(t *testing.T) {
  374. ctx := context.Background()
  375. conn := testutil.GetTestSqlConn()
  376. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  377. pc := "t_pm_txc_" + testutil.UniqueId()
  378. userId := randProductMemberUserId()
  379. ts := time.Now().Unix()
  380. var insertedId int64
  381. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  382. res, err := m.InsertWithTx(c, session, &SysProductMember{
  383. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  384. })
  385. if err != nil {
  386. return err
  387. }
  388. insertedId, _ = res.LastInsertId()
  389. return nil
  390. })
  391. if err != nil {
  392. t.Fatalf("commit: %v", err)
  393. }
  394. defer testutil.CleanTable(ctx, conn, "sys_product_member", insertedId)
  395. got, err := m.FindOne(ctx, insertedId)
  396. if err != nil {
  397. t.Fatalf("FindOne after commit: %v", err)
  398. }
  399. if got.ProductCode != pc {
  400. t.Fatalf("productCode mismatch: %s", got.ProductCode)
  401. }
  402. pc2 := "t_pm_txr_" + testutil.UniqueId()
  403. userId2 := randProductMemberUserId()
  404. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  405. _, err := m.InsertWithTx(c, session, &SysProductMember{
  406. ProductCode: pc2, UserId: userId2, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  407. })
  408. if err != nil {
  409. return err
  410. }
  411. return errors.New("rollback")
  412. })
  413. if err == nil || err.Error() != "rollback" {
  414. t.Fatalf("want rollback error got %v", err)
  415. }
  416. _, err = m.FindOneByProductCodeUserId(ctx, pc2, userId2)
  417. if err != ErrNotFound {
  418. t.Fatalf("after rollback want ErrNotFound got %v", err)
  419. }
  420. }
  421. // TC-0333: 获取表名
  422. func TestSysProductMemberModel_TableName(t *testing.T) {
  423. conn := testutil.GetTestSqlConn()
  424. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  425. if m.TableName() != "`sys_product_member`" {
  426. t.Fatalf("want `sys_product_member` got %s", m.TableName())
  427. }
  428. }
  429. // TC-0335: 单条记录
  430. func TestSysProductMemberModel_BatchInsert_Single(t *testing.T) {
  431. ctx := context.Background()
  432. conn := testutil.GetTestSqlConn()
  433. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  434. pc := "t_pm_bis_" + testutil.UniqueId()
  435. userId := randProductMemberUserId()
  436. ts := time.Now().Unix()
  437. if err := m.BatchInsert(ctx, []*SysProductMember{
  438. {ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts},
  439. }); err != nil {
  440. t.Fatalf("BatchInsert: %v", err)
  441. }
  442. got, err := m.FindOneByProductCodeUserId(ctx, pc, userId)
  443. if err != nil {
  444. t.Fatalf("FindOneByProductCodeUserId: %v", err)
  445. }
  446. defer testutil.CleanTable(ctx, conn, "sys_product_member", got.Id)
  447. if got.MemberType != "MEMBER" {
  448. t.Fatalf("mismatch: %+v", got)
  449. }
  450. }
  451. // TC-0338: 唯一索引冲突
  452. func TestSysProductMemberModel_BatchInsert_UniqueConflict(t *testing.T) {
  453. ctx := context.Background()
  454. conn := testutil.GetTestSqlConn()
  455. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  456. pc := "t_pm_bic_" + testutil.UniqueId()
  457. userId := randProductMemberUserId()
  458. ts := time.Now().Unix()
  459. err := m.BatchInsert(ctx, []*SysProductMember{
  460. {ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts},
  461. {ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts},
  462. })
  463. if err == nil {
  464. t.Fatal("want error for duplicate")
  465. }
  466. var me *mysql.MySQLError
  467. if !errors.As(err, &me) || me.Number != 1062 {
  468. t.Fatalf("want duplicate key 1062, got %v", err)
  469. }
  470. }
  471. // TC-0343: 空列表
  472. func TestSysProductMemberModel_BatchUpdate_Empty(t *testing.T) {
  473. conn := testutil.GetTestSqlConn()
  474. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  475. if err := m.BatchUpdate(context.Background(), nil); err != nil {
  476. t.Fatalf("nil: %v", err)
  477. }
  478. if err := m.BatchUpdate(context.Background(), []*SysProductMember{}); err != nil {
  479. t.Fatalf("empty: %v", err)
  480. }
  481. }
  482. // TC-0345: 多条记录(3条)
  483. func TestSysProductMemberModel_BatchUpdate_Multi(t *testing.T) {
  484. ctx := context.Background()
  485. conn := testutil.GetTestSqlConn()
  486. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  487. pc := "t_pm_bum_" + testutil.UniqueId()
  488. u1, u2 := randProductMemberUserId(), randProductMemberUserId()
  489. ts := time.Now().Unix()
  490. res1, err := m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: u1, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts})
  491. if err != nil {
  492. t.Fatalf("Insert1: %v", err)
  493. }
  494. id1, _ := res1.LastInsertId()
  495. res2, err := m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: u2, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts})
  496. if err != nil {
  497. t.Fatalf("Insert2: %v", err)
  498. }
  499. id2, _ := res2.LastInsertId()
  500. defer testutil.CleanTable(ctx, conn, "sys_product_member", id1, id2)
  501. newTs := ts + 100
  502. err = m.BatchUpdate(ctx, []*SysProductMember{
  503. {Id: id1, ProductCode: pc, UserId: u1, MemberType: "ADMIN", Status: 2, CreateTime: ts, UpdateTime: newTs},
  504. {Id: id2, ProductCode: pc, UserId: u2, MemberType: "ADMIN", Status: 2, CreateTime: ts, UpdateTime: newTs},
  505. })
  506. if err != nil {
  507. t.Fatalf("BatchUpdate: %v", err)
  508. }
  509. got1, err := m.FindOne(ctx, id1)
  510. if err != nil {
  511. t.Fatalf("FindOne1: %v", err)
  512. }
  513. if got1.MemberType != "ADMIN" || got1.Status != 2 || got1.UpdateTime != newTs {
  514. t.Fatalf("got1 mismatch: %+v", got1)
  515. }
  516. got2, err := m.FindOne(ctx, id2)
  517. if err != nil {
  518. t.Fatalf("FindOne2: %v", err)
  519. }
  520. if got2.MemberType != "ADMIN" || got2.Status != 2 || got2.UpdateTime != newTs {
  521. t.Fatalf("got2 mismatch: %+v", got2)
  522. }
  523. }
  524. // TC-0355: 多个id(3个)
  525. func TestSysProductMemberModel_BatchDelete_Multi(t *testing.T) {
  526. ctx := context.Background()
  527. conn := testutil.GetTestSqlConn()
  528. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  529. pc := "t_pm_bdm_" + testutil.UniqueId()
  530. ts := time.Now().Unix()
  531. var ids []int64
  532. for i := 0; i < 3; i++ {
  533. res, err := m.Insert(ctx, &SysProductMember{
  534. ProductCode: pc, UserId: randProductMemberUserId(), MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  535. })
  536. if err != nil {
  537. t.Fatalf("Insert: %v", err)
  538. }
  539. id, _ := res.LastInsertId()
  540. ids = append(ids, id)
  541. }
  542. defer func() {
  543. for _, id := range ids {
  544. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  545. }
  546. }()
  547. if err := m.BatchDelete(ctx, ids); err != nil {
  548. t.Fatalf("BatchDelete: %v", err)
  549. }
  550. for _, id := range ids {
  551. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  552. t.Fatalf("id %d should be deleted: %v", id, err)
  553. }
  554. }
  555. }
  556. // TC-0354: 单个id
  557. func TestSysProductMemberModel_BatchDelete_Single(t *testing.T) {
  558. ctx := context.Background()
  559. conn := testutil.GetTestSqlConn()
  560. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  561. pc := "t_pm_bds_" + testutil.UniqueId()
  562. userId := randProductMemberUserId()
  563. ts := time.Now().Unix()
  564. res, err := m.Insert(ctx, &SysProductMember{
  565. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  566. })
  567. if err != nil {
  568. t.Fatalf("Insert: %v", err)
  569. }
  570. id, _ := res.LastInsertId()
  571. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  572. if err := m.BatchDelete(ctx, []int64{id}); err != nil {
  573. t.Fatalf("BatchDelete: %v", err)
  574. }
  575. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  576. t.Fatalf("want ErrNotFound got %v", err)
  577. }
  578. }
  579. // TC-0356: 包含不存在id
  580. func TestSysProductMemberModel_BatchDelete_ContainsNonExist(t *testing.T) {
  581. ctx := context.Background()
  582. conn := testutil.GetTestSqlConn()
  583. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  584. pc := "t_pm_bdn_" + testutil.UniqueId()
  585. userId := randProductMemberUserId()
  586. ts := time.Now().Unix()
  587. res, err := m.Insert(ctx, &SysProductMember{
  588. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  589. })
  590. if err != nil {
  591. t.Fatalf("Insert: %v", err)
  592. }
  593. id, _ := res.LastInsertId()
  594. defer testutil.CleanTable(ctx, conn, "sys_product_member", id)
  595. if err := m.BatchDelete(ctx, []int64{id, 999999999}); err != nil {
  596. t.Fatalf("BatchDelete: %v", err)
  597. }
  598. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  599. t.Fatalf("want ErrNotFound got %v", err)
  600. }
  601. }
  602. // TC-0341: 正常多条
  603. func TestSysProductMemberModel_BatchInsertWithTx_Normal(t *testing.T) {
  604. ctx := context.Background()
  605. conn := testutil.GetTestSqlConn()
  606. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  607. pc := "t_pm_bitn_" + testutil.UniqueId()
  608. u1, u2 := randProductMemberUserId(), randProductMemberUserId()
  609. ts := time.Now().Unix()
  610. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  611. return m.BatchInsertWithTx(c, session, []*SysProductMember{
  612. {ProductCode: pc, UserId: u1, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts},
  613. {ProductCode: pc, UserId: u2, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts},
  614. })
  615. })
  616. if err != nil {
  617. t.Fatalf("BatchInsertWithTx: %v", err)
  618. }
  619. got1, err := m.FindOneByProductCodeUserId(ctx, pc, u1)
  620. if err != nil {
  621. t.Fatalf("FindOne u1: %v", err)
  622. }
  623. got2, err := m.FindOneByProductCodeUserId(ctx, pc, u2)
  624. if err != nil {
  625. t.Fatalf("FindOne u2: %v", err)
  626. }
  627. defer testutil.CleanTable(ctx, conn, "sys_product_member", got1.Id, got2.Id)
  628. }
  629. // TC-0340: 空列表
  630. func TestSysProductMemberModel_BatchInsertWithTx_Empty(t *testing.T) {
  631. ctx := context.Background()
  632. conn := testutil.GetTestSqlConn()
  633. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  634. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  635. if err := m.BatchInsertWithTx(c, session, nil); err != nil {
  636. return err
  637. }
  638. return m.BatchInsertWithTx(c, session, []*SysProductMember{})
  639. })
  640. if err != nil {
  641. t.Fatalf("BatchInsertWithTx empty: %v", err)
  642. }
  643. }
  644. // TC-0342: 事务回滚
  645. func TestSysProductMemberModel_BatchInsertWithTx_Rollback(t *testing.T) {
  646. ctx := context.Background()
  647. conn := testutil.GetTestSqlConn()
  648. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  649. pc := "t_pm_bitr_" + testutil.UniqueId()
  650. userId := randProductMemberUserId()
  651. ts := time.Now().Unix()
  652. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  653. if err := m.BatchInsertWithTx(c, session, []*SysProductMember{
  654. {ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts},
  655. }); err != nil {
  656. return err
  657. }
  658. return errors.New("rollback")
  659. })
  660. if err == nil || err.Error() != "rollback" {
  661. t.Fatalf("want rollback error got %v", err)
  662. }
  663. _, err = m.FindOneByProductCodeUserId(ctx, pc, userId)
  664. if err != ErrNotFound {
  665. t.Fatalf("after rollback want ErrNotFound got %v", err)
  666. }
  667. }
  668. // TC-0349: 正常多条
  669. func TestSysProductMemberModel_BatchUpdateWithTx_Normal(t *testing.T) {
  670. ctx := context.Background()
  671. conn := testutil.GetTestSqlConn()
  672. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  673. pc := "t_pm_butn_" + testutil.UniqueId()
  674. u1, u2 := randProductMemberUserId(), randProductMemberUserId()
  675. ts := time.Now().Unix()
  676. res1, err := m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: u1, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts})
  677. if err != nil {
  678. t.Fatalf("Insert1: %v", err)
  679. }
  680. id1, _ := res1.LastInsertId()
  681. res2, err := m.Insert(ctx, &SysProductMember{ProductCode: pc, UserId: u2, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts})
  682. if err != nil {
  683. t.Fatalf("Insert2: %v", err)
  684. }
  685. id2, _ := res2.LastInsertId()
  686. defer testutil.CleanTable(ctx, conn, "sys_product_member", id1, id2)
  687. newTs := ts + 200
  688. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  689. return m.BatchUpdateWithTx(c, session, []*SysProductMember{
  690. {Id: id1, ProductCode: pc, UserId: u1, MemberType: "ADMIN", Status: 2, CreateTime: ts, UpdateTime: newTs},
  691. {Id: id2, ProductCode: pc, UserId: u2, MemberType: "ADMIN", Status: 2, CreateTime: ts, UpdateTime: newTs},
  692. })
  693. })
  694. if err != nil {
  695. t.Fatalf("BatchUpdateWithTx: %v", err)
  696. }
  697. got1, err := m.FindOne(ctx, id1)
  698. if err != nil {
  699. t.Fatalf("FindOne1: %v", err)
  700. }
  701. if got1.MemberType != "ADMIN" || got1.Status != 2 || got1.UpdateTime != newTs {
  702. t.Fatalf("got1 mismatch: %+v", got1)
  703. }
  704. }
  705. // TC-0348: 空列表
  706. func TestSysProductMemberModel_BatchUpdateWithTx_Empty(t *testing.T) {
  707. ctx := context.Background()
  708. conn := testutil.GetTestSqlConn()
  709. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  710. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  711. if err := m.BatchUpdateWithTx(c, session, nil); err != nil {
  712. return err
  713. }
  714. return m.BatchUpdateWithTx(c, session, []*SysProductMember{})
  715. })
  716. if err != nil {
  717. t.Fatalf("BatchUpdateWithTx empty: %v", err)
  718. }
  719. }
  720. // TC-0358: 正常多条
  721. func TestSysProductMemberModel_BatchDeleteWithTx_Normal(t *testing.T) {
  722. ctx := context.Background()
  723. conn := testutil.GetTestSqlConn()
  724. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  725. pc := "t_pm_bdtn_" + testutil.UniqueId()
  726. ts := time.Now().Unix()
  727. var ids []int64
  728. for i := 0; i < 2; i++ {
  729. res, err := m.Insert(ctx, &SysProductMember{
  730. ProductCode: pc, UserId: randProductMemberUserId(), MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  731. })
  732. if err != nil {
  733. t.Fatalf("Insert: %v", err)
  734. }
  735. id, _ := res.LastInsertId()
  736. ids = append(ids, id)
  737. }
  738. defer func() {
  739. for _, id := range ids {
  740. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  741. }
  742. }()
  743. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  744. return m.BatchDeleteWithTx(c, session, ids)
  745. })
  746. if err != nil {
  747. t.Fatalf("BatchDeleteWithTx: %v", err)
  748. }
  749. for _, id := range ids {
  750. if _, err := m.FindOne(ctx, id); err != ErrNotFound {
  751. t.Fatalf("id %d should be deleted: %v", id, err)
  752. }
  753. }
  754. }
  755. // TC-0357: 空ids
  756. func TestSysProductMemberModel_BatchDeleteWithTx_Empty(t *testing.T) {
  757. ctx := context.Background()
  758. conn := testutil.GetTestSqlConn()
  759. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  760. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  761. if err := m.BatchDeleteWithTx(c, session, nil); err != nil {
  762. return err
  763. }
  764. return m.BatchDeleteWithTx(c, session, []int64{})
  765. })
  766. if err != nil {
  767. t.Fatalf("BatchDeleteWithTx empty: %v", err)
  768. }
  769. }
  770. // TC-0323: 事务内可见性
  771. func TestSysProductMemberModel_FindOneWithTx_InsertThenFind(t *testing.T) {
  772. ctx := context.Background()
  773. conn := testutil.GetTestSqlConn()
  774. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  775. pc := "t_pm_fotx_" + testutil.UniqueId()
  776. userId := randProductMemberUserId()
  777. ts := time.Now().Unix()
  778. var insertedId int64
  779. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  780. res, err := m.InsertWithTx(c, session, &SysProductMember{
  781. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: ts, UpdateTime: ts,
  782. })
  783. if err != nil {
  784. return err
  785. }
  786. insertedId, err = res.LastInsertId()
  787. if err != nil {
  788. return err
  789. }
  790. got, err := m.FindOneWithTx(c, session, insertedId)
  791. if err != nil {
  792. return err
  793. }
  794. assert.Equal(t, pc, got.ProductCode)
  795. assert.Equal(t, userId, got.UserId)
  796. assert.Equal(t, "MEMBER", got.MemberType)
  797. return nil
  798. })
  799. require.NoError(t, err)
  800. defer testutil.CleanTable(ctx, conn, "sys_product_member", insertedId)
  801. }
  802. // TC-0322: 事务内记录不存在
  803. func TestSysProductMemberModel_FindOneWithTx_NotFound(t *testing.T) {
  804. ctx := context.Background()
  805. conn := testutil.GetTestSqlConn()
  806. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  807. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  808. _, err := m.FindOneWithTx(c, session, 999999999999)
  809. require.ErrorIs(t, err, ErrNotFound)
  810. return nil
  811. })
  812. require.NoError(t, err)
  813. }
  814. // TC-0393: FindOneByProductCodeUserIdWithTx
  815. func TestSysProductMemberModel_FindOneByProductCodeUserIdWithTx_InsertThenFind(t *testing.T) {
  816. ctx := context.Background()
  817. conn := testutil.GetTestSqlConn()
  818. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  819. pc := "t_pm_fbytx_" + testutil.UniqueId()
  820. userId := randProductMemberUserId()
  821. ts := time.Now().Unix()
  822. var insertedId int64
  823. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  824. res, err := m.InsertWithTx(c, session, &SysProductMember{
  825. ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 1, CreateTime: ts, UpdateTime: ts,
  826. })
  827. if err != nil {
  828. return err
  829. }
  830. insertedId, err = res.LastInsertId()
  831. if err != nil {
  832. return err
  833. }
  834. got, err := m.FindOneByProductCodeUserIdWithTx(c, session, pc, userId)
  835. if err != nil {
  836. return err
  837. }
  838. assert.Equal(t, insertedId, got.Id)
  839. assert.Equal(t, pc, got.ProductCode)
  840. assert.Equal(t, userId, got.UserId)
  841. return nil
  842. })
  843. require.NoError(t, err)
  844. defer testutil.CleanTable(ctx, conn, "sys_product_member", insertedId)
  845. }
  846. // TC-0394: FindOneByProductCodeUserIdWithTx
  847. func TestSysProductMemberModel_FindOneByProductCodeUserIdWithTx_NotFound(t *testing.T) {
  848. ctx := context.Background()
  849. conn := testutil.GetTestSqlConn()
  850. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  851. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  852. _, err := m.FindOneByProductCodeUserIdWithTx(c, session, "notexist_"+testutil.UniqueId(), 999999999)
  853. require.ErrorIs(t, err, ErrNotFound)
  854. return nil
  855. })
  856. require.NoError(t, err)
  857. }
  858. // TC-0478 / TC-0480: [REMOVED] 参见 TC-0477;方法已随 清理一并移除。
  859. func TestCountOtherActiveAdminsTx_SoleAdmin_ReturnsZero(t *testing.T) {
  860. ctx := context.Background()
  861. conn := testutil.GetTestSqlConn()
  862. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  863. pc := "t_pm_coaa_sole_" + testutil.UniqueId()
  864. adminUser := randProductMemberUserId()
  865. ts := time.Now().Unix()
  866. res, err := m.Insert(ctx, &SysProductMember{
  867. ProductCode: pc, UserId: adminUser,
  868. MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled,
  869. CreateTime: ts, UpdateTime: ts,
  870. })
  871. require.NoError(t, err)
  872. adminId, _ := res.LastInsertId()
  873. defer testutil.CleanTable(ctx, conn, "sys_product_member", adminId)
  874. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  875. n, e := m.CountOtherActiveAdminsTx(c, session, pc, adminId)
  876. require.NoError(t, e)
  877. assert.Equal(t, int64(0), n,
  878. "唯一 admin 排除自己后必须为 0,调用方据此才能阻止删除最后一个 admin")
  879. return nil
  880. })
  881. require.NoError(t, err)
  882. }
  883. // TC-0868: 多 admin 场景,排除 A 后返回剩余 backup admin 数量。
  884. func TestCountOtherActiveAdminsTx_MultipleAdmins_ExcludesSelf(t *testing.T) {
  885. ctx := context.Background()
  886. conn := testutil.GetTestSqlConn()
  887. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  888. pc := "t_pm_coaa_multi_" + testutil.UniqueId()
  889. ts := time.Now().Unix()
  890. // 插三个启用 ADMIN + 一个启用 MEMBER + 一个禁用 ADMIN,用来检验 WHERE 条件完整性。
  891. type row struct {
  892. mt string
  893. status int64
  894. }
  895. rows := []row{
  896. {consts.MemberTypeAdmin, consts.StatusEnabled},
  897. {consts.MemberTypeAdmin, consts.StatusEnabled},
  898. {consts.MemberTypeAdmin, consts.StatusEnabled},
  899. {consts.MemberTypeMember, consts.StatusEnabled}, // 不计入
  900. {consts.MemberTypeAdmin, consts.StatusDisabled}, // 不计入
  901. }
  902. ids := make([]int64, 0, len(rows))
  903. for _, r := range rows {
  904. uid := randProductMemberUserId()
  905. res, err := m.Insert(ctx, &SysProductMember{
  906. ProductCode: pc, UserId: uid,
  907. MemberType: r.mt, Status: r.status,
  908. CreateTime: ts, UpdateTime: ts,
  909. })
  910. require.NoError(t, err)
  911. id, _ := res.LastInsertId()
  912. ids = append(ids, id)
  913. }
  914. t.Cleanup(func() {
  915. for _, id := range ids {
  916. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  917. }
  918. })
  919. // 排除 ids[0]:剩下应还有两个启用 ADMIN。
  920. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  921. n, e := m.CountOtherActiveAdminsTx(c, session, pc, ids[0])
  922. require.NoError(t, e)
  923. assert.Equal(t, int64(2), n,
  924. "MEMBER 与 Disabled 行不得被计入;排除自己后剩余 admin 数必须等于 2")
  925. return nil
  926. })
  927. require.NoError(t, err)
  928. }
  929. // TC-0869: 排除一个根本不存在的 id,CountOtherActiveAdminsTx 应直接返回总数 2。
  930. //
  931. // 本轮已经把冗余的 CountActiveAdminsTx(不带 Other)从接口删掉,自洽性校验从
  932. //
  933. // CountOther(-1) == CountActive(pc)
  934. //
  935. // 收紧为
  936. //
  937. // CountOther(-1) == 已知播种总数
  938. //
  939. // 语义等价,但不再依赖已删除的镜像方法(收敛 surface area)。
  940. func TestCountOtherActiveAdminsTx_NonExistentExclude_EqualsTotal(t *testing.T) {
  941. ctx := context.Background()
  942. conn := testutil.GetTestSqlConn()
  943. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  944. pc := "t_pm_coaa_none_" + testutil.UniqueId()
  945. ts := time.Now().Unix()
  946. var ids []int64
  947. for i := 0; i < 2; i++ {
  948. res, err := m.Insert(ctx, &SysProductMember{
  949. ProductCode: pc, UserId: randProductMemberUserId(),
  950. MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled,
  951. CreateTime: ts, UpdateTime: ts,
  952. })
  953. require.NoError(t, err)
  954. id, _ := res.LastInsertId()
  955. ids = append(ids, id)
  956. }
  957. t.Cleanup(func() {
  958. for _, id := range ids {
  959. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  960. }
  961. })
  962. err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  963. other, e := m.CountOtherActiveAdminsTx(c, session, pc, -1) // -1 不存在
  964. require.NoError(t, e)
  965. assert.Equal(t, int64(2), other,
  966. "excludeId 不存在时 CountOtherActiveAdminsTx 应等于产品内 active admin 总数")
  967. return nil
  968. })
  969. require.NoError(t, err)
  970. }
  971. // TC-0870: 空 productCode 不会串库 —— 不同产品线互不影响。
  972. func TestCountOtherActiveAdminsTx_ScopedByProductCode(t *testing.T) {
  973. ctx := context.Background()
  974. conn := testutil.GetTestSqlConn()
  975. m := NewSysProductMemberModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix())
  976. ts := time.Now().Unix()
  977. pcA := "t_pm_coaa_A_" + testutil.UniqueId()
  978. pcB := "t_pm_coaa_B_" + testutil.UniqueId()
  979. // 产品 A 有 1 个 admin(自己),排除后应为 0;产品 B 有 2 个 admin,这条查询不应拉到产品 B。
  980. resA, err := m.Insert(ctx, &SysProductMember{
  981. ProductCode: pcA, UserId: randProductMemberUserId(),
  982. MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled,
  983. CreateTime: ts, UpdateTime: ts,
  984. })
  985. require.NoError(t, err)
  986. aId, _ := resA.LastInsertId()
  987. defer testutil.CleanTable(ctx, conn, "sys_product_member", aId)
  988. var bIds []int64
  989. for i := 0; i < 2; i++ {
  990. r, err := m.Insert(ctx, &SysProductMember{
  991. ProductCode: pcB, UserId: randProductMemberUserId(),
  992. MemberType: consts.MemberTypeAdmin, Status: consts.StatusEnabled,
  993. CreateTime: ts, UpdateTime: ts,
  994. })
  995. require.NoError(t, err)
  996. id, _ := r.LastInsertId()
  997. bIds = append(bIds, id)
  998. }
  999. t.Cleanup(func() {
  1000. for _, id := range bIds {
  1001. testutil.CleanTable(ctx, conn, "sys_product_member", id)
  1002. }
  1003. })
  1004. err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error {
  1005. n, e := m.CountOtherActiveAdminsTx(c, session, pcA, aId)
  1006. require.NoError(t, e)
  1007. assert.Equal(t, int64(0), n,
  1008. "pcA 的排除计数必须只看 pcA,绝不能把 pcB 的 2 个 admin 误计入")
  1009. return nil
  1010. })
  1011. require.NoError(t, err)
  1012. }