Files
breakpilot-core/consent-service/tests/integration_test.go
Benjamin Boenisch ad111d5e69 Initial commit: breakpilot-core - Shared Infrastructure
Docker Compose with 24+ services:
- PostgreSQL (PostGIS), Valkey, MinIO, Qdrant
- Vault (PKI/TLS), Nginx (Reverse Proxy)
- Backend Core API, Consent Service, Billing Service
- RAG Service, Embedding Service
- Gitea, Woodpecker CI/CD
- Night Scheduler, Health Aggregator
- Jitsi (Web/XMPP/JVB/Jicofo), Mailpit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:13 +01:00

740 lines
17 KiB
Go

package tests
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Integration tests for the Consent Service
// These tests simulate complete user workflows
func init() {
gin.SetMode(gin.TestMode)
}
// TestFullAuthFlow tests the complete authentication workflow
func TestFullAuthFlow(t *testing.T) {
t.Log("Starting full authentication flow integration test")
// Step 1: Register a new user
t.Run("Step 1: Register User", func(t *testing.T) {
reqBody := map[string]string{
"email": "testuser@example.com",
"password": "SecurePassword123!",
"name": "Test User",
}
// Validate registration data
if reqBody["email"] == "" || reqBody["password"] == "" {
t.Fatal("Registration data incomplete")
}
// Check password strength
if len(reqBody["password"]) < 8 {
t.Error("Password too weak")
}
t.Log("User registration data validated")
})
// Step 2: Verify email
t.Run("Step 2: Verify Email", func(t *testing.T) {
token := "verification-token-123"
if token == "" {
t.Fatal("Verification token missing")
}
t.Log("Email verification simulated")
})
// Step 3: Login
t.Run("Step 3: Login", func(t *testing.T) {
loginReq := map[string]string{
"email": "testuser@example.com",
"password": "SecurePassword123!",
}
if loginReq["email"] == "" || loginReq["password"] == "" {
t.Fatal("Login credentials incomplete")
}
// Simulate successful login
accessToken := "jwt-access-token-123"
refreshToken := "jwt-refresh-token-456"
if accessToken == "" || refreshToken == "" {
t.Fatal("Tokens not generated")
}
t.Log("Login successful, tokens generated")
})
// Step 4: Setup 2FA
t.Run("Step 4: Setup 2FA", func(t *testing.T) {
// Generate TOTP secret
totpSecret := "JBSWY3DPEHPK3PXP"
if totpSecret == "" {
t.Fatal("TOTP secret not generated")
}
// Verify TOTP code
totpCode := "123456"
if len(totpCode) != 6 {
t.Error("Invalid TOTP code format")
}
t.Log("2FA setup completed")
})
// Step 5: Login with 2FA
t.Run("Step 5: Login with 2FA", func(t *testing.T) {
// First phase - email/password
challengeID := uuid.New().String()
if challengeID == "" {
t.Fatal("Challenge ID not generated")
}
// Second phase - TOTP verification
totpCode := "654321"
if len(totpCode) != 6 {
t.Error("Invalid TOTP code")
}
t.Log("2FA login flow completed")
})
// Step 6: Refresh token
t.Run("Step 6: Refresh Access Token", func(t *testing.T) {
refreshToken := "jwt-refresh-token-456"
if refreshToken == "" {
t.Fatal("Refresh token missing")
}
// Generate new access token
newAccessToken := "jwt-access-token-789"
if newAccessToken == "" {
t.Fatal("New access token not generated")
}
t.Log("Token refresh successful")
})
// Step 7: Logout
t.Run("Step 7: Logout", func(t *testing.T) {
// Revoke tokens
sessionRevoked := true
if !sessionRevoked {
t.Error("Session not properly revoked")
}
t.Log("Logout successful")
})
}
// TestDocumentLifecycle tests the complete document workflow
func TestDocumentLifecycle(t *testing.T) {
t.Log("Starting document lifecycle integration test")
var documentID uuid.UUID
var versionID uuid.UUID
// Step 1: Create document (Admin)
t.Run("Step 1: Admin Creates Document", func(t *testing.T) {
docReq := map[string]interface{}{
"type": "terms",
"name": "Terms of Service",
"description": "Our terms and conditions",
"is_mandatory": true,
}
// Validate
if docReq["type"] == "" || docReq["name"] == "" {
t.Fatal("Document data incomplete")
}
documentID = uuid.New()
t.Logf("Document created with ID: %s", documentID)
})
// Step 2: Create version (Admin)
t.Run("Step 2: Admin Creates Version", func(t *testing.T) {
versionReq := map[string]interface{}{
"document_id": documentID.String(),
"version": "1.0.0",
"language": "de",
"title": "Nutzungsbedingungen",
"content": "<h1>Terms</h1><p>Content...</p>",
"summary": "Initial version",
}
if versionReq["version"] == "" || versionReq["content"] == "" {
t.Fatal("Version data incomplete")
}
versionID = uuid.New()
t.Logf("Version created with ID: %s", versionID)
})
// Step 3: Submit for review (Admin)
t.Run("Step 3: Submit for Review", func(t *testing.T) {
currentStatus := "draft"
newStatus := "review"
if currentStatus != "draft" {
t.Error("Can only submit drafts for review")
}
t.Logf("Status changed: %s -> %s", currentStatus, newStatus)
})
// Step 4: Approve version (DSB)
t.Run("Step 4: DSB Approves Version", func(t *testing.T) {
approverRole := "data_protection_officer"
currentStatus := "review"
if approverRole != "data_protection_officer" {
t.Error("Only DSB can approve")
}
if currentStatus != "review" {
t.Error("Can only approve review versions")
}
newStatus := "approved"
t.Logf("Version approved, status: %s", newStatus)
})
// Step 5: Publish version (Admin/DSB)
t.Run("Step 5: Publish Version", func(t *testing.T) {
currentStatus := "approved"
if currentStatus != "approved" && currentStatus != "scheduled" {
t.Error("Can only publish approved/scheduled versions")
}
publishedAt := time.Now()
t.Logf("Version published at: %s", publishedAt)
})
// Step 6: User views published document
t.Run("Step 6: User Views Document", func(t *testing.T) {
language := "de"
// Fetch latest published version
if language == "" {
language = "de" // default
}
t.Log("User retrieved latest published version")
})
// Step 7: Archive old version
t.Run("Step 7: Archive Old Version", func(t *testing.T) {
status := "published"
if status != "published" {
t.Error("Can only archive published versions")
}
newStatus := "archived"
t.Logf("Version archived, status: %s", newStatus)
})
}
// TestConsentFlow tests the complete consent workflow
func TestConsentFlow(t *testing.T) {
t.Log("Starting consent flow integration test")
userID := uuid.New()
versionID := uuid.New()
// Step 1: User checks consent status
t.Run("Step 1: Check Consent Status", func(t *testing.T) {
documentType := "terms"
hasConsent := false
needsUpdate := true
if hasConsent {
t.Log("User already has consent")
}
if !needsUpdate {
t.Error("Should need consent for new document")
}
t.Logf("User %s needs consent for %s", userID, documentType)
})
// Step 2: User retrieves document details
t.Run("Step 2: Get Document Details", func(t *testing.T) {
language := "de"
if language == "" {
t.Error("Language required")
}
t.Log("Document details retrieved")
})
// Step 3: User gives consent
t.Run("Step 3: Give Consent", func(t *testing.T) {
consentReq := map[string]interface{}{
"version_id": versionID.String(),
"consented": true,
}
if consentReq["version_id"] == "" {
t.Fatal("Version ID required")
}
consentID := uuid.New()
consentedAt := time.Now()
t.Logf("Consent given, ID: %s, At: %s", consentID, consentedAt)
})
// Step 4: Verify consent recorded
t.Run("Step 4: Verify Consent", func(t *testing.T) {
hasConsent := true
needsUpdate := false
if !hasConsent {
t.Error("Consent should be recorded")
}
if needsUpdate {
t.Error("Should not need update after consent")
}
t.Log("Consent verified")
})
// Step 5: New version published
t.Run("Step 5: New Version Published", func(t *testing.T) {
newVersionID := uuid.New()
// Check if consent needs update
currentVersionID := versionID
latestVersionID := newVersionID
needsUpdate := currentVersionID != latestVersionID
if !needsUpdate {
t.Error("Should need update for new version")
}
t.Log("New version published, consent update needed")
})
// Step 6: User withdraws consent
t.Run("Step 6: Withdraw Consent", func(t *testing.T) {
withdrawnConsentID := uuid.New()
withdrawnAt := time.Now()
consented := false
if consented {
t.Error("Consent should be withdrawn")
}
t.Logf("Consent %s withdrawn at: %s", withdrawnConsentID, withdrawnAt)
})
// Step 7: User gives consent again
t.Run("Step 7: Re-consent", func(t *testing.T) {
newConsentID := uuid.New()
consented := true
if !consented {
t.Error("Should be consented")
}
t.Logf("Re-consented, ID: %s", newConsentID)
})
// Step 8: Get consent history
t.Run("Step 8: View Consent History", func(t *testing.T) {
// Fetch all consents for user
consentCount := 2 // initial + re-consent
if consentCount < 1 {
t.Error("Should have consent history")
}
t.Logf("User has %d consent records", consentCount)
})
}
// TestOAuthFlow tests the OAuth 2.0 authorization code flow
func TestOAuthFlow(t *testing.T) {
t.Log("Starting OAuth flow integration test")
clientID := "client-app-123"
_ = uuid.New() // userID simulated
// Step 1: Authorization request
t.Run("Step 1: Authorization Request", func(t *testing.T) {
authReq := map[string]string{
"response_type": "code",
"client_id": clientID,
"redirect_uri": "https://app.example.com/callback",
"scope": "read:consents write:consents",
"state": "random-state-123",
}
if authReq["response_type"] != "code" {
t.Error("Must use authorization code flow")
}
if authReq["state"] == "" {
t.Error("State required for CSRF protection")
}
t.Log("Authorization request validated")
})
// Step 2: User approves
t.Run("Step 2: User Approves", func(t *testing.T) {
approved := true
if !approved {
t.Skip("User denied authorization")
}
authCode := "auth-code-abc123"
t.Logf("Authorization code issued: %s", authCode)
})
// Step 3: Exchange code for token
t.Run("Step 3: Token Exchange", func(t *testing.T) {
tokenReq := map[string]string{
"grant_type": "authorization_code",
"code": "auth-code-abc123",
"redirect_uri": "https://app.example.com/callback",
"client_id": clientID,
}
if tokenReq["grant_type"] != "authorization_code" {
t.Error("Invalid grant type")
}
accessToken := "access-token-xyz"
refreshToken := "refresh-token-def"
expiresIn := 3600
if accessToken == "" || refreshToken == "" {
t.Fatal("Tokens not issued")
}
t.Logf("Tokens issued, expires in %d seconds", expiresIn)
})
// Step 4: Use access token
t.Run("Step 4: Access Protected Resource", func(t *testing.T) {
accessToken := "access-token-xyz"
if accessToken == "" {
t.Fatal("Access token required")
}
// Make API request
authorized := true
if !authorized {
t.Error("Token should grant access")
}
t.Log("Successfully accessed protected resource")
})
// Step 5: Refresh token
t.Run("Step 5: Refresh Access Token", func(t *testing.T) {
refreshReq := map[string]string{
"grant_type": "refresh_token",
"refresh_token": "refresh-token-def",
"client_id": clientID,
}
if refreshReq["grant_type"] != "refresh_token" {
t.Error("Invalid grant type")
}
newAccessToken := "access-token-new"
t.Logf("New access token issued: %s", newAccessToken)
})
}
// TestConsentDeadlineFlow tests consent deadline and suspension workflow
func TestConsentDeadlineFlow(t *testing.T) {
t.Log("Starting consent deadline flow test")
_ = uuid.New() // userID simulated
_ = uuid.New() // versionID simulated
// Step 1: New mandatory version published
t.Run("Step 1: Mandatory Version Published", func(t *testing.T) {
isMandatory := true
if !isMandatory {
t.Skip("Only for mandatory documents")
}
// Create deadline
deadlineAt := time.Now().AddDate(0, 0, 30)
t.Logf("Deadline created: %s (30 days)", deadlineAt)
})
// Step 2: Send reminder (14 days before)
t.Run("Step 2: First Reminder", func(t *testing.T) {
daysLeft := 14
urgency := "normal"
if daysLeft <= 7 {
urgency = "warning"
}
t.Logf("Reminder sent, %d days left, urgency: %s", daysLeft, urgency)
})
// Step 3: Send urgent reminder (3 days before)
t.Run("Step 3: Urgent Reminder", func(t *testing.T) {
daysLeft := 3
urgency := "urgent"
if daysLeft > 3 {
t.Skip("Not yet urgent")
}
t.Logf("Urgent reminder sent, urgency level: %s", urgency)
})
// Step 4: Deadline passes without consent
t.Run("Step 4: Deadline Exceeded", func(t *testing.T) {
deadlineAt := time.Now().AddDate(0, 0, -1)
hasConsent := false
isOverdue := deadlineAt.Before(time.Now())
if !isOverdue {
t.Skip("Not yet overdue")
}
if hasConsent {
t.Skip("User has consent")
}
t.Log("Deadline exceeded without consent")
})
// Step 5: Suspend account
t.Run("Step 5: Suspend Account", func(t *testing.T) {
accountStatus := "suspended"
suspensionReason := "consent_deadline_exceeded"
if accountStatus != "suspended" {
t.Error("Account should be suspended")
}
t.Logf("Account suspended: %s", suspensionReason)
})
// Step 6: User gives consent
t.Run("Step 6: User Gives Consent", func(t *testing.T) {
consentGiven := true
if !consentGiven {
t.Error("Consent should be given")
}
t.Log("Consent provided")
})
// Step 7: Lift suspension
t.Run("Step 7: Lift Suspension", func(t *testing.T) {
accountStatus := "active"
liftedAt := time.Now()
if accountStatus != "active" {
t.Error("Account should be active")
}
t.Logf("Suspension lifted at: %s", liftedAt)
})
}
// TestGDPRDataExport tests GDPR data export workflow
func TestGDPRDataExport(t *testing.T) {
t.Log("Starting GDPR data export test")
userID := uuid.New()
// Step 1: Request data export
t.Run("Step 1: Request Data Export", func(t *testing.T) {
exportID := uuid.New()
status := "pending"
if status != "pending" {
t.Error("Export should start as pending")
}
t.Logf("Export requested, ID: %s", exportID)
})
// Step 2: Process export
t.Run("Step 2: Process Export", func(t *testing.T) {
status := "processing"
// Collect user data
userData := map[string]interface{}{
"user": map[string]string{"id": userID.String(), "email": "user@example.com"},
"consents": []interface{}{},
"cookie_consents": []interface{}{},
"audit_log": []interface{}{},
}
if userData == nil {
t.Fatal("User data not collected")
}
t.Logf("Export %s", status)
})
// Step 3: Export complete
t.Run("Step 3: Export Complete", func(t *testing.T) {
status := "completed"
downloadURL := "https://example.com/exports/user-data.json"
expiresAt := time.Now().AddDate(0, 0, 7)
if status != "completed" {
t.Error("Export should be completed")
}
if downloadURL == "" {
t.Error("Download URL required")
}
t.Logf("Export complete, expires at: %s", expiresAt)
})
// Step 4: User downloads data
t.Run("Step 4: Download Export", func(t *testing.T) {
downloaded := true
if !downloaded {
t.Error("Export should be downloadable")
}
t.Log("Export downloaded")
})
}
// Helper functions for integration tests
// makeRequest simulates an HTTP request
func makeRequest(t *testing.T, method, endpoint string, body interface{}, headers map[string]string) *httptest.ResponseRecorder {
var req *http.Request
if body != nil {
jsonBody, _ := json.Marshal(body)
req, _ = http.NewRequest(method, endpoint, bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
} else {
req, _ = http.NewRequest(method, endpoint, nil)
}
// Add custom headers
for key, value := range headers {
req.Header.Set(key, value)
}
w := httptest.NewRecorder()
return w
}
// assertStatus checks HTTP status code
func assertStatus(t *testing.T, expected, actual int) {
if actual != expected {
t.Errorf("Expected status %d, got %d", expected, actual)
}
}
// assertJSONField checks a JSON field value
func assertJSONField(t *testing.T, body []byte, field string, expected interface{}) {
var response map[string]interface{}
if err := json.Unmarshal(body, &response); err != nil {
t.Fatalf("Failed to parse JSON: %v", err)
}
actual, ok := response[field]
if !ok {
t.Errorf("Field %s not found in response", field)
return
}
if actual != expected {
t.Errorf("Field %s: expected %v, got %v", field, expected, actual)
}
}
// logTestStep logs a test step with context
func logTestStep(t *testing.T, step int, description string) {
t.Logf("Step %d: %s", step, description)
}
// TestEndToEndScenario runs a complete end-to-end scenario
func TestEndToEndScenario(t *testing.T) {
t.Log("Running complete end-to-end scenario")
scenario := []struct {
step int
description string
action func(t *testing.T)
}{
{1, "User registers", func(t *testing.T) {
t.Log("User registration")
}},
{2, "User verifies email", func(t *testing.T) {
t.Log("Email verified")
}},
{3, "User logs in", func(t *testing.T) {
t.Log("User logged in")
}},
{4, "User views documents", func(t *testing.T) {
t.Log("Documents retrieved")
}},
{5, "User gives consent", func(t *testing.T) {
t.Log("Consent given")
}},
{6, "Admin publishes new version", func(t *testing.T) {
t.Log("New version published")
}},
{7, "User updates consent", func(t *testing.T) {
t.Log("Consent updated")
}},
{8, "User exports data", func(t *testing.T) {
t.Log("Data exported")
}},
}
for _, s := range scenario {
t.Run(fmt.Sprintf("Step_%d_%s", s.step, s.description), s.action)
}
t.Log("End-to-end scenario completed successfully")
}