package integration import ( "bytes" "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "omega-server/local/model" "omega-server/local/service" "testing" "time" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) // IntegrationTestUtils provides utilities for integration testing type IntegrationTestUtils struct { App *fiber.App DB *gorm.DB MembershipService *service.MembershipService } // TestRequest represents an HTTP test request type TestRequest struct { Method string URL string Body interface{} Headers map[string]string } // TestResponse represents an HTTP test response type TestResponse struct { StatusCode int Body string Headers map[string]string } // NewIntegrationTestUtils creates a new integration test utilities instance func NewIntegrationTestUtils(app *fiber.App, db *gorm.DB, membershipService *service.MembershipService) *IntegrationTestUtils { return &IntegrationTestUtils{ App: app, DB: db, MembershipService: membershipService, } } // ExecuteRequest executes an HTTP request and returns the response func (itu *IntegrationTestUtils) ExecuteRequest(t *testing.T, req TestRequest) *TestResponse { var body []byte var err error if req.Body != nil { body, err = json.Marshal(req.Body) if err != nil { t.Fatalf("Failed to marshal request body: %v", err) } } httpReq := httptest.NewRequest(req.Method, req.URL, bytes.NewBuffer(body)) // Set default headers httpReq.Header.Set("Content-Type", "application/json") // Set custom headers for key, value := range req.Headers { httpReq.Header.Set(key, value) } resp, err := itu.App.Test(httpReq) if err != nil { t.Fatalf("Failed to execute request: %v", err) } defer resp.Body.Close() // Read response body buf := new(bytes.Buffer) buf.ReadFrom(resp.Body) responseBody := buf.String() // Extract response headers responseHeaders := make(map[string]string) for key, values := range resp.Header { if len(values) > 0 { responseHeaders[key] = values[0] } } return &TestResponse{ StatusCode: resp.StatusCode, Body: responseBody, Headers: responseHeaders, } } // ExecuteRequestWithAuth executes an HTTP request with authentication func (itu *IntegrationTestUtils) ExecuteRequestWithAuth(t *testing.T, req TestRequest, token string) *TestResponse { if req.Headers == nil { req.Headers = make(map[string]string) } req.Headers["Authorization"] = "Bearer " + token return itu.ExecuteRequest(t, req) } // LoginUser logs in a user and returns the auth token func (itu *IntegrationTestUtils) LoginUser(t *testing.T, email, password string) string { loginReq := TestRequest{ Method: "POST", URL: "/v1/auth/login", Body: map[string]interface{}{ "email": email, "password": password, }, } resp := itu.ExecuteRequest(t, loginReq) if resp.StatusCode != http.StatusOK { t.Fatalf("Failed to login user: status %d, body: %s", resp.StatusCode, resp.Body) } var loginResp map[string]interface{} if err := json.Unmarshal([]byte(resp.Body), &loginResp); err != nil { t.Fatalf("Failed to unmarshal login response: %v", err) } token, ok := loginResp["token"].(string) if !ok { t.Fatalf("Failed to extract token from login response: %+v", loginResp) } return token } // CreateTestUserWithAuth creates a test user and returns auth token func (itu *IntegrationTestUtils) CreateTestUserWithAuth(t *testing.T, email, fullName, password string) (string, *model.User) { // Create domain model user := &model.User{ Email: email, FullName: fullName, } if err := user.SetPassword(password); err != nil { t.Fatalf("Failed to set password: %v", err) } // Create user createdUser, err := itu.MembershipService.CreateUser(context.Background(), user, []string{"user"}) if err != nil { t.Fatalf("Failed to create test user: %v", err) } // Login to get token token := itu.LoginUser(t, email, password) return token, createdUser } // CreateTestAdmin creates a test admin user and returns auth token func (itu *IntegrationTestUtils) CreateTestAdmin(t *testing.T, email, fullName, password string) (string, *model.User) { // Create domain model user := &model.User{ Email: email, FullName: fullName, } if err := user.SetPassword(password); err != nil { t.Fatalf("Failed to set password: %v", err) } // Create admin user createdUser, err := itu.MembershipService.CreateUser(context.Background(), user, []string{"admin"}) if err != nil { t.Fatalf("Failed to create test admin: %v", err) } // Login to get token token := itu.LoginUser(t, email, password) return token, createdUser } // AssertStatusCode asserts that the response has the expected status code func (itu *IntegrationTestUtils) AssertStatusCode(t *testing.T, resp *TestResponse, expectedStatusCode int) { if resp.StatusCode != expectedStatusCode { t.Errorf("Expected status code %d, got %d. Response body: %s", expectedStatusCode, resp.StatusCode, resp.Body) } } // AssertResponseBody asserts that the response body contains expected content func (itu *IntegrationTestUtils) AssertResponseBody(t *testing.T, resp *TestResponse, expectedContent string) { if !contains(resp.Body, expectedContent) { t.Errorf("Expected response body to contain '%s', but got: %s", expectedContent, resp.Body) } } // AssertResponseNotContains asserts that the response body does not contain specific content func (itu *IntegrationTestUtils) AssertResponseNotContains(t *testing.T, resp *TestResponse, content string) { if contains(resp.Body, content) { t.Errorf("Expected response body to not contain '%s', but it did: %s", content, resp.Body) } } // AssertResponseJSON asserts that the response body is valid JSON and matches expected structure func (itu *IntegrationTestUtils) AssertResponseJSON(t *testing.T, resp *TestResponse, expectedJSON interface{}) { var actualJSON interface{} if err := json.Unmarshal([]byte(resp.Body), &actualJSON); err != nil { t.Fatalf("Failed to unmarshal response body as JSON: %v. Body: %s", err, resp.Body) } expectedBytes, err := json.Marshal(expectedJSON) if err != nil { t.Fatalf("Failed to marshal expected JSON: %v", err) } actualBytes, err := json.Marshal(actualJSON) if err != nil { t.Fatalf("Failed to marshal actual JSON: %v", err) } if string(expectedBytes) != string(actualBytes) { t.Errorf("Expected JSON: %s, got: %s", string(expectedBytes), string(actualBytes)) } } // AssertHeader asserts that the response has a specific header with expected value func (itu *IntegrationTestUtils) AssertHeader(t *testing.T, resp *TestResponse, headerName, expectedValue string) { actualValue, exists := resp.Headers[headerName] if !exists { t.Errorf("Expected header '%s' to exist, but it doesn't", headerName) return } if actualValue != expectedValue { t.Errorf("Expected header '%s' to have value '%s', got '%s'", headerName, expectedValue, actualValue) } } // ParseJSONResponse parses the response body as JSON func (itu *IntegrationTestUtils) ParseJSONResponse(t *testing.T, resp *TestResponse) map[string]interface{} { var result map[string]interface{} if err := json.Unmarshal([]byte(resp.Body), &result); err != nil { t.Fatalf("Failed to parse response body as JSON: %v. Body: %s", err, resp.Body) } return result } // ExtractJSONField extracts a field from JSON response func (itu *IntegrationTestUtils) ExtractJSONField(t *testing.T, resp *TestResponse, fieldPath ...string) interface{} { data := itu.ParseJSONResponse(t, resp) var result interface{} = data for _, field := range fieldPath { if dataMap, ok := result.(map[string]interface{}); ok { if value, exists := dataMap[field]; exists { result = value } else { t.Fatalf("Field '%s' not found in JSON response: %+v", field, dataMap) } } else { t.Fatalf("Cannot extract field '%s' from non-map data: %+v", field, result) } } return result } // ExtractStringField extracts a string field from JSON response func (itu *IntegrationTestUtils) ExtractStringField(t *testing.T, resp *TestResponse, fieldPath ...string) string { value := itu.ExtractJSONField(t, resp, fieldPath...) if str, ok := value.(string); ok { return str } t.Fatalf("Expected string value for field %v, but got: %+v", fieldPath, value) return "" } // ExtractIntField extracts an integer field from JSON response func (itu *IntegrationTestUtils) ExtractIntField(t *testing.T, resp *TestResponse, fieldPath ...string) int { value := itu.ExtractJSONField(t, resp, fieldPath...) if floatVal, ok := value.(float64); ok { return int(floatVal) } if intVal, ok := value.(int); ok { return intVal } t.Fatalf("Expected int value for field %v, but got: %+v", fieldPath, value) return 0 } // ExtractBoolField extracts a boolean field from JSON response func (itu *IntegrationTestUtils) ExtractBoolField(t *testing.T, resp *TestResponse, fieldPath ...string) bool { value := itu.ExtractJSONField(t, resp, fieldPath...) if boolVal, ok := value.(bool); ok { return boolVal } t.Fatalf("Expected bool value for field %v, but got: %+v", fieldPath, value) return false } // WaitForCondition waits for a condition to be true with timeout func (itu *IntegrationTestUtils) WaitForCondition(t *testing.T, condition func() bool, timeout time.Duration, message string) { start := time.Now() for time.Since(start) < timeout { if condition() { return } time.Sleep(100 * time.Millisecond) } t.Fatalf("Condition not met within timeout: %s", message) } // CleanupDatabase cleans up test data from database func (itu *IntegrationTestUtils) CleanupDatabase(t *testing.T) { // Clean up in reverse order of dependencies tables := []string{ "task_assignees", "project_members", "integrations", "tasks", "projects", "types", "user_roles", "role_permissions", "users", "roles", "permissions", "system_configs", "audit_logs", "security_events", } for _, table := range tables { err := itu.DB.Exec(fmt.Sprintf("DELETE FROM %s", table)).Error if err != nil { t.Logf("Warning: Failed to clean table %s: %v", table, err) } } } // SeedTestData seeds the database with test data func (itu *IntegrationTestUtils) SeedTestData(t *testing.T) { // Create test roles itu.CreateTestRole(t, "admin", "Administrator role") itu.CreateTestRole(t, "user", "Regular user role") // Create test permissions itu.CreateTestPermission(t, "user:read", "Read user data", "user") itu.CreateTestPermission(t, "user:write", "Write user data", "user") itu.CreateTestPermission(t, "project:read", "Read project data", "project") itu.CreateTestPermission(t, "project:write", "Write project data", "project") // Create test project types itu.CreateTestType(t, "Web Development", "Standard web development project", nil) itu.CreateTestType(t, "Mobile App", "Mobile application development", nil) } // CreateTestRole creates a test role in the database func (itu *IntegrationTestUtils) CreateTestRole(t *testing.T, name, description string) *model.Role { role := &model.Role{ Name: name, Description: description, Active: true, } role.Init() err := itu.DB.Create(role).Error if err != nil { t.Fatalf("Failed to create test role: %v", err) } return role } // CreateTestPermission creates a test permission in the database func (itu *IntegrationTestUtils) CreateTestPermission(t *testing.T, name, description, category string) *model.Permission { permission := &model.Permission{ Name: name, Description: description, Category: category, Active: true, } permission.Init() err := itu.DB.Create(permission).Error if err != nil { t.Fatalf("Failed to create test permission: %v", err) } return permission } // CreateTestType creates a test project type in the database func (itu *IntegrationTestUtils) CreateTestType(t *testing.T, name, description string, userID *string) *model.Type { projectType := &model.Type{ Name: name, Description: description, UserID: userID, } projectType.Init() err := itu.DB.Create(projectType).Error if err != nil { t.Fatalf("Failed to create test type: %v", err) } return projectType } // CreateTestProject creates a test project in the database func (itu *IntegrationTestUtils) CreateTestProject(t *testing.T, name, description, ownerID, typeID string) *model.Project { project := &model.Project{ Name: name, Description: description, OwnerID: ownerID, TypeID: typeID, } project.Init() err := itu.DB.Create(project).Error if err != nil { t.Fatalf("Failed to create test project: %v", err) } return project } // CreateTestTask creates a test task in the database func (itu *IntegrationTestUtils) CreateTestTask(t *testing.T, title, description, projectID string) *model.Task { task := &model.Task{ Title: title, Description: description, Status: model.TaskStatusTodo, Priority: model.TaskPriorityMedium, ProjectID: projectID, } task.Init() err := itu.DB.Create(task).Error if err != nil { t.Fatalf("Failed to create test task: %v", err) } return task } // contains checks if a string contains a substring func contains(str, substr string) bool { return len(str) >= len(substr) && (str == substr || len(substr) == 0 || (len(substr) > 0 && findSubstring(str, substr))) } // findSubstring finds if substr exists in str func findSubstring(str, substr string) bool { for i := 0; i <= len(str)-len(substr); i++ { if str[i:i+len(substr)] == substr { return true } } return false }