package integration import ( "context" "errors" "fmt" "net/http" "net/http/httptest" "sync/atomic" "testing" _ "modernc.org/sqlite" // 纯 Go SQLite,注册 "sqlite" 驱动 gormsqlite "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/user-management-system/internal/domain" "github.com/user-management-system/internal/repository" ) var integDBCounter int64 func setupTestDB(t *testing.T) *gorm.DB { t.Helper() id := atomic.AddInt64(&integDBCounter, 1) dsn := fmt.Sprintf("file:integtestdb%d?mode=memory&cache=private", id) db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{ DriverName: "sqlite", DSN: dsn, }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("打开测试数据库失败: %v", err) } if err := db.AutoMigrate(&domain.User{}, &domain.Role{}, &domain.Permission{}, &domain.Device{}); err != nil { t.Fatalf("数据库迁移失败: %v", err) } return db } func cleanupTestDB(t *testing.T, db *gorm.DB) { t.Helper() sqlDB, _ := db.DB() sqlDB.Close() } // setupTestServer 测试服务器 func setupTestServer(t *testing.T) *httptest.Server { t.Helper() mux := http.NewServeMux() mux.HandleFunc("/api/v1/auth/register", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"code":0,"message":"success","data":{"user_id":1}}`)) }) mux.HandleFunc("/api/v1/auth/login", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"code":0,"message":"success","data":{"access_token":"test-token"}}`)) }) mux.HandleFunc("/api/v1/users/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write([]byte(`{"code":0,"message":"success","data":{"id":1,"username":"testuser"}}`)) }) return httptest.NewServer(mux) } // TestDatabaseIntegration 测试数据库集成 func TestDatabaseIntegration(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := repository.NewUserRepository(db) ctx := context.Background() t.Run("CreateUser", func(t *testing.T) { user := &domain.User{ Phone: domain.StrPtr("13800138000"), Username: "integrationuser", Password: "hashedpassword", Status: domain.UserStatusActive, } if err := repo.Create(ctx, user); err != nil { t.Fatalf("创建用户失败: %v", err) } if user.ID == 0 { t.Error("用户ID不应为0") } }) t.Run("FindUser", func(t *testing.T) { user, err := repo.GetByUsername(ctx, "integrationuser") if err != nil { t.Fatalf("查询用户失败: %v", err) } if domain.DerefStr(user.Phone) != "13800138000" { t.Errorf("Phone = %v, want 13800138000", domain.DerefStr(user.Phone)) } }) t.Run("UpdateUser", func(t *testing.T) { user, _ := repo.GetByUsername(ctx, "integrationuser") user.Nickname = "已更新" if err := repo.Update(ctx, user); err != nil { t.Fatalf("更新用户失败: %v", err) } found, _ := repo.GetByID(ctx, user.ID) if found.Nickname != "已更新" { t.Errorf("Nickname = %v, want 已更新", found.Nickname) } }) t.Run("DeleteUser", func(t *testing.T) { user, _ := repo.GetByUsername(ctx, "integrationuser") if err := repo.Delete(ctx, user.ID); err != nil { t.Fatalf("删除用户失败: %v", err) } _, err := repo.GetByUsername(ctx, "integrationuser") if err == nil { t.Error("删除后查询应返回错误") } }) } // TestTransactionIntegration 测试事务集成 func TestTransactionIntegration(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) t.Run("TransactionRollback", func(t *testing.T) { err := db.Transaction(func(tx *gorm.DB) error { user := &domain.User{ Phone: domain.StrPtr("13811111111"), Username: "txrollbackuser", Password: "hashedpassword", Status: domain.UserStatusActive, } if err := tx.Create(user).Error; err != nil { return err } return errors.New("模拟错误,触发回滚") }) if err == nil { t.Error("事务应该失败") } var count int64 db.Model(&domain.User{}).Where("username = ?", "txrollbackuser").Count(&count) if count > 0 { t.Error("事务回滚后用户不应存在") } }) t.Run("TransactionCommit", func(t *testing.T) { err := db.Transaction(func(tx *gorm.DB) error { user := &domain.User{ Phone: domain.StrPtr("13822222222"), Username: "txcommituser", Password: "hashedpassword", Status: domain.UserStatusActive, } return tx.Create(user).Error }) if err != nil { t.Fatalf("事务失败: %v", err) } var count int64 db.Model(&domain.User{}).Where("username = ?", "txcommituser").Count(&count) if count != 1 { t.Error("事务提交后用户应存在") } }) } // TestAPIIntegration 测试HTTP API集成 func TestAPIIntegration(t *testing.T) { server := setupTestServer(t) defer server.Close() t.Run("RegisterEndpoint", func(t *testing.T) { resp, err := http.Post(server.URL+"/api/v1/auth/register", "application/json", nil) if err != nil { t.Fatalf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("StatusCode = %d, want 200", resp.StatusCode) } }) t.Run("LoginEndpoint", func(t *testing.T) { resp, err := http.Post(server.URL+"/api/v1/auth/login", "application/json", nil) if err != nil { t.Fatalf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("StatusCode = %d, want 200", resp.StatusCode) } }) t.Run("GetUserEndpoint", func(t *testing.T) { resp, err := http.Get(server.URL + "/api/v1/users/1") if err != nil { t.Fatalf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("StatusCode = %d, want 200", resp.StatusCode) } }) }