add tests

This commit is contained in:
Fran Jurmanović
2025-07-06 19:19:42 +02:00
parent 26a0d33592
commit b9cb315944
8 changed files with 2918 additions and 0 deletions

369
tests/README.md Normal file
View File

@@ -0,0 +1,369 @@
# Testing Module
This directory contains the comprehensive testing framework for the Omega server application. The testing module is organized to support unit tests, integration tests, GraphQL tests, and provides utilities for creating test fixtures and managing test data.
## Directory Structure
```
tests/
├── README.md # This file
├── testing.go # Main testing utilities and test suite
├── fixtures/ # Test data fixtures
│ └── fixtures.go # Predefined test data
├── unit/ # Unit testing utilities
│ └── unit_test_utils.go # Unit test helpers and assertions
├── integration/ # Integration testing utilities
│ └── integration_test_utils.go # HTTP request testing and API integration
└── graphql/ # GraphQL testing utilities
└── graphql_test_utils.go # GraphQL query execution and testing
```
## Getting Started
### Prerequisites
Before running tests, ensure you have:
1. **PostgreSQL Test Database**: Set up a dedicated test database
2. **Environment Variables**: Configure test-specific environment variables
3. **Go Testing Tools**: Standard Go testing framework
### Environment Configuration
Set the following environment variables for testing:
```bash
# Test Database Configuration
TEST_DB_HOST=localhost
TEST_DB_PORT=5432
TEST_DB_USER=postgres
TEST_DB_PASSWORD=password
TEST_DB_NAME=omega_test
TEST_DB_SSL_MODE=disable
# Optional: Test-specific configurations
LOG_LEVEL=DEBUG
DEFAULT_ADMIN_PASSWORD=testpassword123
```
### Running Tests
```bash
# Run all tests
go test ./...
# Run tests with verbose output
go test -v ./...
# Run specific test package
go test ./tests/unit/...
go test ./tests/integration/...
go test ./tests/graphql/...
# Run tests with coverage
go test -cover ./...
# Run tests with detailed coverage report
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
```
## Test Categories
### 1. Unit Tests (`tests/unit/`)
Unit tests focus on testing individual components in isolation:
- **Model validation**: Testing model methods and validation logic
- **Service layer**: Testing business logic without external dependencies
- **Utility functions**: Testing helper functions and utilities
- **Repository layer**: Testing data access logic with mocked dependencies
**Example Unit Test Structure:**
```go
func TestUserValidation(t *testing.T) {
utils := unit.NewUnitTestUtils(nil)
user := utils.MockUser("test-id", "test@example.com", "Test User")
err := user.Validate()
utils.AssertNoError(t, err)
utils.AssertStringEqual(t, "test@example.com", user.Email)
}
```
### 2. Integration Tests (`tests/integration/`)
Integration tests verify the interaction between different components:
- **API endpoints**: Testing complete HTTP request/response cycles
- **Database operations**: Testing real database interactions
- **Authentication flows**: Testing JWT token generation and validation
- **Business workflows**: Testing complete user scenarios
**Example Integration Test Structure:**
```go
func TestCreateUserAPI(t *testing.T) {
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
utils := integration.NewIntegrationTestUtils(app, testSuite.DB, membershipService)
req := integration.TestRequest{
Method: "POST",
URL: "/v1/users",
Body: map[string]interface{}{
"email": "new@example.com",
"fullName": "New User",
"password": "password123",
},
}
resp := utils.ExecuteRequest(t, req)
utils.AssertStatusCode(t, resp, 201)
}
```
### 3. GraphQL Tests (`tests/graphql/`)
GraphQL tests specifically target the GraphQL API layer:
- **Query execution**: Testing GraphQL queries and mutations
- **Schema validation**: Ensuring GraphQL schema correctness
- **Error handling**: Testing GraphQL error responses
- **Authentication**: Testing GraphQL with JWT authentication
**Example GraphQL Test Structure:**
```go
func TestGraphQLLogin(t *testing.T) {
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
utils := graphql.NewGraphQLTestUtils(testSuite.DB, membershipService)
response := utils.ExecuteLoginMutation(t, "admin@example.com", "password123")
utils.AssertNoErrors(t, response)
utils.AssertDataNotNil(t, response)
token := utils.ExtractString(t, response, "login", "token")
utils.AssertNotEmpty(t, token)
}
```
## Test Utilities
### TestSuite (`testing.go`)
The main test suite provides:
- **Database Setup**: Automatic test database configuration
- **Migration Management**: Running migrations for tests
- **Cleanup**: Automatic cleanup after tests
- **Dependency Injection**: DI container setup for tests
- **Helper Methods**: Common test operations
```go
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
// Create test data
user := testSuite.CreateTestUser(t, "test@example.com", "Test User")
project := testSuite.CreateTestProject(t, "Test Project", "Description", user.ID, typeID)
```
### Fixtures (`fixtures/`)
Predefined test data for consistent testing:
- **User Fixtures**: Standard test users with different roles
- **Project Fixtures**: Sample projects with various configurations
- **Task Fixtures**: Tasks in different states and priorities
- **Integration Fixtures**: Sample third-party integrations
- **GraphQL Queries**: Common GraphQL operations
```go
fixtures := fixtures.NewFixtures()
users := fixtures.Users()
adminUser := users["admin"]
testProject := fixtures.Projects()["omega_project"]
```
### Assertions
Each test utility provides rich assertion methods:
- **Model Assertions**: Compare model instances
- **Response Assertions**: Validate HTTP responses
- **GraphQL Assertions**: Check GraphQL responses
- **Database Assertions**: Verify database state
## Best Practices
### 1. Test Organization
- **One test file per source file**: Mirror the source code structure
- **Descriptive test names**: Use `TestFunctionName_Scenario_ExpectedBehavior` format
- **Group related tests**: Use subtests with `t.Run()` for related scenarios
### 2. Test Data Management
- **Use fixtures**: Leverage predefined fixtures for consistent test data
- **Clean up**: Always clean up test data after tests
- **Isolation**: Ensure tests don't depend on each other
- **Transactions**: Use database transactions for rollback capabilities
### 3. Mocking and Dependencies
- **Mock external services**: Don't make real API calls in tests
- **Inject dependencies**: Use dependency injection for testability
- **Test doubles**: Use appropriate test doubles (mocks, stubs, fakes)
### 4. Error Testing
- **Test error paths**: Ensure error conditions are properly tested
- **Validate error messages**: Check that error messages are meaningful
- **Edge cases**: Test boundary conditions and edge cases
### 5. Performance Considerations
- **Fast tests**: Keep unit tests fast (< 100ms each)
- **Parallel execution**: Use `t.Parallel()` where appropriate
- **Database optimization**: Use transactions and minimal data for speed
## Test Patterns
### 1. Table-Driven Tests
```go
func TestUserValidation(t *testing.T) {
testCases := []struct {
name string
email string
expectedErr string
}{
{"valid email", "test@example.com", ""},
{"invalid email", "invalid-email", "invalid email format"},
{"empty email", "", "email is required"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
user := &model.User{Email: tc.email}
err := user.Validate()
if tc.expectedErr == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tc.expectedErr)
}
})
}
}
```
### 2. Setup and Teardown
```go
func TestUserService(t *testing.T) {
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
t.Run("CreateUser", func(t *testing.T) {
// Test implementation
})
t.Run("GetUser", func(t *testing.T) {
// Test implementation
})
}
```
### 3. Database Transactions
```go
func TestDatabaseOperations(t *testing.T) {
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
testSuite.RunInTestTransaction(t, func(tx *gorm.DB) {
// Database operations here will be rolled back
user := &model.User{Email: "test@example.com"}
tx.Create(user)
// Assertions
var count int64
tx.Model(&model.User{}).Where("email = ?", "test@example.com").Count(&count)
assert.Equal(t, int64(1), count)
})
}
```
## Debugging Tests
### 1. Verbose Output
```bash
go test -v ./tests/...
```
### 2. Run Specific Tests
```bash
go test -run TestSpecificFunction ./tests/unit/...
```
### 3. Test Coverage
```bash
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage.html
```
### 4. Race Condition Detection
```bash
go test -race ./tests/...
```
## Contributing
When adding new tests:
1. **Follow naming conventions**: Use clear, descriptive names
2. **Add documentation**: Include comments for complex test logic
3. **Update fixtures**: Add new fixtures for new features
4. **Maintain coverage**: Ensure new code has adequate test coverage
5. **Test edge cases**: Include tests for error conditions and edge cases
## Troubleshooting
### Common Issues
1. **Database Connection Failed**
- Check PostgreSQL is running
- Verify environment variables
- Ensure test database exists
2. **Tests Failing Intermittently**
- Check for race conditions
- Ensure proper test isolation
- Review shared state between tests
3. **Slow Test Execution**
- Profile test performance
- Optimize database operations
- Consider using test transactions
4. **Import Errors**
- Run `go mod tidy`
- Check module dependencies
- Verify Go version compatibility
For additional help, refer to the main project documentation or contact the development team.

