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>
661 lines
16 KiB
Go
661 lines
16 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|