package services import ( "regexp" "testing" "time" "github.com/google/uuid" ) // TestDocumentService_CreateDocument tests creating a new legal document func TestDocumentService_CreateDocument(t *testing.T) { tests := []struct { name string docType string docName string description string isMandatory bool expectError bool errorContains string }{ { name: "valid mandatory document", docType: "terms", docName: "Terms of Service", description: "Our terms and conditions", isMandatory: true, expectError: false, }, { name: "valid optional document", docType: "cookies", docName: "Cookie Policy", description: "How we use cookies", isMandatory: false, expectError: false, }, { name: "empty document type", docType: "", docName: "Test Document", description: "Test", isMandatory: true, expectError: true, errorContains: "type", }, { name: "empty document name", docType: "privacy", docName: "", description: "Test", isMandatory: true, expectError: true, errorContains: "name", }, { name: "invalid document type", docType: "invalid_type", docName: "Test", description: "Test", isMandatory: false, expectError: true, errorContains: "type", }, } validTypes := map[string]bool{ "terms": true, "privacy": true, "cookies": true, "community_guidelines": true, "imprint": true, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Validate inputs var err error if tt.docType == "" { err = &ValidationError{Field: "type", Message: "required"} } else if !validTypes[tt.docType] { err = &ValidationError{Field: "type", Message: "invalid document type"} } else if tt.docName == "" { err = &ValidationError{Field: "name", Message: "required"} } // Assert if tt.expectError { if err == nil { t.Errorf("Expected error containing '%s', got nil", tt.errorContains) } } else { if err != nil { t.Errorf("Expected no error, got %v", err) } } }) } } // TestDocumentService_UpdateDocument tests updating a document func TestDocumentService_UpdateDocument(t *testing.T) { tests := []struct { name string documentID uuid.UUID newName string newActive bool expectError bool }{ { name: "valid update", documentID: uuid.New(), newName: "Updated Name", newActive: true, expectError: false, }, { name: "deactivate document", documentID: uuid.New(), newName: "Test", newActive: false, expectError: false, }, { name: "invalid document ID", documentID: uuid.Nil, newName: "Test", newActive: true, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.documentID == uuid.Nil { err = &ValidationError{Field: "document ID", Message: "required"} } if tt.expectError && err == nil { t.Error("Expected error, got nil") } if !tt.expectError && err != nil { t.Errorf("Expected no error, got %v", err) } }) } } // TestDocumentService_CreateVersion tests creating a document version func TestDocumentService_CreateVersion(t *testing.T) { tests := []struct { name string documentID uuid.UUID version string language string title string content string expectError bool errorContains string }{ { name: "valid version - German", documentID: uuid.New(), version: "1.0.0", language: "de", title: "Nutzungsbedingungen", content: "
Content...
", expectError: false, }, { name: "valid version - English", documentID: uuid.New(), version: "1.0.0", language: "en", title: "Terms of Service", content: "Content...
", expectError: false, }, { name: "invalid version format", documentID: uuid.New(), version: "1.0", language: "de", title: "Test", content: "Content", expectError: true, errorContains: "version", }, { name: "invalid language", documentID: uuid.New(), version: "1.0.0", language: "fr", title: "Test", content: "Content", expectError: true, errorContains: "language", }, { name: "empty title", documentID: uuid.New(), version: "1.0.0", language: "de", title: "", content: "Content", expectError: true, errorContains: "title", }, { name: "empty content", documentID: uuid.New(), version: "1.0.0", language: "de", title: "Test", content: "", expectError: true, errorContains: "content", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Validate semver format (X.Y.Z pattern) validVersion := regexp.MustCompile(`^\d+\.\d+\.\d+$`).MatchString(tt.version) validLanguage := tt.language == "de" || tt.language == "en" var err error if !validVersion { err = &ValidationError{Field: "version", Message: "invalid format"} } else if !validLanguage { err = &ValidationError{Field: "language", Message: "must be 'de' or 'en'"} } else if tt.title == "" { err = &ValidationError{Field: "title", Message: "required"} } else if tt.content == "" { err = &ValidationError{Field: "content", Message: "required"} } if tt.expectError { if err == nil { t.Errorf("Expected error containing '%s', got nil", tt.errorContains) } } else { if err != nil { t.Errorf("Expected no error, got %v", err) } } }) } } // TestDocumentService_VersionStatusTransitions tests version status workflow func TestDocumentService_VersionStatusTransitions(t *testing.T) { tests := []struct { name string fromStatus string toStatus string isAllowed bool }{ // Valid transitions {"draft to review", "draft", "review", true}, {"review to approved", "review", "approved", true}, {"review to rejected", "review", "rejected", true}, {"approved to published", "approved", "published", true}, {"approved to scheduled", "approved", "scheduled", true}, {"scheduled to published", "scheduled", "published", true}, {"published to archived", "published", "archived", true}, {"rejected to draft", "rejected", "draft", true}, // Invalid transitions {"draft to published", "draft", "published", false}, {"draft to approved", "draft", "approved", false}, {"review to published", "review", "published", false}, {"published to draft", "published", "draft", false}, {"published to review", "published", "review", false}, {"archived to draft", "archived", "draft", false}, {"archived to published", "archived", "published", false}, } // Define valid transitions validTransitions := map[string][]string{ "draft": {"review"}, "review": {"approved", "rejected"}, "approved": {"published", "scheduled"}, "scheduled": {"published"}, "published": {"archived"}, "rejected": {"draft"}, "archived": {}, // terminal state } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Check if transition is allowed allowed := false if transitions, ok := validTransitions[tt.fromStatus]; ok { for _, validTo := range transitions { if validTo == tt.toStatus { allowed = true break } } } if allowed != tt.isAllowed { t.Errorf("Transition %s->%s: expected allowed=%v, got %v", tt.fromStatus, tt.toStatus, tt.isAllowed, allowed) } }) } } // TestDocumentService_PublishVersion tests publishing a version func TestDocumentService_PublishVersion(t *testing.T) { tests := []struct { name string versionID uuid.UUID currentStatus string expectError bool errorContains string }{ { name: "publish approved version", versionID: uuid.New(), currentStatus: "approved", expectError: false, }, { name: "publish scheduled version", versionID: uuid.New(), currentStatus: "scheduled", expectError: false, }, { name: "cannot publish draft", versionID: uuid.New(), currentStatus: "draft", expectError: true, errorContains: "draft", }, { name: "cannot publish review", versionID: uuid.New(), currentStatus: "review", expectError: true, errorContains: "review", }, { name: "invalid version ID", versionID: uuid.Nil, currentStatus: "approved", expectError: true, errorContains: "ID", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.versionID == uuid.Nil { err = &ValidationError{Field: "version ID", Message: "required"} } else if tt.currentStatus != "approved" && tt.currentStatus != "scheduled" { err = &ValidationError{Field: "status", Message: "only approved or scheduled versions can be published"} } if tt.expectError { if err == nil { t.Errorf("Expected error containing '%s', got nil", tt.errorContains) } } else { if err != nil { t.Errorf("Expected no error, got %v", err) } } }) } } // TestDocumentService_ArchiveVersion tests archiving a version func TestDocumentService_ArchiveVersion(t *testing.T) { tests := []struct { name string versionID uuid.UUID expectError bool }{ { name: "archive valid version", versionID: uuid.New(), expectError: false, }, { name: "invalid version ID", versionID: uuid.Nil, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.versionID == uuid.Nil { err = &ValidationError{Field: "version ID", Message: "required"} } if tt.expectError && err == nil { t.Error("Expected error, got nil") } if !tt.expectError && err != nil { t.Errorf("Expected no error, got %v", err) } }) } } // TestDocumentService_DeleteVersion tests deleting a version func TestDocumentService_DeleteVersion(t *testing.T) { tests := []struct { name string versionID uuid.UUID status string canDelete bool expectError bool }{ { name: "delete draft version", versionID: uuid.New(), status: "draft", canDelete: true, expectError: false, }, { name: "delete rejected version", versionID: uuid.New(), status: "rejected", canDelete: true, expectError: false, }, { name: "cannot delete published version", versionID: uuid.New(), status: "published", canDelete: false, expectError: true, }, { name: "cannot delete approved version", versionID: uuid.New(), status: "approved", canDelete: false, expectError: true, }, { name: "cannot delete archived version", versionID: uuid.New(), status: "archived", canDelete: false, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Only draft and rejected can be deleted canDelete := tt.status == "draft" || tt.status == "rejected" var err error if !canDelete { err = &ValidationError{Field: "status", Message: "only draft or rejected versions can be deleted"} } if tt.expectError { if err == nil { t.Error("Expected error, got nil") } } else { if err != nil { t.Errorf("Expected no error, got %v", err) } } if canDelete != tt.canDelete { t.Errorf("Expected canDelete=%v, got %v", tt.canDelete, canDelete) } }) } } // TestDocumentService_GetLatestVersion tests retrieving the latest version func TestDocumentService_GetLatestVersion(t *testing.T) { tests := []struct { name string documentID uuid.UUID language string status string expectError bool }{ { name: "get latest German version", documentID: uuid.New(), language: "de", status: "published", expectError: false, }, { name: "get latest English version", documentID: uuid.New(), language: "en", status: "published", expectError: false, }, { name: "invalid document ID", documentID: uuid.Nil, language: "de", status: "published", expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.documentID == uuid.Nil { err = &ValidationError{Field: "document ID", Message: "required"} } if tt.expectError && err == nil { t.Error("Expected error, got nil") } if !tt.expectError && err != nil { t.Errorf("Expected no error, got %v", err) } }) } } // TestDocumentService_CompareVersions tests version comparison func TestDocumentService_CompareVersions(t *testing.T) { tests := []struct { name string version1 string version2 string isDifferent bool }{ { name: "same version", version1: "1.0.0", version2: "1.0.0", isDifferent: false, }, { name: "different major version", version1: "2.0.0", version2: "1.0.0", isDifferent: true, }, { name: "different minor version", version1: "1.1.0", version2: "1.0.0", isDifferent: true, }, { name: "different patch version", version1: "1.0.1", version2: "1.0.0", isDifferent: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { isDifferent := tt.version1 != tt.version2 if isDifferent != tt.isDifferent { t.Errorf("Expected isDifferent=%v, got %v", tt.isDifferent, isDifferent) } }) } } // TestDocumentService_ScheduledPublishing tests scheduled publishing func TestDocumentService_ScheduledPublishing(t *testing.T) { now := time.Now() tests := []struct { name string scheduledAt time.Time shouldPublish bool }{ { name: "scheduled for past - should publish", scheduledAt: now.Add(-1 * time.Hour), shouldPublish: true, }, { name: "scheduled for now - should publish", scheduledAt: now, shouldPublish: true, }, { name: "scheduled for future - should not publish", scheduledAt: now.Add(1 * time.Hour), shouldPublish: false, }, { name: "scheduled for tomorrow - should not publish", scheduledAt: now.AddDate(0, 0, 1), shouldPublish: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { shouldPublish := tt.scheduledAt.Before(now) || tt.scheduledAt.Equal(now) if shouldPublish != tt.shouldPublish { t.Errorf("Expected shouldPublish=%v, got %v", tt.shouldPublish, shouldPublish) } }) } } // TestDocumentService_ApprovalWorkflow tests the approval workflow func TestDocumentService_ApprovalWorkflow(t *testing.T) { tests := []struct { name string action string userRole string isAllowed bool }{ // Admin permissions {"admin submit for review", "submit_review", "admin", true}, {"admin cannot approve", "approve", "admin", false}, {"admin can publish", "publish", "admin", true}, // DSB permissions {"dsb can approve", "approve", "data_protection_officer", true}, {"dsb can reject", "reject", "data_protection_officer", true}, {"dsb can publish", "publish", "data_protection_officer", true}, // User permissions {"user cannot submit", "submit_review", "user", false}, {"user cannot approve", "approve", "user", false}, {"user cannot publish", "publish", "user", false}, } permissions := map[string]map[string]bool{ "admin": { "submit_review": true, "approve": false, "reject": false, "publish": true, }, "data_protection_officer": { "submit_review": true, "approve": true, "reject": true, "publish": true, }, "user": { "submit_review": false, "approve": false, "reject": false, "publish": false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rolePerms, ok := permissions[tt.userRole] if !ok { t.Fatalf("Unknown role: %s", tt.userRole) } isAllowed := rolePerms[tt.action] if isAllowed != tt.isAllowed { t.Errorf("Role %s action %s: expected allowed=%v, got %v", tt.userRole, tt.action, tt.isAllowed, isAllowed) } }) } } // TestDocumentService_FourEyesPrinciple tests the four-eyes principle func TestDocumentService_FourEyesPrinciple(t *testing.T) { tests := []struct { name string createdBy uuid.UUID approver uuid.UUID approverRole string canApprove bool }{ { name: "different users - DSB can approve", createdBy: uuid.New(), approver: uuid.New(), approverRole: "data_protection_officer", canApprove: true, }, { name: "same user - DSB cannot approve own", createdBy: uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"), approver: uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"), approverRole: "data_protection_officer", canApprove: false, }, { name: "same user - admin CAN approve own (exception)", createdBy: uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"), approver: uuid.MustParse("123e4567-e89b-12d3-a456-426614174000"), approverRole: "admin", canApprove: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Four-eyes principle: DSB cannot approve their own work // Exception: Admins can (for development/testing) canApprove := tt.createdBy != tt.approver || tt.approverRole == "admin" if canApprove != tt.canApprove { t.Errorf("Expected canApprove=%v, got %v", tt.canApprove, canApprove) } }) } }