276
tests/graphql/basic_test.go Normal file
View File

@@ -0,0 +1,276 @@
package graphql
import (
"omega-server/local/service"
"omega-server/tests"
"testing"
"gorm.io/gorm"
)
func TestGraphQLBasic(t *testing.T) {
// Initialize test suite
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
// Skip if no test database
testSuite.SkipIfNoTestDB(t)
// Create membership service
membershipService := createMembershipService(t, testSuite.DB)
// Create GraphQL test utils
gqlUtils := NewGraphQLTestUtils(testSuite.DB, membershipService)
t.Run("GetSchema", func(t *testing.T) {
schema := gqlUtils.GetSchema(t)
if schema == "" {
t.Fatal("Expected non-empty GraphQL schema")
}
// Check for basic types
if !contains(schema, "type User") {
t.Error("Schema should contain User type")
}
if !contains(schema, "type Project") {
t.Error("Schema should contain Project type")
}
if !contains(schema, "type Task") {
t.Error("Schema should contain Task type")
}
if !contains(schema, "type Query") {
t.Error("Schema should contain Query type")
}
if !contains(schema, "type Mutation") {
t.Error("Schema should contain Mutation type")
}
})
t.Run("CreateUser", func(t *testing.T) {
response := gqlUtils.ExecuteCreateUserMutation(t, "test@example.com", "password123", "Test User")
// Should not have errors for valid user creation
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
// Extract user data
userEmail := gqlUtils.ExtractString(t, response, "createUser", "email")
userFullName := gqlUtils.ExtractString(t, response, "createUser", "fullName")
userId := gqlUtils.ExtractString(t, response, "createUser", "id")
if userEmail != "test@example.com" {
t.Errorf("Expected email 'test@example.com', got '%s'", userEmail)
}
if userFullName != "Test User" {
t.Errorf("Expected full name 'Test User', got '%s'", userFullName)
}
if userId == "" {
t.Error("Expected non-empty user ID")
}
})
t.Run("CreateUserWithInvalidEmail", func(t *testing.T) {
response := gqlUtils.ExecuteCreateUserMutation(t, "invalid-email", "password123", "Test User")
// Should have errors for invalid email
gqlUtils.AssertHasErrors(t, response)
})
t.Run("Login", func(t *testing.T) {
// First create a user
createResponse := gqlUtils.ExecuteCreateUserMutation(t, "login@example.com", "password123", "Login User")
gqlUtils.AssertNoErrors(t, createResponse)
// Then try to login
loginResponse := gqlUtils.ExecuteLoginMutation(t, "login@example.com", "password123")
gqlUtils.AssertNoErrors(t, loginResponse)
gqlUtils.AssertDataNotNil(t, loginResponse)
// Extract token and user data
token := gqlUtils.ExtractString(t, loginResponse, "login", "token")
userEmail := gqlUtils.ExtractString(t, loginResponse, "login", "user", "email")
if token == "" {
t.Error("Expected non-empty token")
}
if userEmail != "login@example.com" {
t.Errorf("Expected email 'login@example.com', got '%s'", userEmail)
}
})
t.Run("LoginWithInvalidCredentials", func(t *testing.T) {
response := gqlUtils.ExecuteLoginMutation(t, "nonexistent@example.com", "wrongpassword")
// Should have errors for invalid credentials
gqlUtils.AssertHasErrors(t, response)
gqlUtils.AssertErrorMessage(t, response, "Invalid credentials")
})
t.Run("MeQuery", func(t *testing.T) {
response := gqlUtils.ExecuteMeQuery(t)
// Should return mock user data
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
// Extract user data
userEmail := gqlUtils.ExtractString(t, response, "me", "email")
userId := gqlUtils.ExtractString(t, response, "me", "id")
if userEmail == "" {
t.Error("Expected non-empty email")
}
if userId == "" {
t.Error("Expected non-empty user ID")
}
})
t.Run("UsersQuery", func(t *testing.T) {
// Create a few test users first
gqlUtils.ExecuteCreateUserMutation(t, "user1@example.com", "password123", "User One")
gqlUtils.ExecuteCreateUserMutation(t, "user2@example.com", "password123", "User Two")
response := gqlUtils.ExecuteUsersQuery(t)
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
// Extract users array
users := gqlUtils.ExtractArray(t, response, "users")
// Should have at least the users we created (plus possibly the admin user)
if len(users) < 2 {
t.Errorf("Expected at least 2 users, got %d", len(users))
}
})
t.Run("CreateProject", func(t *testing.T) {
// Create a user first to be the owner
userResponse := gqlUtils.ExecuteCreateUserMutation(t, "owner@example.com", "password123", "Project Owner")
gqlUtils.AssertNoErrors(t, userResponse)
ownerID := gqlUtils.ExtractString(t, userResponse, "createUser", "id")
response := gqlUtils.ExecuteCreateProjectMutation(t, "Test Project", "A test project", ownerID)
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
// Extract project data
projectName := gqlUtils.ExtractString(t, response, "createProject", "name")
projectOwnerID := gqlUtils.ExtractString(t, response, "createProject", "ownerId")
projectID := gqlUtils.ExtractString(t, response, "createProject", "id")
if projectName != "Test Project" {
t.Errorf("Expected project name 'Test Project', got '%s'", projectName)
}
if projectOwnerID != ownerID {
t.Errorf("Expected owner ID '%s', got '%s'", ownerID, projectOwnerID)
}
if projectID == "" {
t.Error("Expected non-empty project ID")
}
})
t.Run("ProjectsQuery", func(t *testing.T) {
response := gqlUtils.ExecuteProjectsQuery(t)
// Should return empty array for mock implementation
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
projects := gqlUtils.ExtractArray(t, response, "projects")
// Mock implementation returns empty array
if len(projects) != 0 {
t.Errorf("Expected 0 projects from mock implementation, got %d", len(projects))
}
})
t.Run("CreateTask", func(t *testing.T) {
response := gqlUtils.ExecuteCreateTaskMutation(t, "Test Task", "A test task", "todo", "medium", "project-id")
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
// Extract task data
taskTitle := gqlUtils.ExtractString(t, response, "createTask", "title")
taskStatus := gqlUtils.ExtractString(t, response, "createTask", "status")
taskPriority := gqlUtils.ExtractString(t, response, "createTask", "priority")
taskID := gqlUtils.ExtractString(t, response, "createTask", "id")
if taskTitle != "Test Task" {
t.Errorf("Expected task title 'Test Task', got '%s'", taskTitle)
}
if taskStatus != "todo" {
t.Errorf("Expected task status 'todo', got '%s'", taskStatus)
}
if taskPriority != "medium" {
t.Errorf("Expected task priority 'medium', got '%s'", taskPriority)
}
if taskID == "" {
t.Error("Expected non-empty task ID")
}
})
t.Run("TasksQuery", func(t *testing.T) {
response := gqlUtils.ExecuteTasksQuery(t, nil)
// Should return empty array for mock implementation
gqlUtils.AssertNoErrors(t, response)
gqlUtils.AssertDataNotNil(t, response)
tasks := gqlUtils.ExtractArray(t, response, "tasks")
// Mock implementation returns empty array
if len(tasks) != 0 {
t.Errorf("Expected 0 tasks from mock implementation, got %d", len(tasks))
}
})
t.Run("InvalidQuery", func(t *testing.T) {
response := gqlUtils.ExecuteQuery(t, "invalid query syntax", nil)
// Should have errors for invalid query
gqlUtils.AssertHasErrors(t, response)
})
t.Run("UnsupportedQuery", func(t *testing.T) {
response := gqlUtils.ExecuteQuery(t, "query { unsupportedField }", nil)
// Should have errors for unsupported query
gqlUtils.AssertHasErrors(t, response)
gqlUtils.AssertErrorMessage(t, response, "Query not supported")
})
}
// createMembershipService creates a membership service for testing
func createMembershipService(t *testing.T, db *gorm.DB) *service.MembershipService {
// This would normally involve creating repository and other dependencies
// For now, we'll create a basic service that works with our test setup
// Note: This is a simplified version for testing
// In a real implementation, you would:
// 1. Create repository with DB
// 2. Set up all dependencies
// 3. Configure the service properly
// For this test, we'll return nil and handle it in the GraphQL handler
// The handler should gracefully handle the basic operations we're testing
return nil
}
// 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
}

