package role import ( "context" "errors" "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeromicro/go-zero/core/stores/sqlx" "math" "perms-system-server/internal/consts" "perms-system-server/internal/testutil" "strings" "testing" "time" ) func TestSysRoleModel_CRUD(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_crud_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: "role_crud_" + testutil.UniqueId(), Remark: "r", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, data) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) found, err := m.FindOne(ctx, id) require.NoError(t, err) require.NotNil(t, found) assert.Equal(t, id, found.Id) assert.Equal(t, data.Name, found.Name) found.Remark = "updated" found.UpdateTime = now + 1 require.NoError(t, m.Update(ctx, found)) after, err := m.FindOne(ctx, id) require.NoError(t, err) assert.Equal(t, "updated", after.Remark) require.NoError(t, m.Delete(ctx, id)) _, err = m.FindOne(ctx, id) require.Error(t, err) assert.True(t, errors.Is(err, ErrNotFound)) } // TC-0375: FindOneByProductCodeName func TestSysRoleModel_FindOneByProductCodeName_FoundAndNotFound(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_fn_" + testutil.UniqueId() name := "name_fn_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: name, Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, data) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) got, err := m.FindOneByProductCodeName(ctx, pc, name) require.NoError(t, err) require.NotNil(t, got) assert.Equal(t, id, got.Id) _, err = m.FindOneByProductCodeName(ctx, pc, "no_such_"+testutil.UniqueId()) require.Error(t, err) assert.True(t, errors.Is(err, ErrNotFound)) } // TC-0449: 正常分页 func TestSysRoleModel_FindListByProductCode_Pagination(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc := "pc_page_" + testutil.UniqueId() now := time.Now().Unix() var ids []int64 for i := range 5 { r := &SysRole{ ProductCode: pc, Name: "p_" + testutil.UniqueId() + "_" + string(rune('a'+i)), Remark: "", Status: 1, PermsLevel: int64(i + 1), CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, r) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) ids = append(ids, id) } t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, ids...) }) list, total, err := m.FindListByProductCode(ctx, pc, 1, 2) require.NoError(t, err) assert.Equal(t, int64(5), total) require.Len(t, list, 2) list2, total2, err := m.FindListByProductCode(ctx, pc, 2, 2) require.NoError(t, err) assert.Equal(t, int64(5), total2) require.Len(t, list2, 2) list3, total3, err := m.FindListByProductCode(ctx, pc, 3, 2) require.NoError(t, err) assert.Equal(t, int64(5), total3) require.Len(t, list3, 1) } // TC-0451: 正常 func TestSysRoleModel_FindByIds_NormalEmptyPartial(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() empty, err := m.FindByIds(ctx, nil) require.NoError(t, err) assert.Nil(t, empty) empty2, err := m.FindByIds(ctx, []int64{}) require.NoError(t, err) assert.Nil(t, empty2) now := time.Now().Unix() pc := "pc_ids_" + testutil.UniqueId() r1 := &SysRole{ProductCode: pc, Name: "i1_" + testutil.UniqueId(), Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} r2 := &SysRole{ProductCode: pc, Name: "i2_" + testutil.UniqueId(), Remark: "", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now} res1, err := m.Insert(ctx, r1) require.NoError(t, err) id1, _ := res1.LastInsertId() res2, err := m.Insert(ctx, r2) require.NoError(t, err) id2, _ := res2.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) }) list, err := m.FindByIds(ctx, []int64{id1, id2}) require.NoError(t, err) require.Len(t, list, 2) byID := map[int64]struct{}{list[0].Id: {}, list[1].Id: {}} assert.Contains(t, byID, id1) assert.Contains(t, byID, id2) partial, err := m.FindByIds(ctx, []int64{id1, math.MaxInt64}) require.NoError(t, err) require.Len(t, partial, 1) assert.Equal(t, id1, partial[0].Id) } // TC-0336: 多条记录(3条) func TestSysRoleModel_BatchInsert_BatchDelete(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bi_" + testutil.UniqueId() tag := testutil.UniqueId() batch := []*SysRole{ {ProductCode: pc, Name: "bi1_" + tag, Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, {ProductCode: pc, Name: "bi2_" + tag, Remark: "", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now}, } require.NoError(t, m.BatchInsert(ctx, batch)) list, _, err := m.FindListByProductCode(ctx, pc, 1, 10) require.NoError(t, err) require.GreaterOrEqual(t, len(list), 2) var ids []int64 names := map[string]struct{}{"bi1_" + tag: {}, "bi2_" + tag: {}} for _, r := range list { if _, ok := names[r.Name]; ok { ids = append(ids, r.Id) } } require.Len(t, ids, 2) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, ids...) }) require.NoError(t, m.BatchDelete(ctx, ids)) for _, id := range ids { _, err := m.FindOne(ctx, id) require.Error(t, err) assert.True(t, errors.Is(err, ErrNotFound)) } } // TC-0312: 唯一索引冲突 func TestSysRoleModel_Insert_DuplicateProductCodeName(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_dup_" + testutil.UniqueId() name := "dup_name_" + testutil.UniqueId() first := &SysRole{ ProductCode: pc, Name: name, Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, first) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) dup := &SysRole{ ProductCode: pc, Name: name, Remark: "x", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now, } _, err = m.Insert(ctx, dup) require.Error(t, err) var me *mysql.MySQLError require.True(t, errors.As(err, &me)) assert.Equal(t, uint16(1062), me.Number) } // TC-0333: 获取表名 func TestSysRoleModel_TableName(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) assert.Equal(t, "`sys_role`", m.TableName()) } // TC-0319: 记录不存在 func TestSysRoleModel_FindOne_NotFound(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) _, err := m.FindOne(context.Background(), 999999999999) require.ErrorIs(t, err, ErrNotFound) } // TC-0326: 记录不存在 func TestSysRoleModel_Update_NotFound(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.Update(context.Background(), &SysRole{ Id: 999999999999, ProductCode: "x", Name: "n", Status: 1, PermsLevel: 1, CreateTime: time.Now().Unix(), UpdateTime: time.Now().Unix(), }) require.ErrorIs(t, err, ErrNotFound) } // TC-0329: 记录不存在 func TestSysRoleModel_Delete_NotFound(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.Delete(context.Background(), 999999999999) require.ErrorIs(t, err, ErrNotFound) } // TC-0334: 空列表 func TestSysRoleModel_BatchInsert_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) require.NoError(t, m.BatchInsert(context.Background(), nil)) require.NoError(t, m.BatchInsert(context.Background(), []*SysRole{})) } // TC-0353: 空ids func TestSysRoleModel_BatchDelete_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) require.NoError(t, m.BatchDelete(context.Background(), nil)) require.NoError(t, m.BatchDelete(context.Background(), []int64{})) } // TC-0450: 空结果 func TestSysRoleModel_FindListByProductCode_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) list, total, err := m.FindListByProductCode(context.Background(), "empty_"+testutil.UniqueId(), 1, 10) require.NoError(t, err) require.Equal(t, int64(0), total) require.Len(t, list, 0) } // TC-0314: 事务内插入 func TestSysRoleModel_InsertWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_itx_" + testutil.UniqueId() name := "itx_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: name, Remark: "", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } var insertedID int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, data) if err != nil { return err } insertedID, err = res.LastInsertId() return err }) require.NoError(t, err) require.Greater(t, insertedID, int64(0)) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, insertedID) }) got, err := m.FindOne(ctx, insertedID) require.NoError(t, err) assert.Equal(t, pc, got.ProductCode) assert.Equal(t, name, got.Name) } // TC-0316: 事务回滚后无数据 func TestSysRoleModel_InsertWithTx_Rollback(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() pc := "pc_irb_" + testutil.UniqueId() name := "irb_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: name, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { if _, e := m.InsertWithTx(c, session, data); e != nil { return e } return errors.New("force rollback") }) require.Error(t, err) _, err = m.FindOneByProductCodeName(ctx, pc, name) require.ErrorIs(t, err, ErrNotFound) } // TC-0327: 事务内更新 func TestSysRoleModel_UpdateWithTx(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_utx_" + testutil.UniqueId() name := "utx_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: name, Remark: "before", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, data) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { data.Id = id data.Remark = "after_tx" data.UpdateTime = time.Now().Unix() return m.UpdateWithTx(c, session, data) }) require.NoError(t, err) got, err := m.FindOne(ctx, id) require.NoError(t, err) assert.Equal(t, "after_tx", got.Remark) } // TC-0330: 事务内删除 func TestSysRoleModel_DeleteWithTx(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_dtx_" + testutil.UniqueId() data := &SysRole{ ProductCode: pc, Name: "dtx_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } res, err := m.Insert(ctx, data) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.DeleteWithTx(c, session, id) }) require.NoError(t, err) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } // TC-0331: 正常事务 func TestSysRoleModel_TransactCtx_CommitAndRollback(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc1 := "pc_txc_" + testutil.UniqueId() name1 := "txc_" + testutil.UniqueId() var id1 int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysRole{ ProductCode: pc1, Name: name1, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) if err != nil { return err } id1, err = res.LastInsertId() return err }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1) }) _, err = m.FindOne(ctx, id1) require.NoError(t, err) pc2 := "pc_txr_" + testutil.UniqueId() name2 := "txr_" + testutil.UniqueId() err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { if _, e := m.InsertWithTx(c, session, &SysRole{ ProductCode: pc2, Name: name2, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }); e != nil { return e } return errors.New("force rollback") }) require.Error(t, err) _, err = m.FindOneByProductCodeName(ctx, pc2, name2) require.ErrorIs(t, err, ErrNotFound) } // TC-0335: 单条记录 func TestSysRoleModel_BatchInsert_Single(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bis_" + testutil.UniqueId() name := "bis_" + testutil.UniqueId() require.NoError(t, m.BatchInsert(ctx, []*SysRole{ {ProductCode: pc, Name: name, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, })) found, err := m.FindOneByProductCodeName(ctx, pc, name) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, found.Id) }) assert.Equal(t, name, found.Name) } // TC-0338: 唯一索引冲突 func TestSysRoleModel_BatchInsert_UniqueConflict(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_biu_" + testutil.UniqueId() name := "biu_" + testutil.UniqueId() list := []*SysRole{ {ProductCode: pc, Name: name, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, {ProductCode: pc, Name: name, Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now}, } err := m.BatchInsert(ctx, list) require.Error(t, err) t.Cleanup(func() { if found, e := m.FindOneByProductCodeName(ctx, pc, name); e == nil { testutil.CleanTable(ctx, conn, tbl, found.Id) } }) var me *mysql.MySQLError if errors.As(err, &me) { assert.Equal(t, uint16(1062), me.Number) } } // TC-0343: 空列表 func TestSysRoleModel_BatchUpdate_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) require.NoError(t, m.BatchUpdate(context.Background(), nil)) require.NoError(t, m.BatchUpdate(context.Background(), []*SysRole{})) } // TC-0345: 多条记录(3条) func TestSysRoleModel_BatchUpdate_Multi(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bum_" + testutil.UniqueId() r1 := &SysRole{ProductCode: pc, Name: "bum1_" + testutil.UniqueId(), Remark: "old", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} r2 := &SysRole{ProductCode: pc, Name: "bum2_" + testutil.UniqueId(), Remark: "old", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now} res1, err := m.Insert(ctx, r1) require.NoError(t, err) id1, _ := res1.LastInsertId() res2, err := m.Insert(ctx, r2) require.NoError(t, err) id2, _ := res2.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) }) now2 := time.Now().Unix() upd := []*SysRole{ {Id: id1, ProductCode: pc, Name: r1.Name, Remark: "new1", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now2}, {Id: id2, ProductCode: pc, Name: r2.Name, Remark: "new2", Status: 2, PermsLevel: 2, CreateTime: now, UpdateTime: now2}, } require.NoError(t, m.BatchUpdate(ctx, upd)) g1, err := m.FindOne(ctx, id1) require.NoError(t, err) assert.Equal(t, "new1", g1.Remark) g2, err := m.FindOne(ctx, id2) require.NoError(t, err) assert.Equal(t, "new2", g2.Remark) assert.Equal(t, int64(2), g2.Status) } // TC-0341: 正常多条 func TestSysRoleModel_BatchInsertWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bitxn_" + testutil.UniqueId() n1 := "bitxn1_" + testutil.UniqueId() n2 := "bitxn2_" + testutil.UniqueId() err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.BatchInsertWithTx(c, session, []*SysRole{ {ProductCode: pc, Name: n1, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, {ProductCode: pc, Name: n2, Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now}, }) }) require.NoError(t, err) f1, err := m.FindOneByProductCodeName(ctx, pc, n1) require.NoError(t, err) f2, err := m.FindOneByProductCodeName(ctx, pc, n2) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, f1.Id, f2.Id) }) assert.Equal(t, n1, f1.Name) assert.Equal(t, n2, f2.Name) } // TC-0340: 空列表 func TestSysRoleModel_BatchInsertWithTx_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(context.Background(), func(c context.Context, session sqlx.Session) error { return m.BatchInsertWithTx(c, session, nil) }) require.NoError(t, err) } // TC-0342: 事务回滚 func TestSysRoleModel_BatchInsertWithTx_Rollback(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() pc := "pc_bitxr_" + testutil.UniqueId() name := "bitxr_" + testutil.UniqueId() err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { if e := m.BatchInsertWithTx(c, session, []*SysRole{ {ProductCode: pc, Name: name, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, }); e != nil { return e } return errors.New("force rollback") }) require.Error(t, err) _, err = m.FindOneByProductCodeName(ctx, pc, name) require.ErrorIs(t, err, ErrNotFound) } // TC-0349: 正常多条 func TestSysRoleModel_BatchUpdateWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_butxn_" + testutil.UniqueId() r1 := &SysRole{ProductCode: pc, Name: "butxn1_" + testutil.UniqueId(), Remark: "old", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} r2 := &SysRole{ProductCode: pc, Name: "butxn2_" + testutil.UniqueId(), Remark: "old", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now} res1, err := m.Insert(ctx, r1) require.NoError(t, err) id1, _ := res1.LastInsertId() res2, err := m.Insert(ctx, r2) require.NoError(t, err) id2, _ := res2.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) }) now2 := time.Now().Unix() err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.BatchUpdateWithTx(c, session, []*SysRole{ {Id: id1, ProductCode: pc, Name: r1.Name, Remark: "tx_new1", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now2}, {Id: id2, ProductCode: pc, Name: r2.Name, Remark: "tx_new2", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now2}, }) }) require.NoError(t, err) g1, err := m.FindOne(ctx, id1) require.NoError(t, err) assert.Equal(t, "tx_new1", g1.Remark) g2, err := m.FindOne(ctx, id2) require.NoError(t, err) assert.Equal(t, "tx_new2", g2.Remark) } // TC-0348: 空列表 func TestSysRoleModel_BatchUpdateWithTx_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(context.Background(), func(c context.Context, session sqlx.Session) error { return m.BatchUpdateWithTx(c, session, nil) }) require.NoError(t, err) } // TC-0354: 单个id func TestSysRoleModel_BatchDelete_Single(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bds_" + testutil.UniqueId() data := &SysRole{ProductCode: pc, Name: "bds_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} res, err := m.Insert(ctx, data) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) require.NoError(t, m.BatchDelete(ctx, []int64{id})) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } // TC-0356: 包含不存在id func TestSysRoleModel_BatchDelete_ContainsNonExist(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bdne_" + testutil.UniqueId() data := &SysRole{ProductCode: pc, Name: "bdne_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} res, err := m.Insert(ctx, data) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) require.NoError(t, m.BatchDelete(ctx, []int64{id, 999999999})) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } // TC-0358: 正常多条 func TestSysRoleModel_BatchDeleteWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_bdtxn_" + testutil.UniqueId() r1 := &SysRole{ProductCode: pc, Name: "bdtxn1_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now} r2 := &SysRole{ProductCode: pc, Name: "bdtxn2_" + testutil.UniqueId(), Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now} res1, err := m.Insert(ctx, r1) require.NoError(t, err) id1, _ := res1.LastInsertId() res2, err := m.Insert(ctx, r2) require.NoError(t, err) id2, _ := res2.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) }) err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.BatchDeleteWithTx(c, session, []int64{id1, id2}) }) require.NoError(t, err) _, err = m.FindOne(ctx, id1) require.ErrorIs(t, err, ErrNotFound) _, err = m.FindOne(ctx, id2) require.ErrorIs(t, err, ErrNotFound) } // TC-0357: 空ids func TestSysRoleModel_BatchDeleteWithTx_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(context.Background(), func(c context.Context, session sqlx.Session) error { return m.BatchDeleteWithTx(c, session, nil) }) require.NoError(t, err) } // TC-0323: 事务内可见性 func TestSysRoleModel_FindOneWithTx_InsertThenFind(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_fotx_" + testutil.UniqueId() name := "fotx_" + testutil.UniqueId() var insertedID int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysRole{ ProductCode: pc, Name: name, Remark: "r", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, }) if err != nil { return err } insertedID, err = res.LastInsertId() if err != nil { return err } got, err := m.FindOneWithTx(c, session, insertedID) if err != nil { return err } assert.Equal(t, pc, got.ProductCode) assert.Equal(t, name, got.Name) return nil }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, insertedID) }) } // TC-0322: 事务内记录不存在 func TestSysRoleModel_FindOneWithTx_NotFound(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.FindOneWithTx(c, session, 999999999999) require.ErrorIs(t, err, ErrNotFound) return nil }) require.NoError(t, err) } // TC-0377: FindOneByProductCodeNameWithTx func TestSysRoleModel_FindOneByProductCodeNameWithTx_InsertThenFind(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_fbntx_" + testutil.UniqueId() name := "fbntx_" + testutil.UniqueId() var insertedID int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysRole{ ProductCode: pc, Name: name, Remark: "", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now, }) if err != nil { return err } insertedID, err = res.LastInsertId() if err != nil { return err } got, err := m.FindOneByProductCodeNameWithTx(c, session, pc, name) if err != nil { return err } assert.Equal(t, insertedID, got.Id) assert.Equal(t, int64(2), got.PermsLevel) return nil }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, insertedID) }) } // TC-0378: FindOneByProductCodeNameWithTx func TestSysRoleModel_FindOneByProductCodeNameWithTx_NotFound(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.FindOneByProductCodeNameWithTx(c, session, "notexist_"+testutil.UniqueId(), "nope_"+testutil.UniqueId()) require.ErrorIs(t, err, ErrNotFound) return nil }) require.NoError(t, err) } // TC-0453: FindMinPermsLevelByUserIdAndProductCode 正常返回最小权限级别 func TestSysRoleModel_FindMinPermsLevelByUserIdAndProductCode_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() now := time.Now().Unix() pc := "pc_minlvl_" + testutil.UniqueId() r1 := &SysRole{ProductCode: pc, Name: "minlvl1_" + testutil.UniqueId(), Remark: "", Status: 1, PermsLevel: 10, CreateTime: now, UpdateTime: now} res1, err := m.Insert(ctx, r1) require.NoError(t, err) roleId1, _ := res1.LastInsertId() r2 := &SysRole{ProductCode: pc, Name: "minlvl2_" + testutil.UniqueId(), Remark: "", Status: 1, PermsLevel: 5, CreateTime: now, UpdateTime: now} res2, err := m.Insert(ctx, r2) require.NoError(t, err) roleId2, _ := res2.LastInsertId() userId := time.Now().UnixNano()%100_000_000 + 800_000_000 var urIds []int64 for _, rid := range []int64{roleId1, roleId2} { ur, err := conn.ExecCtx(ctx, "INSERT INTO `sys_user_role` (`userId`, `roleId`, `createTime`, `updateTime`) VALUES (?, ?, ?, ?)", userId, rid, now, now) require.NoError(t, err) urId, _ := ur.LastInsertId() urIds = append(urIds, urId) } t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, roleId1, roleId2) testutil.CleanTable(ctx, conn, "`sys_user_role`", urIds...) }) level, err := m.FindMinPermsLevelByUserIdAndProductCode(ctx, userId, pc) require.NoError(t, err) assert.Equal(t, int64(5), level) } // TC-0454: FindMinPermsLevelByUserIdAndProductCode 无角色返回ErrNotFound func TestSysRoleModel_FindMinPermsLevelByUserIdAndProductCode_NoRoles(t *testing.T) { conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) userId := time.Now().UnixNano()%100_000_000 + 900_000_000 _, err := m.FindMinPermsLevelByUserIdAndProductCode(context.Background(), userId, "nonexist_"+testutil.UniqueId()) require.ErrorIs(t, err, ErrNotFound) } // TC-0350: buildBatchUpdateQuery 单条 func TestSysRoleModel_buildBatchUpdateQuery_Single(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() dataList := []*SysRole{ {Id: 1, ProductCode: "pc", Name: "n", Remark: "r", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, } query, vals := dm.buildBatchUpdateQuery(dataList) assert.Contains(t, query, "UPDATE") assert.Contains(t, query, "CASE") assert.Contains(t, query, "WHEN `id` = ? THEN ?") assert.Contains(t, query, "WHERE `id` IN (?)") assert.Equal(t, 15, len(vals)) } // TC-0351: buildBatchUpdateQuery 多条 func TestSysRoleModel_buildBatchUpdateQuery_Multi(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() dataList := []*SysRole{ {Id: 1, ProductCode: "pc1", Name: "n1", Remark: "r1", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}, {Id: 2, ProductCode: "pc2", Name: "n2", Remark: "r2", Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now}, {Id: 3, ProductCode: "pc3", Name: "n3", Remark: "r3", Status: 1, PermsLevel: 3, CreateTime: now, UpdateTime: now}, } query, vals := dm.buildBatchUpdateQuery(dataList) assert.Equal(t, 7*3, strings.Count(query, "WHEN `id` = ? THEN ?")) assert.Contains(t, query, "WHERE `id` IN (?,?,?)") assert.Equal(t, 45, len(vals)) } // TC-0352: buildBatchUpdateQuery vals数量正确 func TestSysRoleModel_buildBatchUpdateQuery_ValsCount(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) now := time.Now().Unix() for _, n := range []int{1, 2, 5, 10} { dataList := make([]*SysRole, n) for i := range dataList { dataList[i] = &SysRole{ Id: int64(i + 1), ProductCode: "pc", Name: "n", Remark: "r", Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now, } } _, vals := dm.buildBatchUpdateQuery(dataList) expected := n * 15 assert.Equal(t, expected, len(vals), "n=%d", n) } } // TC-0395: findListByPrimaryKeys 空ids func TestSysRoleModel_findListByPrimaryKeys_Empty(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) list, err := dm.findListByPrimaryKeys(context.Background(), []interface{}{}) require.NoError(t, err) require.Empty(t, list) } // TC-0396: findListByPrimaryKeys 正常ids func TestSysRoleModel_findListByPrimaryKeys_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := dm.TableName() now := time.Now().Unix() pc := "pc_flbpk_" + testutil.UniqueId() res1, err := dm.Insert(ctx, &SysRole{ProductCode: pc, Name: "flbpk1_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}) require.NoError(t, err) id1, _ := res1.LastInsertId() res2, err := dm.Insert(ctx, &SysRole{ProductCode: pc, Name: "flbpk2_" + testutil.UniqueId(), Status: 1, PermsLevel: 2, CreateTime: now, UpdateTime: now}) require.NoError(t, err) id2, _ := res2.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id1, id2) }) list, err := dm.findListByPrimaryKeys(ctx, []interface{}{id1, id2}) require.NoError(t, err) require.Len(t, list, 2) } // TC-0397: findListByPrimaryKeys 部分不存在 func TestSysRoleModel_findListByPrimaryKeys_PartialNotExist(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := dm.TableName() now := time.Now().Unix() pc := "pc_flbpkp_" + testutil.UniqueId() res, err := dm.Insert(ctx, &SysRole{ProductCode: pc, Name: "flbpkp_" + testutil.UniqueId(), Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) list, err := dm.findListByPrimaryKeys(ctx, []interface{}{id, int64(99999999)}) require.NoError(t, err) require.Len(t, list, 1) assert.Equal(t, id, list[0].Id) } // TC-0398: findListByPrimaryKeys DB异常 func TestSysRoleModel_findListByPrimaryKeys_DBError(t *testing.T) { badConn := sqlx.NewMysql("root:bad@tcp(127.0.0.1:1)/nodb") dm := newSysRoleModel(badConn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) _, err := dm.findListByPrimaryKeys(context.Background(), []interface{}{int64(1)}) require.Error(t, err) } // TC-0399: getPrimaryKeyValue 正常 func TestSysRoleModel_getPrimaryKeyValue_Normal(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) data := &SysRole{Id: 42} val := dm.getPrimaryKeyValue(data) assert.Equal(t, int64(42), val) } // TC-0400: formatPrimary 正常 func TestSysRoleModel_formatPrimary_Normal(t *testing.T) { conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) key := dm.formatPrimary(123) assert.Contains(t, key, "cache:sysRole:id:") assert.Contains(t, key, "123") } // TC-0401: queryPrimary 正常 func TestSysRoleModel_queryPrimary_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() dm := newSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := dm.TableName() now := time.Now().Unix() pc := "pc_qp_" + testutil.UniqueId() name := "qp_" + testutil.UniqueId() res, err := dm.Insert(ctx, &SysRole{ProductCode: pc, Name: name, Status: 1, PermsLevel: 1, CreateTime: now, UpdateTime: now}) require.NoError(t, err) id, _ := res.LastInsertId() t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, id) }) var result SysRole err = dm.queryPrimary(ctx, conn, &result, id) require.NoError(t, err) assert.Equal(t, id, result.Id) assert.Equal(t, pc, result.ProductCode) assert.Equal(t, name, result.Name) } // TC-0402: cachePrefix为空 func TestSysRoleModel_CachePrefix_Empty(t *testing.T) { oldId := cacheSysRoleIdPrefix oldName := cacheSysRoleProductCodeNamePrefix defer func() { cacheSysRoleIdPrefix = oldId cacheSysRoleProductCodeNamePrefix = oldName }() cacheSysRoleIdPrefix = "cache:sysRole:id:" cacheSysRoleProductCodeNamePrefix = "cache:sysRole:productCode:name:" dm := newSysRoleModel(testutil.GetTestSqlConn(), testutil.GetTestCacheConf(), "") key := dm.formatPrimary(123) assert.Equal(t, "cache:sysRole:id:123", key) } // TC-0403: cachePrefix非空 func TestSysRoleModel_CachePrefix_NonEmpty(t *testing.T) { oldId := cacheSysRoleIdPrefix oldName := cacheSysRoleProductCodeNamePrefix defer func() { cacheSysRoleIdPrefix = oldId cacheSysRoleProductCodeNamePrefix = oldName }() dm := newSysRoleModel(testutil.GetTestSqlConn(), testutil.GetTestCacheConf(), "test") key := dm.formatPrimary(123) assert.Equal(t, "test:cache:sysRole:id:123", key) assert.True(t, strings.HasPrefix(key, "test:")) } // TC-0425: FindListByProductCode count查询失败(DB异常) func TestSysRoleModel_FindListByProductCode_DBError(t *testing.T) { badConn := sqlx.NewMysql("root:bad@tcp(127.0.0.1:1)/bad?timeout=1s") m := NewSysRoleModel(badConn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) _, _, err := m.FindListByProductCode(context.Background(), "any", 1, 10) require.Error(t, err) } func insertEnabledRole(t *testing.T, ctx context.Context, m SysRoleModel, pc string) int64 { t.Helper() now := time.Now().Unix() res, err := m.Insert(ctx, &SysRole{ ProductCode: pc, Name: "lr_" + testutil.UniqueId(), Status: consts.StatusEnabled, PermsLevel: 10, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) id, _ := res.LastInsertId() return id } func insertDisabledRole(t *testing.T, ctx context.Context, m SysRoleModel, pc string) int64 { t.Helper() now := time.Now().Unix() res, err := m.Insert(ctx, &SysRole{ ProductCode: pc, Name: "lr_" + testutil.UniqueId(), Status: consts.StatusDisabled, PermsLevel: 10, CreateTime: now, UpdateTime: now, }) require.NoError(t, err) id, _ := res.LastInsertId() return id } // TC-1072: LockRolesForShareTx happy path —— 所有 id 存在且 Enabled → nil func TestLockRolesForShareTx_HappyPath(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc := "pc_r121_happy_" + testutil.UniqueId() r1 := insertEnabledRole(t, ctx, m, pc) r2 := insertEnabledRole(t, ctx, m, pc) r3 := insertEnabledRole(t, ctx, m, pc) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, r1, r2, r3) }) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{r1, r2, r3}) }) require.NoError(t, err, "三条 Enabled role 全部命中时必须返回 nil") } // TC-1073: LockRolesForShareTx 某条 id 不存在(被 DeleteRole 删掉)→ ErrNotFound func TestLockRolesForShareTx_MissingRole_ReturnsErrNotFound(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc := "pc_r121_miss_" + testutil.UniqueId() r1 := insertEnabledRole(t, ctx, m, pc) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, r1) }) // 一个真实 id + 一个 DB 中完全不存在的 id → 期望一刀切 ErrNotFound err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{r1, 999999999}) }) require.Error(t, err) assert.ErrorIs(t, err, sqlx.ErrNotFound, "任一 id 对不上必须原样抛 sqlx.ErrNotFound,让 BindRoles 转 400;"+ "若改成 nil / 部分成功,DeleteRole 刚删掉的 roleId 会被重新绑定进去产生孤儿") } // TC-1074: LockRolesForShareTx 某条 id 为 Disabled(被 UpdateRole 禁用)→ ErrNotFound // 该契约封住"SELECT ... WHERE status=Enabled"这一层 status 过滤,是修复与 UpdateRole 禁用 // 并发下写偏斜的核心:禁用后就不应再出现"新增绑定"的交错。 func TestLockRolesForShareTx_DisabledRole_ReturnsErrNotFound(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc := "pc_r121_disabled_" + testutil.UniqueId() enabled := insertEnabledRole(t, ctx, m, pc) disabled := insertDisabledRole(t, ctx, m, pc) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, enabled, disabled) }) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{enabled, disabled}) }) require.Error(t, err) assert.ErrorIs(t, err, sqlx.ErrNotFound, "SELECT ... WHERE status=Enabled 的过滤是契约;Disabled 行即便 id 仍在"+ "也要当成'已失效'拦在 BindRoles 之前,避免绑到禁用角色") } // TC-1075: LockRolesForShareTx 空 ids → nil 且不查 DB(前置早退) func TestLockRolesForShareTx_EmptyIds_NoOp(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, nil) }) require.NoError(t, err, "空 ids 必须直接返回 nil,不能落 SQL(会因无 placeholder 语法错)") err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{}) }) require.NoError(t, err, "空切片与 nil 语义等价") } // TC-1076: LockRolesForShareTx 入参含重复 id → 去重后仍能正确比对 len // 这是"len(lockedIds) != len(sorted)" 这条契约的鲁棒性边界:如果 seen map 去重漏掉, // DB 会 SELECT 5 条返回 2 条唯一行,误判为"有 id 不存在" → 假阳性。 func TestLockRolesForShareTx_DuplicateIds_Deduped(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc := "pc_r121_dup_" + testutil.UniqueId() r1 := insertEnabledRole(t, ctx, m, pc) r2 := insertEnabledRole(t, ctx, m, pc) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, r1, r2) }) // 传入 5 份但只有 2 个唯一 id err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{r1, r2, r1, r2, r1}) }) require.NoError(t, err, "内部 seen 去重必须把重复 id 压成唯一集合,否则 DB SELECT 的命中行数"+ "会小于 len(ids) 造成假阳性 ErrNotFound") } // TC-1077: LockRolesForShareTx 产品不同但 id 集合命中 Enabled 记录 → 放行 // 当前 的语义是"按 id 锁 sys_role 行 + 仅校验 status",productCode 的校验留给 // 调用方(BindRoles 在事务外 FindByIds 之后逐条比较 r.ProductCode == productCode)。 // 本 TC 把" 函数本身**不**校验 productCode"这条契约钉死,避免未来维护者误把 // productCode 过滤塞进 SQL 造成 BindRoles 逻辑双重校验错位。 func TestLockRolesForShareTx_DoesNotFilterByProductCode(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := NewSysRoleModel(conn, testutil.GetTestCacheConf(), testutil.GetTestCachePrefix()) tbl := m.TableName() pc1 := "pc_r121_pcA_" + testutil.UniqueId() pc2 := "pc_r121_pcB_" + testutil.UniqueId() r1 := insertEnabledRole(t, ctx, m, pc1) r2 := insertEnabledRole(t, ctx, m, pc2) t.Cleanup(func() { testutil.CleanTable(ctx, conn, tbl, r1, r2) }) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.LockRolesForShareTx(c, session, []int64{r1, r2}) }) require.NoError(t, err, "函数只按 id + status 取锁,不做 productCode 过滤(那是 BindRoles 的职责);"+ "本 TC 锁死该边界,避免未来把 productCode 塞进 SQL 造成与上游校验重复 + 错误路径偏移") }