package tests import ( "bytes" "context" "encoding/json" "fmt" "net/http" "photography-backend/internal/config" "photography-backend/internal/svc" "photography-backend/internal/types" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/zeromicro/go-zero/core/conf" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // IntegrationTestSuite 集成测试套件 type IntegrationTestSuite struct { suite.Suite svcCtx *svc.ServiceContext cfg config.Config db *gorm.DB authToken string userID int64 photoID int64 categoryID int64 } // SetupSuite 设置测试套件 func (suite *IntegrationTestSuite) SetupSuite() { // 加载配置 var cfg config.Config conf.MustLoad("../etc/photography-api.yaml", &cfg) // 使用内存数据库 cfg.Database.Driver = "sqlite" cfg.Database.FilePath = ":memory:" // 创建数据库连接 db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) suite.Require().NoError(err) suite.db = db suite.cfg = cfg // 创建服务上下文 suite.svcCtx = svc.NewServiceContext(cfg) // 初始化数据库表 suite.initDatabase() // 创建测试数据 suite.seedTestData() } // TearDownSuite 清理测试套件 func (suite *IntegrationTestSuite) TearDownSuite() { if suite.db != nil { sqlDB, _ := suite.db.DB() sqlDB.Close() } } // initDatabase 初始化数据库表 func (suite *IntegrationTestSuite) initDatabase() { // 这里应该运行迁移或创建表 // 简化示例,实际应该使用迁移系统 // 创建用户表 err := suite.db.Exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, avatar VARCHAR(255), status INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `).Error suite.Require().NoError(err) // 创建分类表 err = suite.db.Exec(` CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(100) NOT NULL, description TEXT, parent_id INTEGER, sort_order INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `).Error suite.Require().NoError(err) // 创建照片表 err = suite.db.Exec(` CREATE TABLE IF NOT EXISTS photos ( id INTEGER PRIMARY KEY AUTOINCREMENT, title VARCHAR(255) NOT NULL, description TEXT, file_path VARCHAR(500) NOT NULL, thumbnail_path VARCHAR(500), category_id INTEGER, user_id INTEGER NOT NULL, status INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories(id), FOREIGN KEY (user_id) REFERENCES users(id) ) `).Error suite.Require().NoError(err) } // seedTestData 创建测试数据 func (suite *IntegrationTestSuite) seedTestData() { // 创建测试用户 err := suite.db.Exec(` INSERT INTO users (username, email, password_hash, status) VALUES ('testuser', 'test@example.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 1) `).Error suite.Require().NoError(err) // 获取用户ID var user struct { ID int64 `gorm:"column:id"` } err = suite.db.Table("users").Where("username = ?", "testuser").First(&user).Error suite.Require().NoError(err) suite.userID = user.ID // 创建测试分类 err = suite.db.Exec(` INSERT INTO categories (name, description, is_active) VALUES ('测试分类', '这是一个测试分类', 1) `).Error suite.Require().NoError(err) // 获取分类ID var category struct { ID int64 `gorm:"column:id"` } err = suite.db.Table("categories").Where("name = ?", "测试分类").First(&category).Error suite.Require().NoError(err) suite.categoryID = category.ID } // TestCompleteWorkflow 完整工作流程测试 func (suite *IntegrationTestSuite) TestCompleteWorkflow() { // 1. 用户注册 suite.testUserRegistration() // 2. 用户登录 suite.testUserLogin() // 3. 分类管理 suite.testCategoryManagement() // 4. 照片管理 suite.testPhotoManagement() // 5. 权限验证 suite.testPermissionValidation() // 6. 数据关联性测试 suite.testDataRelationships() } // testUserRegistration 用户注册测试 func (suite *IntegrationTestSuite) testUserRegistration() { // 测试正常注册 registerData := map[string]interface{}{ "username": "newuser", "email": "newuser@example.com", "password": "newpassword123", } resp := suite.makeRequest("POST", "/api/v1/auth/register", registerData, "") suite.Equal(200, resp.Code) // 测试重复用户名 resp = suite.makeRequest("POST", "/api/v1/auth/register", registerData, "") suite.NotEqual(200, resp.Code) // 测试无效邮箱 invalidData := map[string]interface{}{ "username": "testuser2", "email": "invalid-email", "password": "password123", } resp = suite.makeRequest("POST", "/api/v1/auth/register", invalidData, "") suite.NotEqual(200, resp.Code) } // testUserLogin 用户登录测试 func (suite *IntegrationTestSuite) testUserLogin() { // 测试正常登录 loginData := map[string]interface{}{ "username": "testuser", "password": "password", } resp := suite.makeRequest("POST", "/api/v1/auth/login", loginData, "") suite.Equal(200, resp.Code) var loginResp types.LoginResponse err := json.Unmarshal(resp.Body, &loginResp) suite.Require().NoError(err) suite.authToken = loginResp.Data.Token suite.NotEmpty(suite.authToken) // 测试无效凭证 invalidLogin := map[string]interface{}{ "username": "testuser", "password": "wrongpassword", } resp = suite.makeRequest("POST", "/api/v1/auth/login", invalidLogin, "") suite.NotEqual(200, resp.Code) } // testCategoryManagement 分类管理测试 func (suite *IntegrationTestSuite) testCategoryManagement() { // 测试创建分类 categoryData := map[string]interface{}{ "name": "新分类", "description": "这是一个新的分类", "parent_id": suite.categoryID, } resp := suite.makeRequest("POST", "/api/v1/categories", categoryData, suite.authToken) suite.Equal(200, resp.Code) var createResp types.CreateCategoryResponse err := json.Unmarshal(resp.Body, &createResp) suite.Require().NoError(err) newCategoryID := createResp.Data.ID // 测试获取分类列表 resp = suite.makeRequest("GET", "/api/v1/categories", nil, suite.authToken) suite.Equal(200, resp.Code) var listResp types.GetCategoryListResponse err = json.Unmarshal(resp.Body, &listResp) suite.Require().NoError(err) suite.GreaterOrEqual(len(listResp.Data), 2) // 测试更新分类 updateData := map[string]interface{}{ "name": "更新的分类", "description": "这是一个更新的分类", } resp = suite.makeRequest("PUT", fmt.Sprintf("/api/v1/categories/%d", newCategoryID), updateData, suite.authToken) suite.Equal(200, resp.Code) // 测试删除分类 resp = suite.makeRequest("DELETE", fmt.Sprintf("/api/v1/categories/%d", newCategoryID), nil, suite.authToken) suite.Equal(200, resp.Code) } // testPhotoManagement 照片管理测试 func (suite *IntegrationTestSuite) testPhotoManagement() { // 测试创建照片记录(简化版,不包含实际文件上传) photoData := map[string]interface{}{ "title": "测试照片", "description": "这是一个测试照片", "file_path": "/uploads/test.jpg", "category_id": suite.categoryID, } // 这里应该测试实际的文件上传,简化为直接插入数据库 err := suite.db.Exec(` INSERT INTO photos (title, description, file_path, category_id, user_id) VALUES (?, ?, ?, ?, ?) `, "测试照片", "这是一个测试照片", "/uploads/test.jpg", suite.categoryID, suite.userID).Error suite.Require().NoError(err) // 获取照片ID var photo struct { ID int64 `gorm:"column:id"` } err = suite.db.Table("photos").Where("title = ?", "测试照片").First(&photo).Error suite.Require().NoError(err) suite.photoID = photo.ID // 测试获取照片列表 resp := suite.makeRequest("GET", "/api/v1/photos", nil, suite.authToken) suite.Equal(200, resp.Code) var listResp types.GetPhotoListResponse err = json.Unmarshal(resp.Body, &listResp) suite.Require().NoError(err) suite.GreaterOrEqual(len(listResp.Data), 1) // 测试获取照片详情 resp = suite.makeRequest("GET", fmt.Sprintf("/api/v1/photos/%d", suite.photoID), nil, suite.authToken) suite.Equal(200, resp.Code) // 测试更新照片 updateData := map[string]interface{}{ "title": "更新的照片", "description": "这是一个更新的照片", } resp = suite.makeRequest("PUT", fmt.Sprintf("/api/v1/photos/%d", suite.photoID), updateData, suite.authToken) suite.Equal(200, resp.Code) } // testPermissionValidation 权限验证测试 func (suite *IntegrationTestSuite) testPermissionValidation() { // 测试未认证访问 resp := suite.makeRequest("GET", "/api/v1/photos", nil, "") suite.Equal(401, resp.Code) // 测试无效token resp = suite.makeRequest("GET", "/api/v1/photos", nil, "invalid_token") suite.Equal(401, resp.Code) // 测试权限不足(尝试访问其他用户的照片) // 这里需要创建另一个用户的照片进行测试 } // testDataRelationships 数据关联性测试 func (suite *IntegrationTestSuite) testDataRelationships() { // 测试分类与照片的关联 var count int64 err := suite.db.Table("photos").Where("category_id = ?", suite.categoryID).Count(&count).Error suite.Require().NoError(err) suite.GreaterOrEqual(count, int64(1)) // 测试用户与照片的关联 err = suite.db.Table("photos").Where("user_id = ?", suite.userID).Count(&count).Error suite.Require().NoError(err) suite.GreaterOrEqual(count, int64(1)) // 测试级联删除(如果删除分类,照片的category_id应该被处理) // 这里可以测试数据库约束和业务逻辑 } // makeRequest 发送HTTP请求的辅助方法 func (suite *IntegrationTestSuite) makeRequest(method, path string, data interface{}, token string) *TestResponse { var body []byte var err error if data != nil { body, err = json.Marshal(data) suite.Require().NoError(err) } req, err := http.NewRequest(method, path, bytes.NewReader(body)) suite.Require().NoError(err) req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Bearer "+token) } // 这里应该实际调用API服务 // 简化示例,返回模拟响应 return &TestResponse{ Code: 200, Body: []byte(`{"code": 200, "message": "success"}`), } } // TestResponse 测试响应结构 type TestResponse struct { Code int Body []byte } // TestDataConsistency 数据一致性测试 func (suite *IntegrationTestSuite) TestDataConsistency() { ctx := context.Background() // 测试事务操作 tx := suite.db.Begin() // 创建用户 err := tx.Exec(` INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?) `, "txuser", "txuser@example.com", "hashedpassword").Error suite.Require().NoError(err) // 创建分类 err = tx.Exec(` INSERT INTO categories (name, description) VALUES (?, ?) `, "事务分类", "事务测试分类").Error suite.Require().NoError(err) // 回滚事务 tx.Rollback() // 验证数据未被插入 var userCount int64 err = suite.db.WithContext(ctx).Table("users").Where("username = ?", "txuser").Count(&userCount).Error suite.Require().NoError(err) suite.Equal(int64(0), userCount) var categoryCount int64 err = suite.db.WithContext(ctx).Table("categories").Where("name = ?", "事务分类").Count(&categoryCount).Error suite.Require().NoError(err) suite.Equal(int64(0), categoryCount) } // TestConcurrentOperations 并发操作测试 func (suite *IntegrationTestSuite) TestConcurrentOperations() { concurrency := 10 done := make(chan bool, concurrency) // 并发创建分类 for i := 0; i < concurrency; i++ { go func(index int) { defer func() { done <- true }() err := suite.db.Exec(` INSERT INTO categories (name, description) VALUES (?, ?) `, fmt.Sprintf("并发分类_%d", index), fmt.Sprintf("并发测试分类_%d", index)).Error suite.Require().NoError(err) }(i) } // 等待所有操作完成 for i := 0; i < concurrency; i++ { <-done } // 验证数据一致性 var count int64 err := suite.db.Table("categories").Where("name LIKE ?", "并发分类_%").Count(&count).Error suite.Require().NoError(err) suite.Equal(int64(concurrency), count) } // TestCacheOperations 缓存操作测试 func (suite *IntegrationTestSuite) TestCacheOperations() { // 如果项目使用Redis缓存,这里测试缓存操作 // 简化示例,测试内存缓存 cache := make(map[string]interface{}) // 测试缓存设置 cache["test_key"] = "test_value" suite.Equal("test_value", cache["test_key"]) // 测试缓存过期(简化) delete(cache, "test_key") _, exists := cache["test_key"] suite.False(exists) } // TestPerformanceWithLoad 负载性能测试 func (suite *IntegrationTestSuite) TestPerformanceWithLoad() { // 创建大量测试数据 batchSize := 1000 start := time.Now() for i := 0; i < batchSize; i++ { err := suite.db.Exec(` INSERT INTO categories (name, description) VALUES (?, ?) `, fmt.Sprintf("性能测试分类_%d", i), fmt.Sprintf("性能测试描述_%d", i)).Error suite.Require().NoError(err) } insertDuration := time.Since(start) // 测试查询性能 start = time.Now() var categories []struct { ID int64 `gorm:"column:id"` Name string `gorm:"column:name"` } err := suite.db.Table("categories").Where("name LIKE ?", "性能测试分类_%").Find(&categories).Error suite.Require().NoError(err) queryDuration := time.Since(start) suite.Equal(batchSize, len(categories)) // 记录性能指标 suite.T().Logf("Insert %d records took: %v", batchSize, insertDuration) suite.T().Logf("Query %d records took: %v", batchSize, queryDuration) // 性能断言 suite.Less(insertDuration, 5*time.Second) suite.Less(queryDuration, 1*time.Second) } // TestErrorRecovery 错误恢复测试 func (suite *IntegrationTestSuite) TestErrorRecovery() { // 测试数据库连接错误恢复 // 这里应该测试数据库连接中断后的恢复机制 // 测试事务失败恢复 tx := suite.db.Begin() // 故意创建一个会失败的操作 err := tx.Exec(` INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?) `, "testuser", "test@example.com", "password").Error // 重复的用户名 if err != nil { tx.Rollback() suite.T().Log("Transaction properly rolled back after error") } else { tx.Commit() } // 验证数据库状态仍然正常 var count int64 err = suite.db.Table("users").Count(&count).Error suite.Require().NoError(err) suite.GreaterOrEqual(count, int64(1)) } // TestIntegrationTestSuite 运行集成测试套件 func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) }