View File

@@ -0,0 +1,427 @@
package graphql
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"omega-server/local/graphql/handler"
"omega-server/local/service"
"testing"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
// GraphQLTestUtils provides utilities for testing GraphQL endpoints
type GraphQLTestUtils struct {
App *fiber.App
Handler *handler.GraphQLHandler
DB *gorm.DB
}
// GraphQLTestRequest represents a GraphQL test request
type GraphQLTestRequest struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
// GraphQLTestResponse represents a GraphQL test response
type GraphQLTestResponse struct {
Data interface{} `json:"data,omitempty"`
Errors []GraphQLError `json:"errors,omitempty"`
}
// GraphQLError represents a GraphQL error in test responses
type GraphQLError struct {
Message string `json:"message"`
Path []string `json:"path,omitempty"`
}
// NewGraphQLTestUtils creates a new GraphQL test utilities instance
func NewGraphQLTestUtils(db *gorm.DB, membershipService *service.MembershipService) *GraphQLTestUtils {
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
})
graphqlHandler := handler.NewGraphQLHandler(membershipService)
app.Post("/graphql", graphqlHandler.Handle)
app.Get("/graphql", func(c *fiber.Ctx) error {
return c.SendString(graphqlHandler.GetSchema())
})
return &GraphQLTestUtils{
App: app,
Handler: graphqlHandler,
DB: db,
}
}
// ExecuteQuery executes a GraphQL query and returns the response
func (gtu *GraphQLTestUtils) ExecuteQuery(t *testing.T, query string, variables map[string]interface{}) *GraphQLTestResponse {
request := GraphQLTestRequest{
Query: query,
Variables: variables,
}
body, err := json.Marshal(request)
if err != nil {
t.Fatalf("Failed to marshal GraphQL request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, err := gtu.App.Test(req)
if err != nil {
t.Fatalf("Failed to execute GraphQL request: %v", err)
}
defer resp.Body.Close()
var response GraphQLTestResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode GraphQL response: %v", err)
}
return &response
}
// ExecuteQueryWithContext executes a GraphQL query with context
func (gtu *GraphQLTestUtils) ExecuteQueryWithContext(t *testing.T, ctx context.Context, query string, variables map[string]interface{}) *GraphQLTestResponse {
request := GraphQLTestRequest{
Query: query,
Variables: variables,
}
body, err := json.Marshal(request)
if err != nil {
t.Fatalf("Failed to marshal GraphQL request: %v", err)
}
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req = req.WithContext(ctx)
resp, err := gtu.App.Test(req)
if err != nil {
t.Fatalf("Failed to execute GraphQL request: %v", err)
}
defer resp.Body.Close()
var response GraphQLTestResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode GraphQL response: %v", err)
}
return &response
}
// ExecuteLoginMutation executes a login mutation
func (gtu *GraphQLTestUtils) ExecuteLoginMutation(t *testing.T, email, password string) *GraphQLTestResponse {
query := `
mutation Login($email: String!, $password: String!) {
login(input: {email: $email, password: $password}) {
token
user {
id
email
fullName
}
}
}
`
variables := map[string]interface{}{
"email": email,
"password": password,
}
return gtu.ExecuteQuery(t, query, variables)
}
// ExecuteCreateUserMutation executes a create user mutation
func (gtu *GraphQLTestUtils) ExecuteCreateUserMutation(t *testing.T, email, password, fullName string) *GraphQLTestResponse {
query := `
mutation CreateUser($email: String!, $password: String!, $fullName: String!) {
createUser(input: {email: $email, password: $password, fullName: $fullName}) {
id
email
fullName
createdAt
updatedAt
}
}
`
variables := map[string]interface{}{
"email": email,
"password": password,
"fullName": fullName,
}
return gtu.ExecuteQuery(t, query, variables)
}
// ExecuteMeQuery executes a me query
func (gtu *GraphQLTestUtils) ExecuteMeQuery(t *testing.T) *GraphQLTestResponse {
query := `
query Me {
me {
id
email
fullName
createdAt
updatedAt
}
}
`
return gtu.ExecuteQuery(t, query, nil)
}
// ExecuteUsersQuery executes a users query
func (gtu *GraphQLTestUtils) ExecuteUsersQuery(t *testing.T) *GraphQLTestResponse {
query := `
query Users {
users {
id
email
fullName
createdAt
updatedAt
}
}
`
return gtu.ExecuteQuery(t, query, nil)
}
// ExecuteUserQuery executes a user query by ID
func (gtu *GraphQLTestUtils) ExecuteUserQuery(t *testing.T, userID string) *GraphQLTestResponse {
query := `
query User($id: String!) {
user(id: $id) {
id
email
fullName
createdAt
updatedAt
}
}
`
variables := map[string]interface{}{
"id": userID,
}
return gtu.ExecuteQuery(t, query, variables)
}
// ExecuteCreateProjectMutation executes a create project mutation
func (gtu *GraphQLTestUtils) ExecuteCreateProjectMutation(t *testing.T, name, description, ownerID string) *GraphQLTestResponse {
query := `
mutation CreateProject($name: String!, $description: String, $ownerId: String!) {
createProject(input: {name: $name, description: $description, ownerId: $ownerId}) {
id
name
description
ownerId
createdAt
updatedAt
}
}
`
variables := map[string]interface{}{
"name": name,
"description": description,
"ownerId": ownerID,
}
return gtu.ExecuteQuery(t, query, variables)
}
// ExecuteProjectsQuery executes a projects query
func (gtu *GraphQLTestUtils) ExecuteProjectsQuery(t *testing.T) *GraphQLTestResponse {
query := `
query Projects {
projects {
id
name
description
ownerId
createdAt
updatedAt
}
}
`
return gtu.ExecuteQuery(t, query, nil)
}
// ExecuteCreateTaskMutation executes a create task mutation
func (gtu *GraphQLTestUtils) ExecuteCreateTaskMutation(t *testing.T, title, description, status, priority, projectID string) *GraphQLTestResponse {
query := `
mutation CreateTask($title: String!, $description: String, $status: String, $priority: String, $projectId: String!) {
createTask(input: {title: $title, description: $description, status: $status, priority: $priority, projectId: $projectId}) {
id
title
description
status
priority
projectId
createdAt
updatedAt
}
}
`
variables := map[string]interface{}{
"title": title,
"description": description,
"status": status,
"priority": priority,
"projectId": projectID,
}
return gtu.ExecuteQuery(t, query, variables)
}
// ExecuteTasksQuery executes a tasks query
func (gtu *GraphQLTestUtils) ExecuteTasksQuery(t *testing.T, projectID *string) *GraphQLTestResponse {
query := `
query Tasks($projectId: String) {
tasks(projectId: $projectId) {
id
title
description
status
priority
projectId
createdAt
updatedAt
}
}
`
variables := make(map[string]interface{})
if projectID != nil {
variables["projectId"] = *projectID
}
return gtu.ExecuteQuery(t, query, variables)
}
// GetSchema returns the GraphQL schema
func (gtu *GraphQLTestUtils) GetSchema(t *testing.T) string {
return gtu.Handler.GetSchema()
}
// AssertNoErrors asserts that the GraphQL response has no errors
func (gtu *GraphQLTestUtils) AssertNoErrors(t *testing.T, response *GraphQLTestResponse) {
if len(response.Errors) > 0 {
t.Fatalf("Expected no GraphQL errors, but got: %+v", response.Errors)
}
}
// AssertHasErrors asserts that the GraphQL response has errors
func (gtu *GraphQLTestUtils) AssertHasErrors(t *testing.T, response *GraphQLTestResponse) {
if len(response.Errors) == 0 {
t.Fatalf("Expected GraphQL errors, but got none")
}
}
// AssertErrorMessage asserts that the GraphQL response contains a specific error message
func (gtu *GraphQLTestUtils) AssertErrorMessage(t *testing.T, response *GraphQLTestResponse, expectedMessage string) {
if len(response.Errors) == 0 {
t.Fatalf("Expected GraphQL errors, but got none")
}
for _, err := range response.Errors {
if err.Message == expectedMessage {
return
}
}
t.Fatalf("Expected error message '%s', but not found in errors: %+v", expectedMessage, response.Errors)
}
// AssertDataNotNil asserts that the GraphQL response data is not nil
func (gtu *GraphQLTestUtils) AssertDataNotNil(t *testing.T, response *GraphQLTestResponse) {
if response.Data == nil {
t.Fatalf("Expected GraphQL data to not be nil, but it was")
}
}
// AssertDataNil asserts that the GraphQL response data is nil
func (gtu *GraphQLTestUtils) AssertDataNil(t *testing.T, response *GraphQLTestResponse) {
if response.Data != nil {
t.Fatalf("Expected GraphQL data to be nil, but got: %+v", response.Data)
}
}
// ExtractField extracts a field from the GraphQL response data
func (gtu *GraphQLTestUtils) ExtractField(t *testing.T, response *GraphQLTestResponse, fieldPath ...string) interface{} {
if response.Data == nil {
t.Fatalf("Cannot extract field from nil data")
}
data := response.Data
for _, field := range fieldPath {
if dataMap, ok := data.(map[string]interface{}); ok {
if value, exists := dataMap[field]; exists {
data = value
} else {
t.Fatalf("Field '%s' not found in data: %+v", field, dataMap)
}
} else {
t.Fatalf("Cannot extract field '%s' from non-map data: %+v", field, data)
}
}
return data
}
// ExtractString extracts a string field from the GraphQL response data
func (gtu *GraphQLTestUtils) ExtractString(t *testing.T, response *GraphQLTestResponse, fieldPath ...string) string {
value := gtu.ExtractField(t, response, fieldPath...)
if str, ok := value.(string); ok {
return str
}
t.Fatalf("Expected string value for field %v, but got: %+v", fieldPath, value)
return ""
}
// ExtractInt extracts an integer field from the GraphQL response data
func (gtu *GraphQLTestUtils) ExtractInt(t *testing.T, response *GraphQLTestResponse, fieldPath ...string) int {
value := gtu.ExtractField(t, response, 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
}
// ExtractBool extracts a boolean field from the GraphQL response data
func (gtu *GraphQLTestUtils) ExtractBool(t *testing.T, response *GraphQLTestResponse, fieldPath ...string) bool {
value := gtu.ExtractField(t, response, fieldPath...)
if boolVal, ok := value.(bool); ok {
return boolVal
}
t.Fatalf("Expected bool value for field %v, but got: %+v", fieldPath, value)
return false
}
// ExtractArray extracts an array field from the GraphQL response data
func (gtu *GraphQLTestUtils) ExtractArray(t *testing.T, response *GraphQLTestResponse, fieldPath ...string) []interface{} {
value := gtu.ExtractField(t, response, fieldPath...)
if arrVal, ok := value.([]interface{}); ok {
return arrVal
}
t.Fatalf("Expected array value for field %v, but got: %+v", fieldPath, value)
return nil
}

View File

@@ -0,0 +1,373 @@
package integration
import (
"omega-server/local/api"
"omega-server/local/model"
"omega-server/local/repository"
"omega-server/local/service"
"omega-server/tests"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestGraphQLIntegration(t *testing.T) {
// Initialize test suite
testSuite := tests.NewTestSuite()
testSuite.Setup(t)
defer testSuite.Teardown(t)
// Skip if no test database
testSuite.SkipIfNoTestDB(t)
// Create Fiber app
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
})
// Initialize services and repositories
membershipRepo := repository.NewMembershipRepository(testSuite.DB)
membershipService := service.NewMembershipService(membershipRepo)
// Provide services to DI container
err := testSuite.DI.Provide(func() *service.MembershipService {
return membershipService
})
if err != nil {
t.Fatalf("Failed to provide membership service: %v", err)
}
// Initialize API routes
api.Init(testSuite.DI, app)
// Create integration test utils
integrationUtils := NewIntegrationTestUtils(app, testSuite.DB, membershipService)
t.Run("GraphQLSchemaEndpoint", func(t *testing.T) {
req := TestRequest{
Method: "GET",
URL: "/v1/graphql",
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
integrationUtils.AssertResponseBody(t, resp, "type User")
integrationUtils.AssertResponseBody(t, resp, "type Query")
integrationUtils.AssertResponseBody(t, resp, "type Mutation")
})
t.Run("GraphQLLoginMutation", func(t *testing.T) {
// First create a user through the service
user := &model.User{
Email: "graphql@example.com",
FullName: "GraphQL User",
}
if err := user.SetPassword("password123"); err != nil {
t.Fatalf("Failed to set password: %v", err)
}
_, err := membershipService.CreateUser(
testSuite.TestContext(),
user,
[]string{"user"},
)
if err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
// Test GraphQL login mutation
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
mutation Login($email: String!, $password: String!) {
login(input: {email: $email, password: $password}) {
token
user {
id
email
fullName
}
}
}
`,
"variables": map[string]interface{}{
"email": "graphql@example.com",
"password": "password123",
},
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
// Parse response and check for token
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
t.Fatalf("GraphQL returned errors: %v", errors)
}
loginData := integrationUtils.ExtractJSONField(t, resp, "data", "login")
if loginData == nil {
t.Fatal("Expected login data in response")
}
token := integrationUtils.ExtractStringField(t, resp, "data", "login", "token")
if token == "" {
t.Error("Expected non-empty token")
}
userEmail := integrationUtils.ExtractStringField(t, resp, "data", "login", "user", "email")
if userEmail != "graphql@example.com" {
t.Errorf("Expected email 'graphql@example.com', got '%s'", userEmail)
}
})
t.Run("GraphQLCreateUserMutation", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
mutation CreateUser($email: String!, $password: String!, $fullName: String!) {
createUser(input: {email: $email, password: $password, fullName: $fullName}) {
id
email
fullName
createdAt
updatedAt
}
}
`,
"variables": map[string]interface{}{
"email": "newuser@example.com",
"password": "password123",
"fullName": "New User",
},
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
// Parse response and verify user creation
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
t.Fatalf("GraphQL returned errors: %v", errors)
}
userEmail := integrationUtils.ExtractStringField(t, resp, "data", "createUser", "email")
if userEmail != "newuser@example.com" {
t.Errorf("Expected email 'newuser@example.com', got '%s'", userEmail)
}
userFullName := integrationUtils.ExtractStringField(t, resp, "data", "createUser", "fullName")
if userFullName != "New User" {
t.Errorf("Expected full name 'New User', got '%s'", userFullName)
}
userID := integrationUtils.ExtractStringField(t, resp, "data", "createUser", "id")
if userID == "" {
t.Error("Expected non-empty user ID")
}
})
t.Run("GraphQLMeQuery", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
query Me {
me {
id
email
fullName
createdAt
updatedAt
}
}
`,
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
// Parse response
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
t.Fatalf("GraphQL returned errors: %v", errors)
}
userEmail := integrationUtils.ExtractStringField(t, resp, "data", "me", "email")
if userEmail == "" {
t.Error("Expected non-empty email")
}
userID := integrationUtils.ExtractStringField(t, resp, "data", "me", "id")
if userID == "" {
t.Error("Expected non-empty user ID")
}
})
t.Run("GraphQLInvalidQuery", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": "invalid query syntax {",
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 400)
// Should have errors in response
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; !exists {
t.Fatal("Expected errors in response for invalid query")
} else {
errorList, ok := errors.([]interface{})
if !ok || len(errorList) == 0 {
t.Fatal("Expected non-empty error list")
}
}
})
t.Run("GraphQLUnsupportedQuery", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
query UnsupportedQuery {
unsupportedField {
id
name
}
}
`,
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 400)
// Should return "Query not supported" error
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
errorList, ok := errors.([]interface{})
if ok && len(errorList) > 0 {
if errorMap, ok := errorList[0].(map[string]interface{}); ok {
if message, ok := errorMap["message"].(string); ok {
if message != "Query not supported" {
t.Errorf("Expected 'Query not supported' error, got '%s'", message)
}
}
}
}
}
})
t.Run("GraphQLCreateProjectMutation", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
mutation CreateProject($name: String!, $description: String, $ownerId: String!) {
createProject(input: {name: $name, description: $description, ownerId: $ownerId}) {
id
name
description
ownerId
createdAt
updatedAt
}
}
`,
"variables": map[string]interface{}{
"name": "Integration Test Project",
"description": "A project created during integration testing",
"ownerId": "test-owner-id",
},
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
// Parse response (should work since it's mocked)
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
t.Fatalf("GraphQL returned errors: %v", errors)
}
projectName := integrationUtils.ExtractStringField(t, resp, "data", "createProject", "name")
if projectName != "Integration Test Project" {
t.Errorf("Expected project name 'Integration Test Project', got '%s'", projectName)
}
projectID := integrationUtils.ExtractStringField(t, resp, "data", "createProject", "id")
if projectID == "" {
t.Error("Expected non-empty project ID")
}
})
t.Run("GraphQLCreateTaskMutation", func(t *testing.T) {
req := TestRequest{
Method: "POST",
URL: "/v1/graphql",
Body: map[string]interface{}{
"query": `
mutation CreateTask($title: String!, $description: String, $status: String, $priority: String, $projectId: String!) {
createTask(input: {title: $title, description: $description, status: $status, priority: $priority, projectId: $projectId}) {
id
title
description
status
priority
projectId
createdAt
updatedAt
}
}
`,
"variables": map[string]interface{}{
"title": "Integration Test Task",
"description": "A task created during integration testing",
"status": "todo",
"priority": "medium",
"projectId": "test-project-id",
},
},
}
resp := integrationUtils.ExecuteRequest(t, req)
integrationUtils.AssertStatusCode(t, resp, 200)
// Parse response (should work since it's mocked)
data := integrationUtils.ParseJSONResponse(t, resp)
if errors, exists := data["errors"]; exists {
t.Fatalf("GraphQL returned errors: %v", errors)
}
taskTitle := integrationUtils.ExtractStringField(t, resp, "data", "createTask", "title")
if taskTitle != "Integration Test Task" {
t.Errorf("Expected task title 'Integration Test Task', got '%s'", taskTitle)
}
taskStatus := integrationUtils.ExtractStringField(t, resp, "data", "createTask", "status")
if taskStatus != "todo" {
t.Errorf("Expected task status 'todo', got '%s'", taskStatus)
}
taskID := integrationUtils.ExtractStringField(t, resp, "data", "createTask", "id")
if taskID == "" {
t.Error("Expected non-empty task ID")
}
})
}

View File

@@ -0,0 +1,463 @@
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
}

362
tests/testing.go Normal file
View File

@@ -0,0 +1,362 @@
package tests
import (
"context"
"fmt"
"omega-server/local/model"
"omega-server/local/utl/logging"
"os"
"testing"
"go.uber.org/dig"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// TestConfig holds configuration for testing
type TestConfig struct {
DBHost string
DBPort string
DBUser string
DBPassword string
DBName string
DBSSLMode string
}
// TestSuite provides a base structure for test suites
type TestSuite struct {
DB *gorm.DB
DI *dig.Container
Config *TestConfig
}
// NewTestSuite creates a new test suite
func NewTestSuite() *TestSuite {
config := &TestConfig{
DBHost: getEnvOrDefault("TEST_DB_HOST", "localhost"),
DBPort: getEnvOrDefault("TEST_DB_PORT", "5432"),
DBUser: getEnvOrDefault("TEST_DB_USER", "postgres"),
DBPassword: getEnvOrDefault("TEST_DB_PASSWORD", "password"),
DBName: getEnvOrDefault("TEST_DB_NAME", "omega_test"),
DBSSLMode: getEnvOrDefault("TEST_DB_SSL_MODE", "disable"),
}
return &TestSuite{
Config: config,
DI: dig.New(),
}
}
// Setup initializes the test environment
func (ts *TestSuite) Setup(t *testing.T) {
// Initialize logger for tests
logger, err := logging.Initialize()
if err != nil {
t.Fatalf("Failed to initialize logger: %v", err)
}
defer logger.Close()
// Setup test database
ts.setupTestDatabase(t)
// Setup dependency injection
ts.setupDI(t)
// Migrate database
ts.migrateDatabase(t)
}
// Teardown cleans up the test environment
func (ts *TestSuite) Teardown(t *testing.T) {
if ts.DB != nil {
// Clean up test data
ts.cleanupDatabase(t)
// Close database connection
sqlDB, err := ts.DB.DB()
if err == nil {
sqlDB.Close()
}
}
}
// setupTestDatabase initializes the test database connection
func (ts *TestSuite) setupTestDatabase(t *testing.T) {
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=UTC",
ts.Config.DBHost,
ts.Config.DBUser,
ts.Config.DBPassword,
ts.Config.DBName,
ts.Config.DBPort,
ts.Config.DBSSLMode,
)
// Use silent logger for tests
gormLogger := logger.Default.LogMode(logger.Silent)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: gormLogger,
})
if err != nil {
t.Skipf("Failed to connect to test database: %v", err)
}
ts.DB = db
}
// setupDI initializes dependency injection for tests
func (ts *TestSuite) setupDI(t *testing.T) {
err := ts.DI.Provide(func() *gorm.DB {
return ts.DB
})
if err != nil {
t.Fatalf("Failed to provide database to DI: %v", err)
}
}
// migrateDatabase runs database migrations for tests
func (ts *TestSuite) migrateDatabase(t *testing.T) {
err := ts.DB.AutoMigrate(
&model.User{},
&model.Role{},
&model.Permission{},
&model.Type{},
&model.Project{},
&model.Task{},
&model.Integration{},
&model.ProjectMember{},
&model.TaskAssignee{},
&model.SystemConfig{},
&model.AuditLog{},
&model.SecurityEvent{},
)
if err != nil {
t.Fatalf("Failed to migrate test database: %v", err)
}
}
// cleanupDatabase cleans up test data
func (ts *TestSuite) 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 := ts.DB.Exec(fmt.Sprintf("TRUNCATE TABLE %s RESTART IDENTITY CASCADE", table)).Error
if err != nil {
t.Logf("Warning: Failed to truncate table %s: %v", table, err)
}
}
}
// CreateTestUser creates a test user
func (ts *TestSuite) CreateTestUser(t *testing.T, email, fullName string) *model.User {
user := &model.User{
Email: email,
FullName: fullName,
}
user.Init()
err := user.SetPassword("testpassword123")
if err != nil {
t.Fatalf("Failed to set password for test user: %v", err)
}
err = ts.DB.Create(user).Error
if err != nil {
t.Fatalf("Failed to create test user: %v", err)
}
return user
}
// CreateTestRole creates a test role
func (ts *TestSuite) CreateTestRole(t *testing.T, name, description string) *model.Role {
role := &model.Role{
Name: name,
Description: description,
Active: true,
}
role.Init()
err := ts.DB.Create(role).Error
if err != nil {
t.Fatalf("Failed to create test role: %v", err)
}
return role
}
// CreateTestPermission creates a test permission
func (ts *TestSuite) CreateTestPermission(t *testing.T, name, description, category string) *model.Permission {
permission := &model.Permission{
Name: name,
Description: description,
Category: category,
Active: true,
}
permission.Init()
err := ts.DB.Create(permission).Error
if err != nil {
t.Fatalf("Failed to create test permission: %v", err)
}
return permission
}
// CreateTestType creates a test project type
func (ts *TestSuite) CreateTestType(t *testing.T, name, description string, userID *string) *model.Type {
projectType := &model.Type{
Name: name,
Description: description,
UserID: userID,
}
projectType.Init()
err := ts.DB.Create(projectType).Error
if err != nil {
t.Fatalf("Failed to create test type: %v", err)
}
return projectType
}
// CreateTestProject creates a test project
func (ts *TestSuite) 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 := ts.DB.Create(project).Error
if err != nil {
t.Fatalf("Failed to create test project: %v", err)
}
return project
}
// CreateTestTask creates a test task
func (ts *TestSuite) 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 := ts.DB.Create(task).Error
if err != nil {
t.Fatalf("Failed to create test task: %v", err)
}
return task
}
// WithTransaction runs a function within a database transaction
func (ts *TestSuite) WithTransaction(t *testing.T, fn func(tx *gorm.DB) error) {
tx := ts.DB.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
t.Fatalf("Transaction panicked: %v", r)
}
}()
if err := fn(tx); err != nil {
tx.Rollback()
t.Fatalf("Transaction failed: %v", err)
}
if err := tx.Commit().Error; err != nil {
t.Fatalf("Failed to commit transaction: %v", err)
}
}
// AssertUserExists asserts that a user exists in the database
func (ts *TestSuite) AssertUserExists(t *testing.T, email string) *model.User {
var user model.User
err := ts.DB.Where("email = ?", email).First(&user).Error
if err != nil {
t.Fatalf("Expected user with email %s to exist, but not found: %v", email, err)
}
return &user
}
// AssertUserNotExists asserts that a user does not exist in the database
func (ts *TestSuite) AssertUserNotExists(t *testing.T, email string) {
var user model.User
err := ts.DB.Where("email = ?", email).First(&user).Error
if err == nil {
t.Fatalf("Expected user with email %s to not exist, but found: %+v", email, user)
}
}
// AssertProjectExists asserts that a project exists in the database
func (ts *TestSuite) AssertProjectExists(t *testing.T, name string) *model.Project {
var project model.Project
err := ts.DB.Where("name = ?", name).First(&project).Error
if err != nil {
t.Fatalf("Expected project with name %s to exist, but not found: %v", name, err)
}
return &project
}
// AssertTaskExists asserts that a task exists in the database
func (ts *TestSuite) AssertTaskExists(t *testing.T, title string) *model.Task {
var task model.Task
err := ts.DB.Where("title = ?", title).First(&task).Error
if err != nil {
t.Fatalf("Expected task with title %s to exist, but not found: %v", title, err)
}
return &task
}
// TestContext creates a test context
func (ts *TestSuite) TestContext() context.Context {
return context.Background()
}
// getEnvOrDefault returns environment variable value or default
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// SkipIfNoTestDB skips test if test database is not available
func (ts *TestSuite) SkipIfNoTestDB(t *testing.T) {
if ts.DB == nil {
t.Skip("Test database not available, skipping test")
}
}
// RunInTestTransaction runs a test function in a transaction that gets rolled back
func (ts *TestSuite) RunInTestTransaction(t *testing.T, testFn func(tx *gorm.DB)) {
tx := ts.DB.Begin()
defer tx.Rollback()
testFn(tx)
}

View File

@@ -0,0 +1,272 @@
package unit
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"omega-server/local/graphql/handler"
"testing"
"github.com/gofiber/fiber/v2"
)
func TestGraphQLHandler(t *testing.T) {
// Create a basic GraphQL handler without dependencies
graphqlHandler := handler.NewGraphQLHandler(nil)
// Create a Fiber app for testing
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
})
// Set up routes
app.Post("/graphql", graphqlHandler.Handle)
app.Get("/graphql", func(c *fiber.Ctx) error {
return c.SendString(graphqlHandler.GetSchema())
})
t.Run("GetSchema", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/graphql", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to execute request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
// Read response body
buf := new(bytes.Buffer)
buf.ReadFrom(resp.Body)
schema := buf.String()
if schema == "" {
t.Fatal("Expected non-empty schema")
}
// Check for basic GraphQL types
expectedTypes := []string{"type User", "type Project", "type Task", "type Query", "type Mutation"}
for _, expectedType := range expectedTypes {
if !contains(schema, expectedType) {
t.Errorf("Schema should contain '%s'", expectedType)
}
}
})
t.Run("InvalidRequestBody", func(t *testing.T) {
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBufferString("invalid json"))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to execute request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Expected status 400, got %d", resp.StatusCode)
}
// Parse response
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}
// Should have errors
if errors, exists := response["errors"]; !exists {
t.Error("Expected errors in response")
} else {
errorList, ok := errors.([]interface{})
if !ok || len(errorList) == 0 {
t.Error("Expected non-empty error list")
}
}
})
t.Run("MeQuery", func(t *testing.T) {
requestBody := map[string]interface{}{
"query": `
query Me {
me {
id
email
fullName
}
}
`,
}
body, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to execute request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
// Parse response
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}
// Should have data
if data, exists := response["data"]; !exists {
t.Error("Expected data in response")
} else {
dataMap, ok := data.(map[string]interface{})
if !ok {
t.Error("Expected data to be an object")
} else {
if me, exists := dataMap["me"]; !exists {
t.Error("Expected 'me' field in data")
} else {
meMap, ok := me.(map[string]interface{})
if !ok {
t.Error("Expected 'me' to be an object")
} else {
// Check for required fields
if id, exists := meMap["id"]; !exists || id == "" {
t.Error("Expected non-empty 'id' field")
}
if email, exists := meMap["email"]; !exists || email == "" {
t.Error("Expected non-empty 'email' field")
}
}
}
}
}
})
t.Run("UnsupportedQuery", func(t *testing.T) {
requestBody := map[string]interface{}{
"query": `
query UnsupportedQuery {
unsupportedField {
id
name
}
}
`,
}
body, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to execute request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("Expected status 400, got %d", resp.StatusCode)
}
// Parse response
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}
// Should have errors
if errors, exists := response["errors"]; !exists {
t.Error("Expected errors in response")
} else {
errorList, ok := errors.([]interface{})
if !ok || len(errorList) == 0 {
t.Error("Expected non-empty error list")
} else {
// Check error message
if errorMap, ok := errorList[0].(map[string]interface{}); ok {
if message, ok := errorMap["message"].(string); ok {
if message != "Query not supported" {
t.Errorf("Expected 'Query not supported', got '%s'", message)
}
}
}
}
}
})
t.Run("CreateProjectMutation", func(t *testing.T) {
requestBody := map[string]interface{}{
"query": `
mutation CreateProject($name: String!, $description: String, $ownerId: String!) {
createProject(input: {name: $name, description: $description, ownerId: $ownerId}) {
id
name
description
ownerId
}
}
`,
"variables": map[string]interface{}{
"name": "Test Project",
"description": "A test project",
"ownerId": "test-owner-id",
},
}
body, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/graphql", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, err := app.Test(req)
if err != nil {
t.Fatalf("Failed to execute request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
// Parse response
var response map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
t.Fatalf("Failed to decode response: %v", err)
}
// Should have data
if data, exists := response["data"]; !exists {
t.Error("Expected data in response")
} else {
dataMap, ok := data.(map[string]interface{})
if !ok {
t.Error("Expected data to be an object")
} else {
if createProject, exists := dataMap["createProject"]; !exists {
t.Error("Expected 'createProject' field in data")
} else {
projectMap, ok := createProject.(map[string]interface{})
if !ok {
t.Error("Expected 'createProject' to be an object")
} else {
// Check for required fields
if id, exists := projectMap["id"]; !exists || id == "" {
t.Error("Expected non-empty 'id' field")
}
if name, exists := projectMap["name"]; !exists || name != "Test Project" {
t.Errorf("Expected name 'Test Project', got '%v'", name)
}
if ownerId, exists := projectMap["ownerId"]; !exists || ownerId != "test-owner-id" {
t.Errorf("Expected ownerId 'test-owner-id', got '%v'", ownerId)
}
}
}
}
}
})
}

View File

@@ -0,0 +1,376 @@
package unit
import (
"context"
"omega-server/local/model"
"testing"
"gorm.io/gorm"
)
// UnitTestUtils provides utilities for unit testing
type UnitTestUtils struct {
DB *gorm.DB
}
// NewUnitTestUtils creates a new unit test utilities instance
func NewUnitTestUtils(db *gorm.DB) *UnitTestUtils {
return &UnitTestUtils{
DB: db,
}
}
// MockUser creates a mock user for testing
func (utu *UnitTestUtils) MockUser(id, email, fullName string) *model.User {
user := &model.User{
BaseModel: model.BaseModel{
ID: id,
},
Email: email,
FullName: fullName,
}
user.Init()
return user
}
// MockRole creates a mock role for testing
func (utu *UnitTestUtils) MockRole(id, name, description string) *model.Role {
role := &model.Role{
BaseModel: model.BaseModel{
ID: id,
},
Name: name,
Description: description,
Active: true,
System: false,
}
role.Init()
return role
}
// MockPermission creates a mock permission for testing
func (utu *UnitTestUtils) MockPermission(id, name, description, category string) *model.Permission {
permission := &model.Permission{
BaseModel: model.BaseModel{
ID: id,
},
Name: name,
Description: description,
Category: category,
Active: true,
System: false,
}
permission.Init()
return permission
}
// MockType creates a mock project type for testing
func (utu *UnitTestUtils) MockType(id, name, description string, userID *string) *model.Type {
projectType := &model.Type{
BaseModel: model.BaseModel{
ID: id,
},
Name: name,
Description: description,
UserID: userID,
}
projectType.Init()
return projectType
}
// MockProject creates a mock project for testing
func (utu *UnitTestUtils) MockProject(id, name, description, ownerID, typeID string) *model.Project {
project := &model.Project{
BaseModel: model.BaseModel{
ID: id,
},
Name: name,
Description: description,
OwnerID: ownerID,
TypeID: typeID,
}
project.Init()
return project
}
// MockTask creates a mock task for testing
func (utu *UnitTestUtils) MockTask(id, title, description, projectID string) *model.Task {
task := &model.Task{
BaseModel: model.BaseModel{
ID: id,
},
Title: title,
Description: description,
Status: model.TaskStatusTodo,
Priority: model.TaskPriorityMedium,
ProjectID: projectID,
}
task.Init()
return task
}
// MockIntegration creates a mock integration for testing
func (utu *UnitTestUtils) MockIntegration(id, projectID, integrationType string, config map[string]interface{}) *model.Integration {
integration := &model.Integration{
BaseModel: model.BaseModel{
ID: id,
},
ProjectID: projectID,
Type: integrationType,
}
integration.Init()
integration.SetConfig(config)
return integration
}
// AssertUserEqual asserts that two users are equal
func (utu *UnitTestUtils) AssertUserEqual(t *testing.T, expected, actual *model.User) {
if expected.ID != actual.ID {
t.Errorf("Expected user ID %s, got %s", expected.ID, actual.ID)
}
if expected.Email != actual.Email {
t.Errorf("Expected user email %s, got %s", expected.Email, actual.Email)
}
if expected.FullName != actual.FullName {
t.Errorf("Expected user full name %s, got %s", expected.FullName, actual.FullName)
}
}
// AssertProjectEqual asserts that two projects are equal
func (utu *UnitTestUtils) AssertProjectEqual(t *testing.T, expected, actual *model.Project) {
if expected.ID != actual.ID {
t.Errorf("Expected project ID %s, got %s", expected.ID, actual.ID)
}
if expected.Name != actual.Name {
t.Errorf("Expected project name %s, got %s", expected.Name, actual.Name)
}
if expected.Description != actual.Description {
t.Errorf("Expected project description %s, got %s", expected.Description, actual.Description)
}
if expected.OwnerID != actual.OwnerID {
t.Errorf("Expected project owner ID %s, got %s", expected.OwnerID, actual.OwnerID)
}
if expected.TypeID != actual.TypeID {
t.Errorf("Expected project type ID %s, got %s", expected.TypeID, actual.TypeID)
}
}
// AssertTaskEqual asserts that two tasks are equal
func (utu *UnitTestUtils) AssertTaskEqual(t *testing.T, expected, actual *model.Task) {
if expected.ID != actual.ID {
t.Errorf("Expected task ID %s, got %s", expected.ID, actual.ID)
}
if expected.Title != actual.Title {
t.Errorf("Expected task title %s, got %s", expected.Title, actual.Title)
}
if expected.Description != actual.Description {
t.Errorf("Expected task description %s, got %s", expected.Description, actual.Description)
}
if expected.Status != actual.Status {
t.Errorf("Expected task status %s, got %s", expected.Status, actual.Status)
}
if expected.Priority != actual.Priority {
t.Errorf("Expected task priority %s, got %s", expected.Priority, actual.Priority)
}
if expected.ProjectID != actual.ProjectID {
t.Errorf("Expected task project ID %s, got %s", expected.ProjectID, actual.ProjectID)
}
}
// AssertRoleEqual asserts that two roles are equal
func (utu *UnitTestUtils) AssertRoleEqual(t *testing.T, expected, actual *model.Role) {
if expected.ID != actual.ID {
t.Errorf("Expected role ID %s, got %s", expected.ID, actual.ID)
}
if expected.Name != actual.Name {
t.Errorf("Expected role name %s, got %s", expected.Name, actual.Name)
}
if expected.Description != actual.Description {
t.Errorf("Expected role description %s, got %s", expected.Description, actual.Description)
}
if expected.Active != actual.Active {
t.Errorf("Expected role active %t, got %t", expected.Active, actual.Active)
}
if expected.System != actual.System {
t.Errorf("Expected role system %t, got %t", expected.System, actual.System)
}
}
// AssertPermissionEqual asserts that two permissions are equal
func (utu *UnitTestUtils) AssertPermissionEqual(t *testing.T, expected, actual *model.Permission) {
if expected.ID != actual.ID {
t.Errorf("Expected permission ID %s, got %s", expected.ID, actual.ID)
}
if expected.Name != actual.Name {
t.Errorf("Expected permission name %s, got %s", expected.Name, actual.Name)
}
if expected.Description != actual.Description {
t.Errorf("Expected permission description %s, got %s", expected.Description, actual.Description)
}
if expected.Category != actual.Category {
t.Errorf("Expected permission category %s, got %s", expected.Category, actual.Category)
}
if expected.Active != actual.Active {
t.Errorf("Expected permission active %t, got %t", expected.Active, actual.Active)
}
if expected.System != actual.System {
t.Errorf("Expected permission system %t, got %t", expected.System, actual.System)
}
}
// AssertTypeEqual asserts that two types are equal
func (utu *UnitTestUtils) AssertTypeEqual(t *testing.T, expected, actual *model.Type) {
if expected.ID != actual.ID {
t.Errorf("Expected type ID %s, got %s", expected.ID, actual.ID)
}
if expected.Name != actual.Name {
t.Errorf("Expected type name %s, got %s", expected.Name, actual.Name)
}
if expected.Description != actual.Description {
t.Errorf("Expected type description %s, got %s", expected.Description, actual.Description)
}
if (expected.UserID == nil) != (actual.UserID == nil) {
t.Errorf("Expected type user ID nullability mismatch")
} else if expected.UserID != nil && actual.UserID != nil && *expected.UserID != *actual.UserID {
t.Errorf("Expected type user ID %s, got %s", *expected.UserID, *actual.UserID)
}
}
// AssertIntegrationEqual asserts that two integrations are equal
func (utu *UnitTestUtils) AssertIntegrationEqual(t *testing.T, expected, actual *model.Integration) {
if expected.ID != actual.ID {
t.Errorf("Expected integration ID %s, got %s", expected.ID, actual.ID)
}
if expected.ProjectID != actual.ProjectID {
t.Errorf("Expected integration project ID %s, got %s", expected.ProjectID, actual.ProjectID)
}
if expected.Type != actual.Type {
t.Errorf("Expected integration type %s, got %s", expected.Type, actual.Type)
}
}
// CreateTestContext creates a test context
func (utu *UnitTestUtils) CreateTestContext() context.Context {
return context.Background()
}
// AssertNoError asserts that there is no error
func (utu *UnitTestUtils) AssertNoError(t *testing.T, err error) {
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}
}
// AssertError asserts that there is an error
func (utu *UnitTestUtils) AssertError(t *testing.T, err error) {
if err == nil {
t.Fatalf("Expected an error, but got none")
}
}
// AssertErrorMessage asserts that the error has a specific message
func (utu *UnitTestUtils) AssertErrorMessage(t *testing.T, err error, expectedMessage string) {
if err == nil {
t.Fatalf("Expected an error with message '%s', but got no error", expectedMessage)
}
if err.Error() != expectedMessage {
t.Fatalf("Expected error message '%s', but got '%s'", expectedMessage, err.Error())
}
}
// AssertStringEqual asserts that two strings are equal
func (utu *UnitTestUtils) AssertStringEqual(t *testing.T, expected, actual string) {
if expected != actual {
t.Errorf("Expected string '%s', got '%s'", expected, actual)
}
}
// AssertIntEqual asserts that two integers are equal
func (utu *UnitTestUtils) AssertIntEqual(t *testing.T, expected, actual int) {
if expected != actual {
t.Errorf("Expected int %d, got %d", expected, actual)
}
}
// AssertBoolEqual asserts that two booleans are equal
func (utu *UnitTestUtils) AssertBoolEqual(t *testing.T, expected, actual bool) {
if expected != actual {
t.Errorf("Expected bool %t, got %t", expected, actual)
}
}
// AssertNotNil asserts that a value is not nil
func (utu *UnitTestUtils) AssertNotNil(t *testing.T, value interface{}) {
if value == nil {
t.Fatalf("Expected value to not be nil, but it was")
}
}
// AssertNil asserts that a value is nil
func (utu *UnitTestUtils) AssertNil(t *testing.T, value interface{}) {
if value != nil {
t.Fatalf("Expected value to be nil, but got: %+v", value)
}
}
// AssertSliceLength asserts that a slice has a specific length
func (utu *UnitTestUtils) AssertSliceLength(t *testing.T, slice interface{}, expectedLength int) {
switch s := slice.(type) {
case []interface{}:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
case []*model.User:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
case []*model.Project:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
case []*model.Task:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
case []*model.Role:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
case []*model.Permission:
if len(s) != expectedLength {
t.Errorf("Expected slice length %d, got %d", expectedLength, len(s))
}
default:
t.Fatalf("Unsupported slice type for length assertion: %T", slice)
}
}
// AssertContains asserts that a string contains a substring
func (utu *UnitTestUtils) AssertContains(t *testing.T, str, substr string) {
if !contains(str, substr) {
t.Errorf("Expected string '%s' to contain '%s'", str, substr)
}
}
// AssertNotContains asserts that a string does not contain a substring
func (utu *UnitTestUtils) AssertNotContains(t *testing.T, str, substr string) {
if contains(str, substr) {
t.Errorf("Expected string '%s' to not contain '%s'", str, substr)
}
}
// 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
}