package services import ( "testing" "time" "github.com/breakpilot/consent-service/internal/models" ) // TestDSRRequestTypeLabel tests label generation for request types func TestDSRRequestTypeLabel(t *testing.T) { tests := []struct { name string reqType models.DSRRequestType expected string }{ {"access type", models.DSRTypeAccess, "Auskunftsanfrage (Art. 15)"}, {"rectification type", models.DSRTypeRectification, "Berichtigungsanfrage (Art. 16)"}, {"erasure type", models.DSRTypeErasure, "Löschanfrage (Art. 17)"}, {"restriction type", models.DSRTypeRestriction, "Einschränkungsanfrage (Art. 18)"}, {"portability type", models.DSRTypePortability, "Datenübertragung (Art. 20)"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.reqType.Label() if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } } // TestDSRRequestTypeDeadlineDays tests deadline calculation for different request types func TestDSRRequestTypeDeadlineDays(t *testing.T) { tests := []struct { name string reqType models.DSRRequestType expectedDays int }{ {"access has 30 days", models.DSRTypeAccess, 30}, {"portability has 30 days", models.DSRTypePortability, 30}, {"rectification has 14 days", models.DSRTypeRectification, 14}, {"erasure has 14 days", models.DSRTypeErasure, 14}, {"restriction has 14 days", models.DSRTypeRestriction, 14}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.reqType.DeadlineDays() if result != tt.expectedDays { t.Errorf("Expected %d days, got %d", tt.expectedDays, result) } }) } } // TestDSRRequestTypeIsExpedited tests expedited flag for request types func TestDSRRequestTypeIsExpedited(t *testing.T) { tests := []struct { name string reqType models.DSRRequestType isExpedited bool }{ {"access not expedited", models.DSRTypeAccess, false}, {"portability not expedited", models.DSRTypePortability, false}, {"rectification is expedited", models.DSRTypeRectification, true}, {"erasure is expedited", models.DSRTypeErasure, true}, {"restriction is expedited", models.DSRTypeRestriction, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.reqType.IsExpedited() if result != tt.isExpedited { t.Errorf("Expected IsExpedited=%v, got %v", tt.isExpedited, result) } }) } } // TestDSRStatusLabel tests label generation for statuses func TestDSRStatusLabel(t *testing.T) { tests := []struct { name string status models.DSRStatus expected string }{ {"intake status", models.DSRStatusIntake, "Eingang"}, {"identity verification", models.DSRStatusIdentityVerification, "Identitätsprüfung"}, {"processing status", models.DSRStatusProcessing, "In Bearbeitung"}, {"completed status", models.DSRStatusCompleted, "Abgeschlossen"}, {"rejected status", models.DSRStatusRejected, "Abgelehnt"}, {"cancelled status", models.DSRStatusCancelled, "Storniert"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := tt.status.Label() if result != tt.expected { t.Errorf("Expected %s, got %s", tt.expected, result) } }) } } // TestValidDSRRequestType tests request type validation func TestValidDSRRequestType(t *testing.T) { tests := []struct { name string reqType string valid bool }{ {"valid access", "access", true}, {"valid rectification", "rectification", true}, {"valid erasure", "erasure", true}, {"valid restriction", "restriction", true}, {"valid portability", "portability", true}, {"invalid type", "invalid", false}, {"empty type", "", false}, {"random string", "delete_everything", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := models.IsValidDSRRequestType(tt.reqType) if result != tt.valid { t.Errorf("Expected IsValidDSRRequestType=%v for %s, got %v", tt.valid, tt.reqType, result) } }) } } // TestValidDSRStatus tests status validation func TestValidDSRStatus(t *testing.T) { tests := []struct { name string status string valid bool }{ {"valid intake", "intake", true}, {"valid identity_verification", "identity_verification", true}, {"valid processing", "processing", true}, {"valid completed", "completed", true}, {"valid rejected", "rejected", true}, {"valid cancelled", "cancelled", true}, {"invalid status", "invalid", false}, {"empty status", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := models.IsValidDSRStatus(tt.status) if result != tt.valid { t.Errorf("Expected IsValidDSRStatus=%v for %s, got %v", tt.valid, tt.status, result) } }) } } // TestDSRStatusTransitionValidation tests allowed status transitions func TestDSRStatusTransitionValidation(t *testing.T) { tests := []struct { name string fromStatus models.DSRStatus toStatus models.DSRStatus allowed bool }{ // From intake {"intake to identity_verification", models.DSRStatusIntake, models.DSRStatusIdentityVerification, true}, {"intake to processing", models.DSRStatusIntake, models.DSRStatusProcessing, true}, {"intake to rejected", models.DSRStatusIntake, models.DSRStatusRejected, true}, {"intake to cancelled", models.DSRStatusIntake, models.DSRStatusCancelled, true}, {"intake to completed invalid", models.DSRStatusIntake, models.DSRStatusCompleted, false}, // From identity_verification {"identity to processing", models.DSRStatusIdentityVerification, models.DSRStatusProcessing, true}, {"identity to rejected", models.DSRStatusIdentityVerification, models.DSRStatusRejected, true}, {"identity to cancelled", models.DSRStatusIdentityVerification, models.DSRStatusCancelled, true}, // From processing {"processing to completed", models.DSRStatusProcessing, models.DSRStatusCompleted, true}, {"processing to rejected", models.DSRStatusProcessing, models.DSRStatusRejected, true}, {"processing to intake invalid", models.DSRStatusProcessing, models.DSRStatusIntake, false}, // From completed {"completed to anything invalid", models.DSRStatusCompleted, models.DSRStatusProcessing, false}, // From rejected {"rejected to anything invalid", models.DSRStatusRejected, models.DSRStatusProcessing, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := testIsValidStatusTransition(tt.fromStatus, tt.toStatus) if result != tt.allowed { t.Errorf("Expected transition %s->%s allowed=%v, got %v", tt.fromStatus, tt.toStatus, tt.allowed, result) } }) } } // testIsValidStatusTransition is a test helper for validating status transitions // This mirrors the logic in dsr_service.go for testing purposes func testIsValidStatusTransition(from, to models.DSRStatus) bool { validTransitions := map[models.DSRStatus][]models.DSRStatus{ models.DSRStatusIntake: { models.DSRStatusIdentityVerification, models.DSRStatusProcessing, models.DSRStatusRejected, models.DSRStatusCancelled, }, models.DSRStatusIdentityVerification: { models.DSRStatusProcessing, models.DSRStatusRejected, models.DSRStatusCancelled, }, models.DSRStatusProcessing: { models.DSRStatusCompleted, models.DSRStatusRejected, models.DSRStatusCancelled, }, models.DSRStatusCompleted: {}, models.DSRStatusRejected: {}, models.DSRStatusCancelled: {}, } allowed, exists := validTransitions[from] if !exists { return false } for _, s := range allowed { if s == to { return true } } return false } // TestCalculateDeadline tests deadline calculation func TestCalculateDeadline(t *testing.T) { tests := []struct { name string reqType models.DSRRequestType expectedDays int }{ {"access 30 days", models.DSRTypeAccess, 30}, {"erasure 14 days", models.DSRTypeErasure, 14}, {"rectification 14 days", models.DSRTypeRectification, 14}, {"restriction 14 days", models.DSRTypeRestriction, 14}, {"portability 30 days", models.DSRTypePortability, 30}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { now := time.Now() deadline := now.AddDate(0, 0, tt.expectedDays) days := tt.reqType.DeadlineDays() if days != tt.expectedDays { t.Errorf("Expected %d days, got %d", tt.expectedDays, days) } // Verify deadline is approximately correct (within 1 day due to test timing) calculatedDeadline := now.AddDate(0, 0, days) diff := calculatedDeadline.Sub(deadline) if diff > time.Hour*24 || diff < -time.Hour*24 { t.Errorf("Deadline calculation off by more than a day") } }) } } // TestCreateDSRRequest_Validation tests validation of create request func TestCreateDSRRequest_Validation(t *testing.T) { tests := []struct { name string request models.CreateDSRRequest expectError bool }{ { name: "valid access request", request: models.CreateDSRRequest{ RequestType: "access", RequesterEmail: "test@example.com", }, expectError: false, }, { name: "valid erasure request with name", request: models.CreateDSRRequest{ RequestType: "erasure", RequesterEmail: "test@example.com", RequesterName: stringPtr("Max Mustermann"), }, expectError: false, }, { name: "missing email", request: models.CreateDSRRequest{ RequestType: "access", }, expectError: true, }, { name: "invalid request type", request: models.CreateDSRRequest{ RequestType: "invalid_type", RequesterEmail: "test@example.com", }, expectError: true, }, { name: "empty request type", request: models.CreateDSRRequest{ RequestType: "", RequesterEmail: "test@example.com", }, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := testValidateCreateDSRRequest(tt.request) hasError := err != nil if hasError != tt.expectError { t.Errorf("Expected error=%v, got error=%v (err: %v)", tt.expectError, hasError, err) } }) } } // testValidateCreateDSRRequest is a test helper for validating create DSR requests func testValidateCreateDSRRequest(req models.CreateDSRRequest) error { if req.RequesterEmail == "" { return &dsrValidationError{"requester_email is required"} } if !models.IsValidDSRRequestType(req.RequestType) { return &dsrValidationError{"invalid request_type"} } return nil } type dsrValidationError struct { Message string } func (e *dsrValidationError) Error() string { return e.Message } // TestDSRTemplateTypes tests the template types func TestDSRTemplateTypes(t *testing.T) { expectedTemplates := []string{ "dsr_receipt_access", "dsr_receipt_rectification", "dsr_receipt_erasure", "dsr_receipt_restriction", "dsr_receipt_portability", "dsr_identity_request", "dsr_processing_started", "dsr_processing_update", "dsr_clarification_request", "dsr_completed_access", "dsr_completed_rectification", "dsr_completed_erasure", "dsr_completed_restriction", "dsr_completed_portability", "dsr_restriction_lifted", "dsr_rejected_identity", "dsr_rejected_exception", "dsr_rejected_unfounded", "dsr_deadline_warning", } // This test documents the expected template types // The actual templates are created in database migration for _, template := range expectedTemplates { if template == "" { t.Error("Template type should not be empty") } } if len(expectedTemplates) != 19 { t.Errorf("Expected 19 template types, got %d", len(expectedTemplates)) } } // TestErasureExceptionTypes tests Art. 17(3) exception types func TestErasureExceptionTypes(t *testing.T) { exceptions := []struct { code string description string }{ {"art_17_3_a", "Meinungs- und Informationsfreiheit"}, {"art_17_3_b", "Rechtliche Verpflichtung"}, {"art_17_3_c", "Öffentliches Interesse im Gesundheitsbereich"}, {"art_17_3_d", "Archivzwecke, wissenschaftliche/historische Forschung"}, {"art_17_3_e", "Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen"}, } if len(exceptions) != 5 { t.Errorf("Expected 5 Art. 17(3) exceptions, got %d", len(exceptions)) } for _, ex := range exceptions { if ex.code == "" || ex.description == "" { t.Error("Exception code and description should not be empty") } } } // stringPtr returns a pointer to the given string func stringPtr(s string) *string { return &s }