package product import ( "context" "errors" "fmt" "strings" "testing" "time" "perms-system-server/internal/testutil" "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zeromicro/go-zero/core/stores/sqlx" ) func newTestModel(t *testing.T) SysProductModel { t.Helper() return NewSysProductModel( testutil.GetTestSqlConn(), testutil.GetTestCacheConf(), testutil.GetTestCachePrefix(), ) } func newSysProduct() *SysProduct { ts := time.Now().Unix() return &SysProduct{ Code: testutil.UniqueId(), Name: "integration-product", AppKey: testutil.UniqueId(), AppSecret: "app-secret-" + testutil.UniqueId(), Remark: "remark", Status: 1, CreateTime: ts, UpdateTime: ts, } } // TC-0423: 正常分页 func TestSysProductModel_Integration(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) t.Run("TableName", func(t *testing.T) { require.Equal(t, "`sys_product`", m.TableName()) }) t.Run("FindList_pagination", func(t *testing.T) { var beforeTotal int64 _, beforeTotal, _ = m.FindList(ctx, 1, 1) const n = 5 ids := make([]int64, 0, n) for i := 0; i < n; i++ { p := newSysProduct() p.Name = fmt.Sprintf("page-item-%d", i) res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) ids = append(ids, id) } defer func() { testutil.CleanTable(ctx, conn, "`sys_product`", ids...) }() expectedTotal := beforeTotal + int64(n) _, total, err := m.FindList(ctx, 1, 2) require.NoError(t, err) require.Equal(t, expectedTotal, total) var pageSize int64 = 2 fullPages := expectedTotal / pageSize lastPageSize := expectedTotal % pageSize if lastPageSize > 0 { lastList, _, err := m.FindList(ctx, fullPages+1, pageSize) require.NoError(t, err) require.Len(t, lastList, int(lastPageSize)) } }) t.Run("CRUD_cycle", func(t *testing.T) { p := newSysProduct() p.Name = "crud-name" res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, "`sys_product`", id) got, err := m.FindOne(ctx, id) require.NoError(t, err) require.Equal(t, id, got.Id) require.Equal(t, p.Code, got.Code) require.Equal(t, p.Name, got.Name) require.Equal(t, p.AppKey, got.AppKey) require.Equal(t, p.AppSecret, got.AppSecret) require.Equal(t, p.Remark, got.Remark) require.Equal(t, p.Status, got.Status) ts2 := time.Now().Unix() got.Name = "crud-name-updated" got.Remark = "updated-remark" got.Status = 2 got.UpdateTime = ts2 require.NoError(t, m.Update(ctx, got)) after, err := m.FindOne(ctx, id) require.NoError(t, err) require.Equal(t, "crud-name-updated", after.Name) require.Equal(t, "updated-remark", after.Remark) require.Equal(t, int64(2), after.Status) require.Equal(t, ts2, after.UpdateTime) require.NoError(t, m.Delete(ctx, id)) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) }) t.Run("FindOneByAppKey_found_and_notFound", func(t *testing.T) { p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, "`sys_product`", id) got, err := m.FindOneByAppKey(ctx, p.AppKey) require.NoError(t, err) require.Equal(t, p.AppKey, got.AppKey) require.Equal(t, id, got.Id) _, err = m.FindOneByAppKey(ctx, "nonexistent-appkey-"+testutil.UniqueId()) require.ErrorIs(t, err, ErrNotFound) }) t.Run("FindOneByCode_found_and_notFound", func(t *testing.T) { p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, "`sys_product`", id) got, err := m.FindOneByCode(ctx, p.Code) require.NoError(t, err) require.Equal(t, p.Code, got.Code) require.Equal(t, id, got.Id) _, err = m.FindOneByCode(ctx, "nonexistent-code-"+testutil.UniqueId()) require.ErrorIs(t, err, ErrNotFound) }) t.Run("BatchInsert_and_BatchDelete", func(t *testing.T) { items := []*SysProduct{newSysProduct(), newSysProduct(), newSysProduct()} for i := range items { items[i].Name = fmt.Sprintf("batch-%d", i) } require.NoError(t, m.BatchInsert(ctx, items)) var ids []int64 for _, it := range items { row, err := m.FindOneByCode(ctx, it.Code) require.NoError(t, err) ids = append(ids, row.Id) } defer testutil.CleanTable(ctx, conn, "`sys_product`", ids...) require.NoError(t, m.BatchDelete(ctx, ids)) for _, id := range ids { _, err := m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } }) t.Run("TransactCtx_commit", func(t *testing.T) { p := newSysProduct() p.Name = "tx-commit" err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.InsertWithTx(c, session, p) return err }) require.NoError(t, err) row, err := m.FindOneByCode(ctx, p.Code) require.NoError(t, err) defer testutil.CleanTable(ctx, conn, "`sys_product`", row.Id) require.Equal(t, p.Code, row.Code) require.Equal(t, "tx-commit", row.Name) }) t.Run("TransactCtx_rollback", func(t *testing.T) { p := newSysProduct() p.Name = "tx-rollback" err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { if _, err := m.InsertWithTx(c, session, p); err != nil { return err } return fmt.Errorf("force rollback") }) require.Error(t, err) _, err = m.FindOneByCode(ctx, p.Code) require.ErrorIs(t, err, ErrNotFound) }) t.Run("Insert_duplicateCode", func(t *testing.T) { code := testutil.UniqueId() ts := time.Now().Unix() p1 := &SysProduct{ Code: code, Name: "dup-a", AppKey: testutil.UniqueId(), AppSecret: "s1", Remark: "", Status: 1, CreateTime: ts, UpdateTime: ts, } res, err := m.Insert(ctx, p1) require.NoError(t, err) id1, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, "`sys_product`", id1) p2 := &SysProduct{ Code: code, Name: "dup-b", AppKey: testutil.UniqueId(), AppSecret: "s2", Remark: "", Status: 1, CreateTime: ts, UpdateTime: ts, } _, err = m.Insert(ctx, p2) require.Error(t, err) var me *mysql.MySQLError require.True(t, errors.As(err, &me)) require.Equal(t, uint16(1062), me.Number) }) } // TC-0319: 记录不存在 func TestSysProductModel_FindOne_NotFound(t *testing.T) { m := newTestModel(t) _, err := m.FindOne(context.Background(), 999999999999) require.ErrorIs(t, err, ErrNotFound) } // TC-0326: 记录不存在 func TestSysProductModel_Update_NotFound(t *testing.T) { m := newTestModel(t) err := m.Update(context.Background(), &SysProduct{ Id: 999999999999, Code: "x", Name: "n", AppKey: "k", AppSecret: "s", Status: 1, CreateTime: time.Now().Unix(), UpdateTime: time.Now().Unix(), }) require.ErrorIs(t, err, ErrNotFound) } // TC-0329: 记录不存在 func TestSysProductModel_Delete_NotFound(t *testing.T) { m := newTestModel(t) err := m.Delete(context.Background(), 999999999999) require.ErrorIs(t, err, ErrNotFound) } // TC-0334: 空列表 func TestSysProductModel_BatchInsert_Empty(t *testing.T) { m := newTestModel(t) require.NoError(t, m.BatchInsert(context.Background(), nil)) require.NoError(t, m.BatchInsert(context.Background(), []*SysProduct{})) } // TC-0353: 空ids func TestSysProductModel_BatchDelete_Empty(t *testing.T) { m := newTestModel(t) require.NoError(t, m.BatchDelete(context.Background(), nil)) require.NoError(t, m.BatchDelete(context.Background(), []int64{})) } // TC-0327: 事务内更新 func TestSysProductModel_UpdateWithTx(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, m.TableName(), id) err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { p.Id = id p.Name = "tx_updated_name" p.UpdateTime = time.Now().Unix() return m.UpdateWithTx(c, session, p) }) require.NoError(t, err) got, err := m.FindOne(ctx, id) require.NoError(t, err) require.Equal(t, "tx_updated_name", got.Name) } // TC-0330: 事务内删除 func TestSysProductModel_DeleteWithTx(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, err := res.LastInsertId() require.NoError(t, err) defer testutil.CleanTable(ctx, conn, m.TableName(), 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-0335: 单条记录 func TestSysProductModel_BatchInsert_Single(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p := newSysProduct() require.NoError(t, m.BatchInsert(ctx, []*SysProduct{p})) found, err := m.FindOneByCode(ctx, p.Code) require.NoError(t, err) defer testutil.CleanTable(ctx, conn, m.TableName(), found.Id) require.Equal(t, p.Code, found.Code) } // TC-0338: 唯一索引冲突 func TestSysProductModel_BatchInsert_UniqueConflict(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) code := testutil.UniqueId() ts := time.Now().Unix() list := []*SysProduct{ {Code: code, Name: "a", AppKey: testutil.UniqueId(), AppSecret: "s1", Status: 1, CreateTime: ts, UpdateTime: ts}, {Code: code, Name: "b", AppKey: testutil.UniqueId(), AppSecret: "s2", Status: 1, CreateTime: ts, UpdateTime: ts}, } err := m.BatchInsert(ctx, list) require.Error(t, err) t.Cleanup(func() { if found, e := m.FindOneByCode(ctx, code); e == nil { testutil.CleanTable(ctx, conn, m.TableName(), found.Id) } }) var me *mysql.MySQLError require.True(t, errors.As(err, &me)) require.Equal(t, uint16(1062), me.Number) } // TC-0343: 空列表 func TestSysProductModel_BatchUpdate_Empty(t *testing.T) { m := newTestModel(t) require.NoError(t, m.BatchUpdate(context.Background(), nil)) require.NoError(t, m.BatchUpdate(context.Background(), []*SysProduct{})) } // TC-0345: 多条记录(3条) func TestSysProductModel_BatchUpdate_Multi(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p1 := newSysProduct() p2 := newSysProduct() r1, err := m.Insert(ctx, p1) require.NoError(t, err) id1, _ := r1.LastInsertId() r2, err := m.Insert(ctx, p2) require.NoError(t, err) id2, _ := r2.LastInsertId() defer testutil.CleanTable(ctx, conn, m.TableName(), id1, id2) now := time.Now().Unix() upd := []*SysProduct{ {Id: id1, Code: p1.Code, Name: "upd1", AppKey: p1.AppKey, AppSecret: p1.AppSecret, Remark: "r1", Status: 1, CreateTime: p1.CreateTime, UpdateTime: now}, {Id: id2, Code: p2.Code, Name: "upd2", AppKey: p2.AppKey, AppSecret: p2.AppSecret, Remark: "r2", Status: 2, CreateTime: p2.CreateTime, UpdateTime: now}, } require.NoError(t, m.BatchUpdate(ctx, upd)) g1, err := m.FindOne(ctx, id1) require.NoError(t, err) require.Equal(t, "upd1", g1.Name) g2, err := m.FindOne(ctx, id2) require.NoError(t, err) require.Equal(t, "upd2", g2.Name) require.Equal(t, int64(2), g2.Status) } // TC-0341: 正常多条 func TestSysProductModel_BatchInsertWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p1 := newSysProduct() p2 := newSysProduct() err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.BatchInsertWithTx(c, session, []*SysProduct{p1, p2}) }) require.NoError(t, err) f1, err := m.FindOneByCode(ctx, p1.Code) require.NoError(t, err) f2, err := m.FindOneByCode(ctx, p2.Code) require.NoError(t, err) defer testutil.CleanTable(ctx, conn, m.TableName(), f1.Id, f2.Id) require.Equal(t, p1.Code, f1.Code) require.Equal(t, p2.Code, f2.Code) } // TC-0340: 空列表 func TestSysProductModel_BatchInsertWithTx_Empty(t *testing.T) { m := newTestModel(t) 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 TestSysProductModel_BatchInsertWithTx_Rollback(t *testing.T) { ctx := context.Background() m := newTestModel(t) p := newSysProduct() err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { if e := m.BatchInsertWithTx(c, session, []*SysProduct{p}); e != nil { return e } return fmt.Errorf("force rollback") }) require.Error(t, err) _, err = m.FindOneByCode(ctx, p.Code) require.ErrorIs(t, err, ErrNotFound) } // TC-0349: 正常多条 func TestSysProductModel_BatchUpdateWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p1 := newSysProduct() p2 := newSysProduct() r1, err := m.Insert(ctx, p1) require.NoError(t, err) id1, _ := r1.LastInsertId() r2, err := m.Insert(ctx, p2) require.NoError(t, err) id2, _ := r2.LastInsertId() defer testutil.CleanTable(ctx, conn, m.TableName(), id1, id2) now := time.Now().Unix() err = m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { return m.BatchUpdateWithTx(c, session, []*SysProduct{ {Id: id1, Code: p1.Code, Name: "tx_upd1", AppKey: p1.AppKey, AppSecret: p1.AppSecret, Status: 1, CreateTime: p1.CreateTime, UpdateTime: now}, {Id: id2, Code: p2.Code, Name: "tx_upd2", AppKey: p2.AppKey, AppSecret: p2.AppSecret, Status: 1, CreateTime: p2.CreateTime, UpdateTime: now}, }) }) require.NoError(t, err) g1, err := m.FindOne(ctx, id1) require.NoError(t, err) require.Equal(t, "tx_upd1", g1.Name) g2, err := m.FindOne(ctx, id2) require.NoError(t, err) require.Equal(t, "tx_upd2", g2.Name) } // TC-0348: 空列表 func TestSysProductModel_BatchUpdateWithTx_Empty(t *testing.T) { m := newTestModel(t) 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 TestSysProductModel_BatchDelete_Single(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, _ := res.LastInsertId() defer testutil.CleanTable(ctx, conn, m.TableName(), id) require.NoError(t, m.BatchDelete(ctx, []int64{id})) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } // TC-0356: 包含不存在id func TestSysProductModel_BatchDelete_ContainsNonExist(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p := newSysProduct() res, err := m.Insert(ctx, p) require.NoError(t, err) id, _ := res.LastInsertId() defer testutil.CleanTable(ctx, conn, m.TableName(), id) require.NoError(t, m.BatchDelete(ctx, []int64{id, 999999999})) _, err = m.FindOne(ctx, id) require.ErrorIs(t, err, ErrNotFound) } // TC-0358: 正常多条 func TestSysProductModel_BatchDeleteWithTx_Normal(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) p1 := newSysProduct() p2 := newSysProduct() r1, err := m.Insert(ctx, p1) require.NoError(t, err) id1, _ := r1.LastInsertId() r2, err := m.Insert(ctx, p2) require.NoError(t, err) id2, _ := r2.LastInsertId() defer testutil.CleanTable(ctx, conn, m.TableName(), 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 TestSysProductModel_BatchDeleteWithTx_Empty(t *testing.T) { m := newTestModel(t) 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 TestSysProductModel_FindOneWithTx_InsertThenFind(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) now := time.Now().Unix() var foundInTx *SysProduct var insertedId int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysProduct{ Code: "ftx_code_" + testutil.UniqueId(), Name: "ftx_name_" + testutil.UniqueId(), AppKey: "ftx_ak_" + testutil.UniqueId(), AppSecret: "ftx_sec_" + testutil.UniqueId(), Remark: "", Status: 1, CreateTime: now, UpdateTime: now, }) if err != nil { return err } insertedId, _ = res.LastInsertId() foundInTx, err = m.FindOneWithTx(c, session, insertedId) return err }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, m.TableName(), insertedId) }) require.NotNil(t, foundInTx) assert.Equal(t, insertedId, foundInTx.Id) } // TC-0322: 事务内记录不存在 func TestSysProductModel_FindOneWithTx_NotFound(t *testing.T) { ctx := context.Background() m := newTestModel(t) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.FindOneWithTx(c, session, 999999999999) return err }) require.ErrorIs(t, err, ErrNotFound) } // TC-0365: FindOneByAppKeyWithTx func TestSysProductModel_FindOneByAppKeyWithTx_InsertThenFind(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) appKey := "ftx_ak_" + testutil.UniqueId() code := "ftx_code_" + testutil.UniqueId() now := time.Now().Unix() var foundByKey *SysProduct var insertedId int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysProduct{ Code: code, Name: "ftx_name_" + testutil.UniqueId(), AppKey: appKey, AppSecret: "ftx_sec_" + testutil.UniqueId(), Remark: "", Status: 1, CreateTime: now, UpdateTime: now, }) if err != nil { return err } insertedId, _ = res.LastInsertId() foundByKey, err = m.FindOneByAppKeyWithTx(c, session, appKey) return err }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, m.TableName(), insertedId) }) require.NotNil(t, foundByKey) assert.Equal(t, insertedId, foundByKey.Id) assert.Equal(t, appKey, foundByKey.AppKey) } // TC-0366: FindOneByAppKeyWithTx func TestSysProductModel_FindOneByAppKeyWithTx_NotFound(t *testing.T) { ctx := context.Background() m := newTestModel(t) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.FindOneByAppKeyWithTx(c, session, "notexist_appkey_"+testutil.UniqueId()) return err }) require.ErrorIs(t, err, ErrNotFound) } // TC-0369: FindOneByCodeWithTx func TestSysProductModel_FindOneByCodeWithTx_InsertThenFind(t *testing.T) { ctx := context.Background() conn := testutil.GetTestSqlConn() m := newTestModel(t) code := "ftx_code_" + testutil.UniqueId() now := time.Now().Unix() var foundByCode *SysProduct var insertedId int64 err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { res, err := m.InsertWithTx(c, session, &SysProduct{ Code: code, Name: "ftx_name_" + testutil.UniqueId(), AppKey: "ftx_ak_" + testutil.UniqueId(), AppSecret: "ftx_sec_" + testutil.UniqueId(), Remark: "", Status: 1, CreateTime: now, UpdateTime: now, }) if err != nil { return err } insertedId, _ = res.LastInsertId() foundByCode, err = m.FindOneByCodeWithTx(c, session, code) return err }) require.NoError(t, err) t.Cleanup(func() { testutil.CleanTable(ctx, conn, m.TableName(), insertedId) }) require.NotNil(t, foundByCode) assert.Equal(t, insertedId, foundByCode.Id) assert.Equal(t, code, foundByCode.Code) } // TC-0370: FindOneByCodeWithTx func TestSysProductModel_FindOneByCodeWithTx_NotFound(t *testing.T) { ctx := context.Background() m := newTestModel(t) err := m.TransactCtx(ctx, func(c context.Context, session sqlx.Session) error { _, err := m.FindOneByCodeWithTx(c, session, "notexist_code_"+testutil.UniqueId()) return err }) require.ErrorIs(t, err, ErrNotFound) } // TC-0404: 多唯一索引前缀(SysProduct) func TestSysProductModel_CachePrefix_MultiUniqueIndex(t *testing.T) { oldId := cacheSysProductIdPrefix oldAppKey := cacheSysProductAppKeyPrefix oldCode := cacheSysProductCodePrefix defer func() { cacheSysProductIdPrefix = oldId cacheSysProductAppKeyPrefix = oldAppKey cacheSysProductCodePrefix = oldCode }() _ = newSysProductModel(testutil.GetTestSqlConn(), testutil.GetTestCacheConf(), "test") assert.True(t, strings.HasPrefix(cacheSysProductIdPrefix, "test:")) assert.True(t, strings.HasPrefix(cacheSysProductAppKeyPrefix, "test:")) assert.True(t, strings.HasPrefix(cacheSysProductCodePrefix, "test:")) }