package unit import ( "context" "errors" "testing" "time" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/store/postgres" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" ) // setupTestStore 创建内存数据库用于单元测试 func setupTestStore(t *testing.T) (*postgres.Store, func()) { // 使用 SQLite 内存数据库进行单元测试 db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err, "创建内存数据库失败") // 自动迁移 err = db.AutoMigrate(&model.User{}, &model.Order{}) require.NoError(t, err, "数据库迁移失败") // 创建测试 logger testLogger := zap.NewNop() store := postgres.NewStore(db, testLogger) cleanup := func() { sqlDB, _ := db.DB() if sqlDB != nil { sqlDB.Close() } } return store, cleanup } // TestUserStore 测试用户 Store 层 func TestUserStore(t *testing.T) { store, cleanup := setupTestStore(t) defer cleanup() ctx := context.Background() t.Run("创建用户成功", func(t *testing.T) { user := &model.User{ Username: "testuser", Email: "test@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) assert.NoError(t, err) assert.NotZero(t, user.ID) assert.False(t, user.CreatedAt.IsZero()) assert.False(t, user.UpdatedAt.IsZero()) }) t.Run("创建重复用户名失败", func(t *testing.T) { user1 := &model.User{ Username: "duplicate", Email: "user1@example.com", Password: "password1", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user1) require.NoError(t, err) // 尝试创建相同用户名 user2 := &model.User{ Username: "duplicate", Email: "user2@example.com", Password: "password2", Status: constants.UserStatusActive, } err = store.User.Create(ctx, user2) assert.Error(t, err, "应该返回唯一约束错误") }) t.Run("根据ID查询用户", func(t *testing.T) { user := &model.User{ Username: "findbyid", Email: "findbyid@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) found, err := store.User.GetByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, user.Username, found.Username) assert.Equal(t, user.Email, found.Email) }) t.Run("查询不存在的用户", func(t *testing.T) { _, err := store.User.GetByID(ctx, 99999) assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) }) t.Run("根据用户名查询用户", func(t *testing.T) { user := &model.User{ Username: "findbyname", Email: "findbyname@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) found, err := store.User.GetByUsername(ctx, "findbyname") assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) }) t.Run("更新用户", func(t *testing.T) { user := &model.User{ Username: "updatetest", Email: "update@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) // 更新用户 user.Email = "newemail@example.com" user.Status = constants.UserStatusInactive err = store.User.Update(ctx, user) assert.NoError(t, err) // 验证更新 found, err := store.User.GetByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, "newemail@example.com", found.Email) assert.Equal(t, constants.UserStatusInactive, found.Status) }) t.Run("软删除用户", func(t *testing.T) { user := &model.User{ Username: "deletetest", Email: "delete@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) // 软删除 err = store.User.Delete(ctx, user.ID) assert.NoError(t, err) // 验证已删除 _, err = store.User.GetByID(ctx, user.ID) assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) }) t.Run("分页列表查询", func(t *testing.T) { // 创建10个用户 for i := 1; i <= 10; i++ { user := &model.User{ Username: "listuser" + string(rune('0'+i)), Email: "list" + string(rune('0'+i)) + "@example.com", Password: "password", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) } // 第一页 users, total, err := store.User.List(ctx, 1, 5) assert.NoError(t, err) assert.GreaterOrEqual(t, len(users), 5) assert.GreaterOrEqual(t, total, int64(10)) // 第二页 users2, total2, err := store.User.List(ctx, 2, 5) assert.NoError(t, err) assert.GreaterOrEqual(t, len(users2), 5) assert.Equal(t, total, total2) // 验证不同页的数据不同 if len(users) > 0 && len(users2) > 0 { assert.NotEqual(t, users[0].ID, users2[0].ID) } }) } // TestOrderStore 测试订单 Store 层 func TestOrderStore(t *testing.T) { store, cleanup := setupTestStore(t) defer cleanup() ctx := context.Background() // 创建测试用户 user := &model.User{ Username: "orderuser", Email: "orderuser@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) t.Run("创建订单成功", func(t *testing.T) { order := &model.Order{ OrderID: "ORD-TEST-001", UserID: user.ID, Amount: 10000, Status: constants.OrderStatusPending, Remark: "测试订单", } err := store.Order.Create(ctx, order) assert.NoError(t, err) assert.NotZero(t, order.ID) assert.False(t, order.CreatedAt.IsZero()) }) t.Run("创建重复订单号失败", func(t *testing.T) { order1 := &model.Order{ OrderID: "ORD-DUP-001", UserID: user.ID, Amount: 10000, Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order1) require.NoError(t, err) // 尝试创建相同订单号 order2 := &model.Order{ OrderID: "ORD-DUP-001", UserID: user.ID, Amount: 20000, Status: constants.OrderStatusPending, } err = store.Order.Create(ctx, order2) assert.Error(t, err, "应该返回唯一约束错误") }) t.Run("根据ID查询订单", func(t *testing.T) { order := &model.Order{ OrderID: "ORD-FIND-001", UserID: user.ID, Amount: 20000, Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order) require.NoError(t, err) found, err := store.Order.GetByID(ctx, order.ID) assert.NoError(t, err) assert.Equal(t, order.OrderID, found.OrderID) assert.Equal(t, order.Amount, found.Amount) }) t.Run("根据订单号查询", func(t *testing.T) { order := &model.Order{ OrderID: "ORD-FIND-002", UserID: user.ID, Amount: 30000, Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order) require.NoError(t, err) found, err := store.Order.GetByOrderID(ctx, "ORD-FIND-002") assert.NoError(t, err) assert.Equal(t, order.ID, found.ID) }) t.Run("根据用户ID列表查询", func(t *testing.T) { // 创建多个订单 for i := 1; i <= 5; i++ { order := &model.Order{ OrderID: "ORD-LIST-" + string(rune('0'+i)), UserID: user.ID, Amount: int64(i * 10000), Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order) require.NoError(t, err) } orders, total, err := store.Order.ListByUserID(ctx, user.ID, 1, 10) assert.NoError(t, err) assert.GreaterOrEqual(t, len(orders), 5) assert.GreaterOrEqual(t, total, int64(5)) }) t.Run("更新订单状态", func(t *testing.T) { order := &model.Order{ OrderID: "ORD-UPDATE-001", UserID: user.ID, Amount: 50000, Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order) require.NoError(t, err) // 更新状态 now := time.Now() order.Status = constants.OrderStatusPaid order.PaidAt = &now err = store.Order.Update(ctx, order) assert.NoError(t, err) // 验证更新 found, err := store.Order.GetByID(ctx, order.ID) assert.NoError(t, err) assert.Equal(t, constants.OrderStatusPaid, found.Status) assert.NotNil(t, found.PaidAt) }) t.Run("软删除订单", func(t *testing.T) { order := &model.Order{ OrderID: "ORD-DELETE-001", UserID: user.ID, Amount: 60000, Status: constants.OrderStatusPending, } err := store.Order.Create(ctx, order) require.NoError(t, err) // 软删除 err = store.Order.Delete(ctx, order.ID) assert.NoError(t, err) // 验证已删除 _, err = store.Order.GetByID(ctx, order.ID) assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) }) } // TestStoreTransaction 测试事务功能 func TestStoreTransaction(t *testing.T) { store, cleanup := setupTestStore(t) defer cleanup() ctx := context.Background() t.Run("事务提交成功", func(t *testing.T) { var userID uint var orderID uint err := store.Transaction(ctx, func(tx *postgres.Store) error { // 创建用户 user := &model.User{ Username: "txuser1", Email: "txuser1@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } if err := tx.User.Create(ctx, user); err != nil { return err } userID = user.ID // 创建订单 order := &model.Order{ OrderID: "ORD-TX-001", UserID: user.ID, Amount: 10000, Status: constants.OrderStatusPending, } if err := tx.Order.Create(ctx, order); err != nil { return err } orderID = order.ID return nil }) assert.NoError(t, err) // 验证用户和订单都已创建 user, err := store.User.GetByID(ctx, userID) assert.NoError(t, err) assert.Equal(t, "txuser1", user.Username) order, err := store.Order.GetByID(ctx, orderID) assert.NoError(t, err) assert.Equal(t, "ORD-TX-001", order.OrderID) }) t.Run("事务回滚", func(t *testing.T) { var userID uint err := store.Transaction(ctx, func(tx *postgres.Store) error { // 创建用户 user := &model.User{ Username: "rollbackuser", Email: "rollback@example.com", Password: "hashedpassword", Status: constants.UserStatusActive, } if err := tx.User.Create(ctx, user); err != nil { return err } userID = user.ID // 模拟错误,触发回滚 return errors.New("模拟错误") }) assert.Error(t, err) assert.Equal(t, "模拟错误", err.Error()) // 验证用户未创建(已回滚) _, err = store.User.GetByID(ctx, userID) assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) }) t.Run("嵌套事务回滚", func(t *testing.T) { var user1ID, user2ID uint err := store.Transaction(ctx, func(tx1 *postgres.Store) error { // 外层事务:创建第一个用户 user1 := &model.User{ Username: "nested1", Email: "nested1@example.com", Password: "password", Status: constants.UserStatusActive, } if err := tx1.User.Create(ctx, user1); err != nil { return err } user1ID = user1.ID // 内层事务:创建第二个用户并失败 err := tx1.Transaction(ctx, func(tx2 *postgres.Store) error { user2 := &model.User{ Username: "nested2", Email: "nested2@example.com", Password: "password", Status: constants.UserStatusActive, } if err := tx2.User.Create(ctx, user2); err != nil { return err } user2ID = user2.ID // 内层事务失败 return errors.New("内层事务失败") }) // 内层事务失败导致外层事务也失败 return err }) assert.Error(t, err) // 验证两个用户都未创建 _, err = store.User.GetByID(ctx, user1ID) assert.Error(t, err) _, err = store.User.GetByID(ctx, user2ID) assert.Error(t, err) }) } // TestConcurrentAccess 测试并发访问 func TestConcurrentAccess(t *testing.T) { store, cleanup := setupTestStore(t) defer cleanup() ctx := context.Background() t.Run("并发创建用户", func(t *testing.T) { concurrency := 20 errChan := make(chan error, concurrency) for i := 0; i < concurrency; i++ { go func(index int) { user := &model.User{ Username: "concurrent" + string(rune('A'+index)), Email: "concurrent" + string(rune('A'+index)) + "@example.com", Password: "password", Status: constants.UserStatusActive, } errChan <- store.User.Create(ctx, user) }(i) } // 收集结果 successCount := 0 for i := 0; i < concurrency; i++ { err := <-errChan if err == nil { successCount++ } } assert.Equal(t, concurrency, successCount, "所有并发创建应该成功") }) t.Run("并发读写同一用户", func(t *testing.T) { // 创建测试用户 user := &model.User{ Username: "rwuser", Email: "rwuser@example.com", Password: "password", Status: constants.UserStatusActive, } err := store.User.Create(ctx, user) require.NoError(t, err) concurrency := 10 done := make(chan bool, concurrency*2) // 并发读 for i := 0; i < concurrency; i++ { go func() { _, err := store.User.GetByID(ctx, user.ID) assert.NoError(t, err) done <- true }() } // 并发写 for i := 0; i < concurrency; i++ { go func(index int) { user.Status = constants.UserStatusActive err := store.User.Update(ctx, user) assert.NoError(t, err) done <- true }(i) } // 等待所有操作完成 for i := 0; i < concurrency*2; i++ { <-done } }) }