This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/consent-service/internal/services/dsr_service_test.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

421 lines
12 KiB
Go

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
}