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>
519 lines
12 KiB
Go
519 lines
12 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// TestConsentService_CreateConsent tests creating a new consent
|
|
func TestConsentService_CreateConsent(t *testing.T) {
|
|
// This is a unit test with table-driven approach
|
|
tests := []struct {
|
|
name string
|
|
userID uuid.UUID
|
|
versionID uuid.UUID
|
|
consented bool
|
|
expectError bool
|
|
errorContains string
|
|
}{
|
|
{
|
|
name: "valid consent - accepted",
|
|
userID: uuid.New(),
|
|
versionID: uuid.New(),
|
|
consented: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "valid consent - declined",
|
|
userID: uuid.New(),
|
|
versionID: uuid.New(),
|
|
consented: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty user ID",
|
|
userID: uuid.Nil,
|
|
versionID: uuid.New(),
|
|
consented: true,
|
|
expectError: true,
|
|
errorContains: "user ID",
|
|
},
|
|
{
|
|
name: "empty version ID",
|
|
userID: uuid.New(),
|
|
versionID: uuid.Nil,
|
|
consented: true,
|
|
expectError: true,
|
|
errorContains: "version ID",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Validate inputs (in real implementation this would be in the service)
|
|
var hasError bool
|
|
if tt.userID == uuid.Nil {
|
|
hasError = true
|
|
} else if tt.versionID == uuid.Nil {
|
|
hasError = true
|
|
}
|
|
|
|
// Assert
|
|
if tt.expectError && !hasError {
|
|
t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
|
|
}
|
|
if !tt.expectError && hasError {
|
|
t.Error("Expected no error, got error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_WithdrawConsent tests withdrawing consent
|
|
func TestConsentService_WithdrawConsent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
consentID uuid.UUID
|
|
userID uuid.UUID
|
|
expectError bool
|
|
errorContains string
|
|
}{
|
|
{
|
|
name: "valid withdrawal",
|
|
consentID: uuid.New(),
|
|
userID: uuid.New(),
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty consent ID",
|
|
consentID: uuid.Nil,
|
|
userID: uuid.New(),
|
|
expectError: true,
|
|
errorContains: "consent ID",
|
|
},
|
|
{
|
|
name: "empty user ID",
|
|
consentID: uuid.New(),
|
|
userID: uuid.Nil,
|
|
expectError: true,
|
|
errorContains: "user ID",
|
|
},
|
|
{
|
|
name: "both empty",
|
|
consentID: uuid.Nil,
|
|
userID: uuid.Nil,
|
|
expectError: true,
|
|
errorContains: "ID",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Validate
|
|
var hasError bool
|
|
if tt.consentID == uuid.Nil || tt.userID == uuid.Nil {
|
|
hasError = true
|
|
}
|
|
|
|
// Assert
|
|
if tt.expectError && !hasError {
|
|
t.Errorf("Expected error containing '%s', got nil", tt.errorContains)
|
|
}
|
|
if !tt.expectError && hasError {
|
|
t.Error("Expected no error, got error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_CheckConsent tests checking consent status
|
|
func TestConsentService_CheckConsent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
userID uuid.UUID
|
|
documentType string
|
|
language string
|
|
hasConsent bool
|
|
needsUpdate bool
|
|
expectedConsent bool
|
|
expectedNeedsUpd bool
|
|
}{
|
|
{
|
|
name: "user has current consent",
|
|
userID: uuid.New(),
|
|
documentType: "terms",
|
|
language: "de",
|
|
hasConsent: true,
|
|
needsUpdate: false,
|
|
expectedConsent: true,
|
|
expectedNeedsUpd: false,
|
|
},
|
|
{
|
|
name: "user has outdated consent",
|
|
userID: uuid.New(),
|
|
documentType: "privacy",
|
|
language: "de",
|
|
hasConsent: true,
|
|
needsUpdate: true,
|
|
expectedConsent: true,
|
|
expectedNeedsUpd: true,
|
|
},
|
|
{
|
|
name: "user has no consent",
|
|
userID: uuid.New(),
|
|
documentType: "cookies",
|
|
language: "de",
|
|
hasConsent: false,
|
|
needsUpdate: true,
|
|
expectedConsent: false,
|
|
expectedNeedsUpd: true,
|
|
},
|
|
{
|
|
name: "english language",
|
|
userID: uuid.New(),
|
|
documentType: "terms",
|
|
language: "en",
|
|
hasConsent: true,
|
|
needsUpdate: false,
|
|
expectedConsent: true,
|
|
expectedNeedsUpd: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Simulate consent check logic
|
|
hasConsent := tt.hasConsent
|
|
needsUpdate := tt.needsUpdate
|
|
|
|
// Assert
|
|
if hasConsent != tt.expectedConsent {
|
|
t.Errorf("Expected hasConsent=%v, got %v", tt.expectedConsent, hasConsent)
|
|
}
|
|
if needsUpdate != tt.expectedNeedsUpd {
|
|
t.Errorf("Expected needsUpdate=%v, got %v", tt.expectedNeedsUpd, needsUpdate)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_GetConsentHistory tests retrieving consent history
|
|
func TestConsentService_GetConsentHistory(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
userID uuid.UUID
|
|
expectError bool
|
|
expectEmpty bool
|
|
}{
|
|
{
|
|
name: "valid user with consents",
|
|
userID: uuid.New(),
|
|
expectError: false,
|
|
expectEmpty: false,
|
|
},
|
|
{
|
|
name: "valid user without consents",
|
|
userID: uuid.New(),
|
|
expectError: false,
|
|
expectEmpty: true,
|
|
},
|
|
{
|
|
name: "invalid user ID",
|
|
userID: uuid.Nil,
|
|
expectError: true,
|
|
expectEmpty: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Validate
|
|
var err error
|
|
if tt.userID == uuid.Nil {
|
|
err = &ValidationError{Field: "user ID", Message: "required"}
|
|
}
|
|
|
|
// Assert error expectation
|
|
if tt.expectError && err == nil {
|
|
t.Error("Expected error, got nil")
|
|
}
|
|
if !tt.expectError && err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_UpdateConsent tests updating existing consent
|
|
func TestConsentService_UpdateConsent(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
consentID uuid.UUID
|
|
userID uuid.UUID
|
|
newConsented bool
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "update to consented",
|
|
consentID: uuid.New(),
|
|
userID: uuid.New(),
|
|
newConsented: true,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "update to not consented",
|
|
consentID: uuid.New(),
|
|
userID: uuid.New(),
|
|
newConsented: false,
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid consent ID",
|
|
consentID: uuid.Nil,
|
|
userID: uuid.New(),
|
|
newConsented: true,
|
|
expectError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var err error
|
|
if tt.consentID == uuid.Nil {
|
|
err = &ValidationError{Field: "consent 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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_GetConsentStats tests getting consent statistics
|
|
func TestConsentService_GetConsentStats(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
documentType string
|
|
totalUsers int
|
|
consentedUsers int
|
|
expectedRate float64
|
|
}{
|
|
{
|
|
name: "100% consent rate",
|
|
documentType: "terms",
|
|
totalUsers: 100,
|
|
consentedUsers: 100,
|
|
expectedRate: 100.0,
|
|
},
|
|
{
|
|
name: "50% consent rate",
|
|
documentType: "privacy",
|
|
totalUsers: 100,
|
|
consentedUsers: 50,
|
|
expectedRate: 50.0,
|
|
},
|
|
{
|
|
name: "0% consent rate",
|
|
documentType: "cookies",
|
|
totalUsers: 100,
|
|
consentedUsers: 0,
|
|
expectedRate: 0.0,
|
|
},
|
|
{
|
|
name: "no users",
|
|
documentType: "terms",
|
|
totalUsers: 0,
|
|
consentedUsers: 0,
|
|
expectedRate: 0.0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Calculate consent rate
|
|
var consentRate float64
|
|
if tt.totalUsers > 0 {
|
|
consentRate = float64(tt.consentedUsers) / float64(tt.totalUsers) * 100
|
|
}
|
|
|
|
// Assert
|
|
if consentRate != tt.expectedRate {
|
|
t.Errorf("Expected consent rate %.2f%%, got %.2f%%", tt.expectedRate, consentRate)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_BulkConsentCheck tests checking multiple consents at once
|
|
func TestConsentService_BulkConsentCheck(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
userID uuid.UUID
|
|
documentTypes []string
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "check multiple documents",
|
|
userID: uuid.New(),
|
|
documentTypes: []string{"terms", "privacy", "cookies"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "check single document",
|
|
userID: uuid.New(),
|
|
documentTypes: []string{"terms"},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "empty document list",
|
|
userID: uuid.New(),
|
|
documentTypes: []string{},
|
|
expectError: false,
|
|
},
|
|
{
|
|
name: "invalid user ID",
|
|
userID: uuid.Nil,
|
|
documentTypes: []string{"terms"},
|
|
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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_ConsentVersionComparison tests version comparison logic
|
|
func TestConsentService_ConsentVersionComparison(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
currentVersion string
|
|
consentedVersion string
|
|
needsUpdate bool
|
|
}{
|
|
{
|
|
name: "same version",
|
|
currentVersion: "1.0.0",
|
|
consentedVersion: "1.0.0",
|
|
needsUpdate: false,
|
|
},
|
|
{
|
|
name: "minor version update",
|
|
currentVersion: "1.1.0",
|
|
consentedVersion: "1.0.0",
|
|
needsUpdate: true,
|
|
},
|
|
{
|
|
name: "major version update",
|
|
currentVersion: "2.0.0",
|
|
consentedVersion: "1.0.0",
|
|
needsUpdate: true,
|
|
},
|
|
{
|
|
name: "patch version update",
|
|
currentVersion: "1.0.1",
|
|
consentedVersion: "1.0.0",
|
|
needsUpdate: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Simple version comparison (in real implementation use proper semver)
|
|
needsUpdate := tt.currentVersion != tt.consentedVersion
|
|
|
|
if needsUpdate != tt.needsUpdate {
|
|
t.Errorf("Expected needsUpdate=%v, got %v", tt.needsUpdate, needsUpdate)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConsentService_ConsentDeadlineCheck tests deadline validation
|
|
func TestConsentService_ConsentDeadlineCheck(t *testing.T) {
|
|
now := time.Now()
|
|
|
|
tests := []struct {
|
|
name string
|
|
deadline time.Time
|
|
isOverdue bool
|
|
daysLeft int
|
|
}{
|
|
{
|
|
name: "deadline in 30 days",
|
|
deadline: now.AddDate(0, 0, 30),
|
|
isOverdue: false,
|
|
daysLeft: 30,
|
|
},
|
|
{
|
|
name: "deadline in 7 days",
|
|
deadline: now.AddDate(0, 0, 7),
|
|
isOverdue: false,
|
|
daysLeft: 7,
|
|
},
|
|
{
|
|
name: "deadline today",
|
|
deadline: now,
|
|
isOverdue: false,
|
|
daysLeft: 0,
|
|
},
|
|
{
|
|
name: "deadline 1 day overdue",
|
|
deadline: now.AddDate(0, 0, -1),
|
|
isOverdue: true,
|
|
daysLeft: -1,
|
|
},
|
|
{
|
|
name: "deadline 30 days overdue",
|
|
deadline: now.AddDate(0, 0, -30),
|
|
isOverdue: true,
|
|
daysLeft: -30,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Calculate if overdue
|
|
isOverdue := tt.deadline.Before(now)
|
|
daysLeft := int(tt.deadline.Sub(now).Hours() / 24)
|
|
|
|
if isOverdue != tt.isOverdue {
|
|
t.Errorf("Expected isOverdue=%v, got %v", tt.isOverdue, isOverdue)
|
|
}
|
|
|
|
// Allow 1 day difference due to time precision
|
|
if abs(daysLeft-tt.daysLeft) > 1 {
|
|
t.Errorf("Expected daysLeft=%d, got %d", tt.daysLeft, daysLeft)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
// abs returns the absolute value of an integer
|
|
func abs(n int) int {
|
|
if n < 0 {
|
|
return -n
|
|
}
|
|
return n
|
|
}
|