Integration Tests
This directory contains end-to-end integration tests for the Engine application. These tests verify complete user workflows with real HTTP requests, database interactions, and middleware.
Overview
Integration tests differ from unit tests:
- Unit Tests: Test individual functions in isolation with mocked dependencies
- Integration Tests: Test complete workflows with real HTTP server, database, CSRF, sessions, and middleware
Test Structure
tests/integration/
├── README.md # This file
├── helper.go # Shared test infrastructure and utilities
├── admin_flow_test.go # Admin user management integration tests (10 tests)
└── password_reset_flow_test.go # Password reset flow integration tests (8 tests)
Additional Integration Tests (in package directories):
internal/auth/integration_test.go # Auth flow tests (registration, login, logout, CSRF)
internal/twofa/integration_test.go # 2FA flow tests (setup, login, recovery codes, disable)
Test Coverage
1. Admin Flow Tests (admin_flow_test.go)
Complete admin panel functionality with HTTP requests:
| Test |
Description |
TestAdminFlow_CompleteUserManagement |
Full admin workflow: dashboard, list users, search, view details, edit user, verify logging |
TestAdminFlow_NonAdminAccessDenied |
Regular users cannot access admin routes (403 Forbidden) |
TestAdminFlow_PaginationWorks |
User list pagination with page/limit parameters |
TestAdminFlow_FilterByRole |
Filter users by role (admin/customer) |
TestAdminFlow_SortByCreatedAt |
Sort users by creation date (asc/desc) |
TestAdminFlow_DeactivateUser |
Deactivate user and verify they cannot login |
TestAdminFlow_ViewUserSessions |
View all sessions for a user |
TestAdminFlow_TerminateUserSession |
Admin terminates user session, verify access revoked |
TestAdminFlow_AuditLogRetention |
Verify admin actions are logged with timestamp and details |
TestAdminFlow_StatisticsAccuracy |
Admin dashboard displays correct user/session statistics |
Total: 10 admin integration tests
2. Password Reset Flow Tests (password_reset_flow_test.go)
Complete password reset workflow via HTTP:
| Test |
Description |
TestPasswordResetFlow_Complete |
Full flow: request reset → receive token → reset password → login with new password |
TestPasswordResetFlow_ExpiredToken |
Expired tokens are rejected with error message |
TestPasswordResetFlow_UsedToken |
Used tokens cannot be reused |
TestPasswordResetFlow_InvalidToken |
Invalid/non-existent tokens show error |
TestPasswordResetFlow_TokenCleanup |
Cleanup job deletes expired tokens, keeps valid ones |
TestPasswordResetFlow_NonexistentEmail |
Reset request for non-existent email succeeds (security: don't reveal user existence) |
TestPasswordResetFlow_WeakPasswordRejected |
Password validation rejects weak passwords |
TestPasswordResetFlow_PasswordMismatch |
Password confirmation must match |
Total: 8 password reset integration tests
3. Auth Flow Tests (internal/auth/integration_test.go)
Authentication workflows:
- Registration (valid, duplicate username/email, invalid data)
- Login (username/email, invalid credentials, inactive user, remember me)
- Logout (session cleanup)
- Protected routes (access control, expired sessions)
- CSRF protection (missing/invalid/valid tokens)
Total: ~15 auth integration tests
4. 2FA Flow Tests (internal/twofa/integration_test.go)
Two-factor authentication workflows:
- 2FA setup (complete flow, invalid code, already enabled)
- 2FA login (TOTP code, invalid code)
- Recovery codes (login with code, single-use enforcement, regeneration)
- 2FA disable (with password confirmation)
Total: ~12 2FA integration tests
Setup
Prerequisites
- PostgreSQL Database: Integration tests require a real PostgreSQL database
- Test Database: Create a separate database for testing (never use production!)
- Environment Variable: Set
TEST_DATABASE_URL or DATABASE_URL
Database Setup
# Create test database
createdb engine_test
# Set environment variable
export TEST_DATABASE_URL="postgresql://postgres:password@localhost:5432/engine_test?sslmode=disable"
# Or use DATABASE_URL as fallback
export DATABASE_URL="postgresql://postgres:password@localhost:5432/engine_test?sslmode=disable"
# Run migrations
migrate -path ./migrations -database "$TEST_DATABASE_URL" up
Important: Integration tests will DELETE ALL DATA from the test database. Never point TEST_DATABASE_URL at a production or development database.
Running Tests
Run All Integration Tests
# Run all integration tests in tests/integration/
go test ./tests/integration/... -v
# Run with coverage
go test ./tests/integration/... -coverprofile=integration-coverage.out -v
# View coverage report
go tool cover -html=integration-coverage.out
Run Specific Test Files
# Admin flow tests only
go test ./tests/integration/admin_flow_test.go ./tests/integration/helper.go -v
# Password reset tests only
go test ./tests/integration/password_reset_flow_test.go ./tests/integration/helper.go -v
Run Specific Tests
# Single test
go test ./tests/integration/... -run TestAdminFlow_CompleteUserManagement -v
# Tests matching pattern
go test ./tests/integration/... -run "Admin.*Deactivate" -v
Run All Integration Tests (Including Package Tests)
# Run auth integration tests
go test ./internal/auth/... -run Integration -v
# Run 2FA integration tests
go test ./internal/twofa/... -run Integration -v
# Run all tests with "Integration" in name
go test ./... -run Integration -v
Run with Timeout
Integration tests may take longer than unit tests:
# Increase timeout to 5 minutes
go test ./tests/integration/... -v -timeout 5m
Test Infrastructure
helper.go
Provides shared utilities for integration tests:
Core Functions:
SetupTestServer(t) - Creates HTTP test server with all routes, middleware, and services
CleanupTestData(t, db) - Deletes all test data from database (sessions, users, logs, etc.)
CreateTestUser(t, db, username, email, password, role) - Creates user directly in DB
CreateTestUserWithSession(t, authService, sessionService, username, role) - Creates user and session
CreateHTTPClient() - HTTP client with cookie jar for session persistence
Helper Functions:
ExtractCSRFToken(html) - Extracts CSRF token from HTML forms
GetSessionCookie(resp) - Gets session cookie from HTTP response
ReadResponseBody(resp) - Reads response body as string
Adapters:
twofaServiceAdapter - Adapts twofa.Service to auth.TwofaService interface
authServiceAdapter - Adapts auth.Service for handlers
activityServiceAdapter - Adapts activity.Service for logging
Best Practices
1. Test Isolation
Each test should:
- Call
CleanupTestData(t, server.DB) in defer to ensure cleanup
- Be independent and not rely on other tests
- Create its own test data
func TestSomething(t *testing.T) {
server := SetupTestServer(t)
defer server.Close()
defer CleanupTestData(t, server.DB)
// Test implementation
}
2. HTTP Request Pattern
// Create client with cookie jar (persists sessions)
client := CreateHTTPClient()
// Make request
req, _ := http.NewRequest("GET", server.URL+"/admin/users", nil)
req.AddCookie(&http.Cookie{Name: auth.SessionCookieName, Value: sessionToken})
resp, err := client.Do(req)
require.NoError(t, err)
defer resp.Body.Close()
// Validate response
assert.Equal(t, http.StatusOK, resp.StatusCode)
body := ReadResponseBody(resp)
assert.Contains(t, body, "expected content")
3. CSRF Token Handling
All POST requests require CSRF tokens:
// 1. GET form to extract CSRF token
resp, _ := client.Get(server.URL + "/auth/login")
body := ReadResponseBody(resp)
csrfToken := ExtractCSRFToken(body)
resp.Body.Close()
// 2. POST with CSRF token
formData := url.Values{
"csrf_token": {csrfToken},
"username": {"testuser"},
"password": {"password123"},
}
resp, _ = client.PostForm(server.URL+"/auth/login", formData)
4. Session Management
// Create user with session
userID, sessionToken := CreateTestUserWithSession(t, server.AuthService, server.SessionService, "testuser", "customer")
// Use session in requests
req.AddCookie(&http.Cookie{
Name: auth.SessionCookieName,
Value: sessionToken,
})
5. Database Verification
Always verify database state after actions:
// Verify user is deactivated
var isActive bool
err := server.DB.QueryRow("SELECT is_active FROM users WHERE id = $1", userID).Scan(&isActive)
require.NoError(t, err)
assert.False(t, isActive, "User should be deactivated")
// Verify session is deleted
var count int
err = server.DB.QueryRow("SELECT COUNT(*) FROM sessions WHERE token = $1", token).Scan(&count)
require.NoError(t, err)
assert.Equal(t, 0, count, "Session should be deleted")
Troubleshooting
Tests Skip with "TEST_DATABASE_URL not set"
Solution: Set the environment variable:
export TEST_DATABASE_URL="postgresql://user:password@localhost:5432/engine_test?sslmode=disable"
go test ./tests/integration/... -v
Database Connection Fails
Possible Issues:
- PostgreSQL not running:
brew services start postgresql (macOS)
- Wrong credentials: Check username/password in connection string
- Database doesn't exist:
createdb engine_test
- Migrations not run:
migrate -path ./migrations -database "$TEST_DATABASE_URL" up
CSRF Validation Fails
Cause: CSRF token not extracted or not included in POST request
Solution: Always GET form first, extract token, then POST with token:
resp, _ := client.Get(server.URL + "/form")
csrfToken := ExtractCSRFToken(ReadResponseBody(resp))
resp.Body.Close()
formData := url.Values{"csrf_token": {csrfToken}, ...}
client.PostForm(server.URL + "/submit", formData)
Session Not Persisted
Cause: Not using HTTP client with cookie jar
Solution: Use CreateHTTPClient() which includes cookie jar:
client := CreateHTTPClient() // ✓ Correct - persists cookies
// NOT: client := &http.Client{} ✗ Wrong - no cookie jar
Template Loading Fails
Cause: Template paths are relative to test execution directory
Note: Some tests may skip if templates fail to load. This is usually harmless for tests that don't render full HTML. Check logs for warnings:
Warning: Failed to load templates: <error> - some tests may fail
Foreign Key Constraint Violations During Cleanup
Cause: Wrong cleanup order (child records deleted before parent)
Solution: CleanupTestData deletes in correct order:
tables := []string{
"sessions", // Child (references users)
"two_factor_recovery_codes", // Child (references users)
"admin_logs", // Child (references users)
"user_activity_log", // Child (references users)
"password_reset_tokens", // Child (references users)
"users", // Parent
}
CI/CD Integration
GitHub Actions Example
name: Integration Tests
on: [push, pull_request]
jobs:
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: engine_test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Run migrations
run: |
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
migrate -path ./migrations -database "postgresql://postgres:postgres@localhost:5432/engine_test?sslmode=disable" up
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/engine_test?sslmode=disable
- name: Run integration tests
run: go test ./tests/integration/... -v -timeout 5m
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/engine_test?sslmode=disable
- name: Run package integration tests
run: |
go test ./internal/auth/... -run Integration -v
go test ./internal/twofa/... -run Integration -v
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/engine_test?sslmode=disable
Summary
This integration test suite provides comprehensive coverage of:
- ✅ Admin user management (10 tests) - User CRUD, sessions, access control, audit logging
- ✅ Password reset flows (8 tests) - Complete reset workflow, token validation, security
- ✅ Authentication flows (~15 tests) - Registration, login, logout, CSRF, session management
- ✅ Two-factor authentication (~12 tests) - Setup, login, recovery codes, disable
Total: 45+ integration tests covering all major user journeys
All tests use real database, HTTP server, middleware (CSRF, auth, 2FA, admin), and session management to verify end-to-end functionality.