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>
This commit is contained in:
420
consent-service/internal/services/dsr_service_test.go
Normal file
420
consent-service/internal/services/dsr_service_test.go
Normal file
@@ -0,0 +1,420 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user