Files
omega-server/tests/integration/integration_test_utils.go
Fran Jurmanović b9cb315944 add tests
2025-07-06 19:19:42 +02:00

464 lines
13 KiB
Go

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
}