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:
660
consent-service/internal/services/notification_service_test.go
Normal file
660
consent-service/internal/services/notification_service_test.go
Normal file
@@ -0,0 +1,660 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TestNotificationService_CreateNotification tests notification creation
|
||||
func TestNotificationService_CreateNotification(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uuid.UUID
|
||||
notifType NotificationType
|
||||
title string
|
||||
body string
|
||||
data map[string]interface{}
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid notification",
|
||||
userID: uuid.New(),
|
||||
notifType: NotificationTypeConsentRequired,
|
||||
title: "Consent Required",
|
||||
body: "Please review and accept the new terms",
|
||||
data: map[string]interface{}{"document_id": "123"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "notification without data",
|
||||
userID: uuid.New(),
|
||||
notifType: NotificationTypeGeneral,
|
||||
title: "General Notification",
|
||||
body: "This is a test",
|
||||
data: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty user ID",
|
||||
userID: uuid.Nil,
|
||||
notifType: NotificationTypeGeneral,
|
||||
title: "Test",
|
||||
body: "Test",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty title",
|
||||
userID: uuid.New(),
|
||||
notifType: NotificationTypeGeneral,
|
||||
title: "",
|
||||
body: "Test body",
|
||||
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.title == "" {
|
||||
err = &ValidationError{Field: "title", 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_NotificationTypes tests notification type validation
|
||||
func TestNotificationService_NotificationTypes(t *testing.T) {
|
||||
tests := []struct {
|
||||
notifType NotificationType
|
||||
isValid bool
|
||||
}{
|
||||
{NotificationTypeConsentRequired, true},
|
||||
{NotificationTypeConsentReminder, true},
|
||||
{NotificationTypeVersionPublished, true},
|
||||
{NotificationTypeVersionApproved, true},
|
||||
{NotificationTypeVersionRejected, true},
|
||||
{NotificationTypeAccountSuspended, true},
|
||||
{NotificationTypeAccountRestored, true},
|
||||
{NotificationTypeGeneral, true},
|
||||
{NotificationType("invalid_type"), false},
|
||||
{NotificationType(""), false},
|
||||
}
|
||||
|
||||
validTypes := map[NotificationType]bool{
|
||||
NotificationTypeConsentRequired: true,
|
||||
NotificationTypeConsentReminder: true,
|
||||
NotificationTypeVersionPublished: true,
|
||||
NotificationTypeVersionApproved: true,
|
||||
NotificationTypeVersionRejected: true,
|
||||
NotificationTypeAccountSuspended: true,
|
||||
NotificationTypeAccountRestored: true,
|
||||
NotificationTypeGeneral: true,
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.notifType), func(t *testing.T) {
|
||||
isValid := validTypes[tt.notifType]
|
||||
|
||||
if isValid != tt.isValid {
|
||||
t.Errorf("Type %s: expected valid=%v, got %v", tt.notifType, tt.isValid, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_NotificationChannels tests channel validation
|
||||
func TestNotificationService_NotificationChannels(t *testing.T) {
|
||||
tests := []struct {
|
||||
channel NotificationChannel
|
||||
isValid bool
|
||||
}{
|
||||
{ChannelInApp, true},
|
||||
{ChannelEmail, true},
|
||||
{ChannelPush, true},
|
||||
{NotificationChannel("sms"), false},
|
||||
{NotificationChannel(""), false},
|
||||
}
|
||||
|
||||
validChannels := map[NotificationChannel]bool{
|
||||
ChannelInApp: true,
|
||||
ChannelEmail: true,
|
||||
ChannelPush: true,
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.channel), func(t *testing.T) {
|
||||
isValid := validChannels[tt.channel]
|
||||
|
||||
if isValid != tt.isValid {
|
||||
t.Errorf("Channel %s: expected valid=%v, got %v", tt.channel, tt.isValid, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_GetUserNotifications tests retrieving notifications
|
||||
func TestNotificationService_GetUserNotifications(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uuid.UUID
|
||||
limit int
|
||||
offset int
|
||||
unreadOnly bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "get all notifications",
|
||||
userID: uuid.New(),
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
unreadOnly: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "get unread only",
|
||||
userID: uuid.New(),
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
unreadOnly: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "with pagination",
|
||||
userID: uuid.New(),
|
||||
limit: 10,
|
||||
offset: 20,
|
||||
unreadOnly: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
userID: uuid.Nil,
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
unreadOnly: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "negative limit",
|
||||
userID: uuid.New(),
|
||||
limit: -1,
|
||||
offset: 0,
|
||||
unreadOnly: false,
|
||||
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.limit < 0 {
|
||||
err = &ValidationError{Field: "limit", Message: "must be >= 0"}
|
||||
}
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_MarkAsRead tests marking notifications as read
|
||||
func TestNotificationService_MarkAsRead(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
notificationID uuid.UUID
|
||||
userID uuid.UUID
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "mark valid notification as read",
|
||||
notificationID: uuid.New(),
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid notification ID",
|
||||
notificationID: uuid.Nil,
|
||||
userID: uuid.New(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
notificationID: uuid.New(),
|
||||
userID: uuid.Nil,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var err error
|
||||
if tt.notificationID == uuid.Nil {
|
||||
err = &ValidationError{Field: "notification ID", Message: "required"}
|
||||
} else if tt.userID == uuid.Nil {
|
||||
err = &ValidationError{Field: "user 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_GetPreferences tests retrieving user preferences
|
||||
func TestNotificationService_GetPreferences(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uuid.UUID
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "get valid user preferences",
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
userID: uuid.Nil,
|
||||
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"}
|
||||
}
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_UpdatePreferences tests updating notification preferences
|
||||
func TestNotificationService_UpdatePreferences(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uuid.UUID
|
||||
emailEnabled bool
|
||||
pushEnabled bool
|
||||
inAppEnabled bool
|
||||
reminderFrequency string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "enable all notifications",
|
||||
userID: uuid.New(),
|
||||
emailEnabled: true,
|
||||
pushEnabled: true,
|
||||
inAppEnabled: true,
|
||||
reminderFrequency: "daily",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "disable email notifications",
|
||||
userID: uuid.New(),
|
||||
emailEnabled: false,
|
||||
pushEnabled: true,
|
||||
inAppEnabled: true,
|
||||
reminderFrequency: "weekly",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "set reminder frequency to never",
|
||||
userID: uuid.New(),
|
||||
emailEnabled: true,
|
||||
pushEnabled: false,
|
||||
inAppEnabled: true,
|
||||
reminderFrequency: "never",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid reminder frequency",
|
||||
userID: uuid.New(),
|
||||
emailEnabled: true,
|
||||
pushEnabled: true,
|
||||
inAppEnabled: true,
|
||||
reminderFrequency: "hourly",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
validFrequencies := map[string]bool{
|
||||
"daily": true,
|
||||
"weekly": true,
|
||||
"never": true,
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var err error
|
||||
if !validFrequencies[tt.reminderFrequency] {
|
||||
err = &ValidationError{Field: "reminder_frequency", Message: "must be daily, weekly, or never"}
|
||||
}
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_NotifyConsentRequired tests consent required notification
|
||||
func TestNotificationService_NotifyConsentRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
documentName string
|
||||
versionID string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid consent notification",
|
||||
documentName: "Terms of Service",
|
||||
versionID: uuid.New().String(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty document name",
|
||||
documentName: "",
|
||||
versionID: uuid.New().String(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "empty version ID",
|
||||
documentName: "Privacy Policy",
|
||||
versionID: "",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var err error
|
||||
if tt.documentName == "" {
|
||||
err = &ValidationError{Field: "document name", Message: "required"}
|
||||
} else if tt.versionID == "" {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_DeleteNotification tests deleting notifications
|
||||
func TestNotificationService_DeleteNotification(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
notificationID uuid.UUID
|
||||
userID uuid.UUID
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "delete valid notification",
|
||||
notificationID: uuid.New(),
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid notification ID",
|
||||
notificationID: uuid.Nil,
|
||||
userID: uuid.New(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
notificationID: uuid.New(),
|
||||
userID: uuid.Nil,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var err error
|
||||
if tt.notificationID == uuid.Nil {
|
||||
err = &ValidationError{Field: "notification ID", Message: "required"}
|
||||
} else if tt.userID == uuid.Nil {
|
||||
err = &ValidationError{Field: "user 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_BatchMarkAsRead tests batch marking as read
|
||||
func TestNotificationService_BatchMarkAsRead(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
notificationIDs []uuid.UUID
|
||||
userID uuid.UUID
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "mark multiple notifications",
|
||||
notificationIDs: []uuid.UUID{uuid.New(), uuid.New(), uuid.New()},
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty list",
|
||||
notificationIDs: []uuid.UUID{},
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
notificationIDs: []uuid.UUID{uuid.New()},
|
||||
userID: uuid.Nil,
|
||||
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"}
|
||||
}
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_GetUnreadCount tests getting unread count
|
||||
func TestNotificationService_GetUnreadCount(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
userID uuid.UUID
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "get count for valid user",
|
||||
userID: uuid.New(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid user ID",
|
||||
userID: uuid.Nil,
|
||||
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"}
|
||||
}
|
||||
|
||||
if tt.expectError && err == nil {
|
||||
t.Error("Expected error, got nil")
|
||||
}
|
||||
if !tt.expectError && err != nil {
|
||||
t.Errorf("Expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_NotificationPriority tests notification priority
|
||||
func TestNotificationService_NotificationPriority(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
notifType NotificationType
|
||||
expectedPrio string
|
||||
}{
|
||||
{
|
||||
name: "consent required - high priority",
|
||||
notifType: NotificationTypeConsentRequired,
|
||||
expectedPrio: "high",
|
||||
},
|
||||
{
|
||||
name: "account suspended - critical",
|
||||
notifType: NotificationTypeAccountSuspended,
|
||||
expectedPrio: "critical",
|
||||
},
|
||||
{
|
||||
name: "version published - normal",
|
||||
notifType: NotificationTypeVersionPublished,
|
||||
expectedPrio: "normal",
|
||||
},
|
||||
{
|
||||
name: "general - low",
|
||||
notifType: NotificationTypeGeneral,
|
||||
expectedPrio: "low",
|
||||
},
|
||||
}
|
||||
|
||||
priorityMap := map[NotificationType]string{
|
||||
NotificationTypeConsentRequired: "high",
|
||||
NotificationTypeConsentReminder: "high",
|
||||
NotificationTypeAccountSuspended: "critical",
|
||||
NotificationTypeAccountRestored: "normal",
|
||||
NotificationTypeVersionPublished: "normal",
|
||||
NotificationTypeVersionApproved: "normal",
|
||||
NotificationTypeVersionRejected: "normal",
|
||||
NotificationTypeGeneral: "low",
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
priority := priorityMap[tt.notifType]
|
||||
|
||||
if priority != tt.expectedPrio {
|
||||
t.Errorf("Expected priority %s, got %s", tt.expectedPrio, priority)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNotificationService_ReminderFrequency tests reminder frequency logic
|
||||
func TestNotificationService_ReminderFrequency(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
frequency string
|
||||
lastReminder time.Time
|
||||
shouldSend bool
|
||||
}{
|
||||
{
|
||||
name: "daily - last sent yesterday",
|
||||
frequency: "daily",
|
||||
lastReminder: now.AddDate(0, 0, -1),
|
||||
shouldSend: true,
|
||||
},
|
||||
{
|
||||
name: "daily - last sent today",
|
||||
frequency: "daily",
|
||||
lastReminder: now.Add(-1 * time.Hour),
|
||||
shouldSend: false,
|
||||
},
|
||||
{
|
||||
name: "weekly - last sent 8 days ago",
|
||||
frequency: "weekly",
|
||||
lastReminder: now.AddDate(0, 0, -8),
|
||||
shouldSend: true,
|
||||
},
|
||||
{
|
||||
name: "weekly - last sent 5 days ago",
|
||||
frequency: "weekly",
|
||||
lastReminder: now.AddDate(0, 0, -5),
|
||||
shouldSend: false,
|
||||
},
|
||||
{
|
||||
name: "never - should not send",
|
||||
frequency: "never",
|
||||
lastReminder: now.AddDate(0, 0, -30),
|
||||
shouldSend: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var shouldSend bool
|
||||
|
||||
switch tt.frequency {
|
||||
case "daily":
|
||||
daysSince := int(now.Sub(tt.lastReminder).Hours() / 24)
|
||||
shouldSend = daysSince >= 1
|
||||
case "weekly":
|
||||
daysSince := int(now.Sub(tt.lastReminder).Hours() / 24)
|
||||
shouldSend = daysSince >= 7
|
||||
case "never":
|
||||
shouldSend = false
|
||||
}
|
||||
|
||||
if shouldSend != tt.shouldSend {
|
||||
t.Errorf("Expected shouldSend=%v, got %v", tt.shouldSend, shouldSend)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user