package services import ( "testing" "time" "github.com/google/uuid" ) // TestDeadlineService_CreateDeadline tests creating consent deadlines func TestDeadlineService_CreateDeadline(t *testing.T) { tests := []struct { name string userID uuid.UUID versionID uuid.UUID deadlineAt time.Time expectError bool }{ { name: "valid deadline - 30 days", userID: uuid.New(), versionID: uuid.New(), deadlineAt: time.Now().AddDate(0, 0, 30), expectError: false, }, { name: "valid deadline - 14 days", userID: uuid.New(), versionID: uuid.New(), deadlineAt: time.Now().AddDate(0, 0, 14), expectError: false, }, { name: "invalid user ID", userID: uuid.Nil, versionID: uuid.New(), deadlineAt: time.Now().AddDate(0, 0, 30), expectError: true, }, { name: "invalid version ID", userID: uuid.New(), versionID: uuid.Nil, deadlineAt: time.Now().AddDate(0, 0, 30), expectError: true, }, { name: "deadline in past", userID: uuid.New(), versionID: uuid.New(), deadlineAt: time.Now().AddDate(0, 0, -1), expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.userID == uuid.Nil { err = &ValidationError{Field: "user ID", Message: "required"} } else if tt.versionID == uuid.Nil { err = &ValidationError{Field: "version ID", Message: "required"} } else if tt.deadlineAt.Before(time.Now()) { err = &ValidationError{Field: "deadline", Message: "must be in the future"} } if tt.expectError && err == nil { t.Error("Expected error, got nil") } if !tt.expectError && err != nil { t.Errorf("Expected no error, got %v", err) } }) } } // TestDeadlineService_CheckDeadlineStatus tests deadline status checking func TestDeadlineService_CheckDeadlineStatus(t *testing.T) { now := time.Now() tests := []struct { name string deadlineAt time.Time isOverdue bool daysLeft int urgency string }{ { name: "30 days left", deadlineAt: now.AddDate(0, 0, 30), isOverdue: false, daysLeft: 30, urgency: "normal", }, { name: "7 days left - warning", deadlineAt: now.AddDate(0, 0, 7), isOverdue: false, daysLeft: 7, urgency: "warning", }, { name: "3 days left - urgent", deadlineAt: now.AddDate(0, 0, 3), isOverdue: false, daysLeft: 3, urgency: "urgent", }, { name: "1 day left - critical", deadlineAt: now.AddDate(0, 0, 1), isOverdue: false, daysLeft: 1, urgency: "critical", }, { name: "overdue by 1 day", deadlineAt: now.AddDate(0, 0, -1), isOverdue: true, daysLeft: -1, urgency: "overdue", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { isOverdue := tt.deadlineAt.Before(now) daysLeft := int(tt.deadlineAt.Sub(now).Hours() / 24) var urgency string if isOverdue { urgency = "overdue" } else if daysLeft <= 1 { urgency = "critical" } else if daysLeft <= 3 { urgency = "urgent" } else if daysLeft <= 7 { urgency = "warning" } else { urgency = "normal" } if isOverdue != tt.isOverdue { t.Errorf("Expected isOverdue=%v, got %v", tt.isOverdue, isOverdue) } if abs(daysLeft-tt.daysLeft) > 1 { // Allow 1 day difference t.Errorf("Expected daysLeft=%d, got %d", tt.daysLeft, daysLeft) } if urgency != tt.urgency { t.Errorf("Expected urgency=%s, got %s", tt.urgency, urgency) } }) } } // TestDeadlineService_SendReminders tests reminder scheduling func TestDeadlineService_SendReminders(t *testing.T) { now := time.Now() tests := []struct { name string deadlineAt time.Time lastReminderAt *time.Time reminderCount int shouldSend bool nextReminder int // days before deadline }{ { name: "first reminder - 14 days before", deadlineAt: now.AddDate(0, 0, 14), lastReminderAt: nil, reminderCount: 0, shouldSend: true, nextReminder: 14, }, { name: "second reminder - 7 days before", deadlineAt: now.AddDate(0, 0, 7), lastReminderAt: ptrTime(now.AddDate(0, 0, -7)), reminderCount: 1, shouldSend: true, nextReminder: 7, }, { name: "third reminder - 3 days before", deadlineAt: now.AddDate(0, 0, 3), lastReminderAt: ptrTime(now.AddDate(0, 0, -4)), reminderCount: 2, shouldSend: true, nextReminder: 3, }, { name: "final reminder - 1 day before", deadlineAt: now.AddDate(0, 0, 1), lastReminderAt: ptrTime(now.AddDate(0, 0, -2)), reminderCount: 3, shouldSend: true, nextReminder: 1, }, { name: "too soon for next reminder", deadlineAt: now.AddDate(0, 0, 10), lastReminderAt: ptrTime(now.AddDate(0, 0, -1)), reminderCount: 1, shouldSend: false, nextReminder: 0, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { daysUntilDeadline := int(tt.deadlineAt.Sub(now).Hours() / 24) // Reminder schedule: 14, 7, 3, 1 days before deadline reminderDays := []int{14, 7, 3, 1} shouldSend := false for _, day := range reminderDays { if daysUntilDeadline == day { // Check if enough time passed since last reminder if tt.lastReminderAt == nil || now.Sub(*tt.lastReminderAt) > 12*time.Hour { shouldSend = true break } } } if shouldSend != tt.shouldSend { t.Errorf("Expected shouldSend=%v, got %v", tt.shouldSend, shouldSend) } }) } } // TestDeadlineService_SuspendAccount tests account suspension logic func TestDeadlineService_SuspendAccount(t *testing.T) { tests := []struct { name string userID uuid.UUID reason string shouldSuspend bool expectError bool }{ { name: "suspend for missed deadline", userID: uuid.New(), reason: "consent_deadline_exceeded", shouldSuspend: true, expectError: false, }, { name: "invalid user ID", userID: uuid.Nil, reason: "consent_deadline_exceeded", shouldSuspend: false, expectError: true, }, { name: "invalid reason", userID: uuid.New(), reason: "", shouldSuspend: false, expectError: true, }, } validReasons := map[string]bool{ "consent_deadline_exceeded": true, "mandatory_consent_missing": true, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.userID == uuid.Nil { err = &ValidationError{Field: "user ID", Message: "required"} } else if !validReasons[tt.reason] && tt.reason != "" { err = &ValidationError{Field: "reason", Message: "invalid suspension reason"} } else if tt.reason == "" { err = &ValidationError{Field: "reason", 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) } }) } } // TestDeadlineService_LiftSuspension tests lifting account suspension func TestDeadlineService_LiftSuspension(t *testing.T) { tests := []struct { name string userID uuid.UUID adminID uuid.UUID reason string expectError bool }{ { name: "lift valid suspension", userID: uuid.New(), adminID: uuid.New(), reason: "consent provided", expectError: false, }, { name: "invalid user ID", userID: uuid.Nil, adminID: uuid.New(), reason: "consent provided", expectError: true, }, { name: "invalid admin ID", userID: uuid.New(), adminID: uuid.Nil, reason: "consent provided", expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var err error if tt.userID == uuid.Nil { err = &ValidationError{Field: "user ID", Message: "required"} } else if tt.adminID == uuid.Nil { err = &ValidationError{Field: "admin 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) } }) } } // TestDeadlineService_GetOverdueDeadlines tests finding overdue deadlines func TestDeadlineService_GetOverdueDeadlines(t *testing.T) { now := time.Now() tests := []struct { name string deadlines []time.Time expected int // number of overdue }{ { name: "no overdue deadlines", deadlines: []time.Time{ now.AddDate(0, 0, 1), now.AddDate(0, 0, 7), now.AddDate(0, 0, 30), }, expected: 0, }, { name: "some overdue", deadlines: []time.Time{ now.AddDate(0, 0, -1), now.AddDate(0, 0, -5), now.AddDate(0, 0, 7), }, expected: 2, }, { name: "all overdue", deadlines: []time.Time{ now.AddDate(0, 0, -1), now.AddDate(0, 0, -7), now.AddDate(0, 0, -30), }, expected: 3, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { overdueCount := 0 for _, deadline := range tt.deadlines { if deadline.Before(now) { overdueCount++ } } if overdueCount != tt.expected { t.Errorf("Expected %d overdue, got %d", tt.expected, overdueCount) } }) } } // TestDeadlineService_ProcessScheduledTasks tests scheduled task processing func TestDeadlineService_ProcessScheduledTasks(t *testing.T) { now := time.Now() tests := []struct { name string task string scheduledAt time.Time shouldProcess bool }{ { name: "process due task", task: "send_reminder", scheduledAt: now.Add(-1 * time.Hour), shouldProcess: true, }, { name: "skip future task", task: "send_reminder", scheduledAt: now.Add(1 * time.Hour), shouldProcess: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { shouldProcess := tt.scheduledAt.Before(now) || tt.scheduledAt.Equal(now) if shouldProcess != tt.shouldProcess { t.Errorf("Expected shouldProcess=%v, got %v", tt.shouldProcess, shouldProcess) } }) } } // Helper functions func ptrTime(t time.Time) *time.Time { return &t }