254 lines
7.6 KiB
Go
254 lines
7.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.threesix.ai/jordan/slack5-1770603014/pkg/logging"
|
|
"git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/domain"
|
|
"git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/port"
|
|
)
|
|
|
|
// mockPreferencesRepository implements port.PreferencesRepository for testing.
|
|
type mockPreferencesRepository struct {
|
|
mu sync.RWMutex
|
|
store map[domain.UserID]*domain.UserPreferences
|
|
}
|
|
|
|
var _ port.PreferencesRepository = (*mockPreferencesRepository)(nil)
|
|
|
|
func newMockPreferencesRepository() *mockPreferencesRepository {
|
|
return &mockPreferencesRepository{
|
|
store: make(map[domain.UserID]*domain.UserPreferences),
|
|
}
|
|
}
|
|
|
|
func (m *mockPreferencesRepository) Get(ctx context.Context, userID domain.UserID) (*domain.UserPreferences, error) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
p, ok := m.store[userID]
|
|
if !ok {
|
|
return nil, nil
|
|
}
|
|
cp := *p
|
|
return &cp, nil
|
|
}
|
|
|
|
func (m *mockPreferencesRepository) Upsert(ctx context.Context, prefs *domain.UserPreferences) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
cp := *prefs
|
|
m.store[prefs.UserID] = &cp
|
|
return nil
|
|
}
|
|
|
|
func strPtr(s string) *string { return &s }
|
|
func boolPtr(b bool) *bool { return &b }
|
|
|
|
func TestPreferencesService_GetPreferences(t *testing.T) {
|
|
repo := newMockPreferencesRepository()
|
|
svc := NewPreferencesService(repo, logging.Nop())
|
|
|
|
t.Run("returns defaults for unknown user", func(t *testing.T) {
|
|
prefs, err := svc.GetPreferences(context.Background(), "unknown-user")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Theme != "system" {
|
|
t.Errorf("expected theme 'system', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
if prefs.Preferences.Language != "en" {
|
|
t.Errorf("expected language 'en', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
if prefs.Preferences.Notifications.Email != true {
|
|
t.Error("expected notifications.email true")
|
|
}
|
|
if prefs.Preferences.Notifications.Push != true {
|
|
t.Error("expected notifications.push true")
|
|
}
|
|
if prefs.Preferences.Notifications.Digest != "weekly" {
|
|
t.Errorf("expected notifications.digest 'weekly', got '%s'", prefs.Preferences.Notifications.Digest)
|
|
}
|
|
})
|
|
|
|
t.Run("returns stored preferences for existing user", func(t *testing.T) {
|
|
// Seed data
|
|
repo.mu.Lock()
|
|
repo.store["user-1"] = &domain.UserPreferences{
|
|
UserID: "user-1",
|
|
Preferences: domain.Preferences{
|
|
Theme: "dark",
|
|
Language: "fr",
|
|
Notifications: domain.NotificationSettings{
|
|
Email: false,
|
|
Push: true,
|
|
Digest: "daily",
|
|
},
|
|
},
|
|
}
|
|
repo.mu.Unlock()
|
|
|
|
prefs, err := svc.GetPreferences(context.Background(), "user-1")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Theme != "dark" {
|
|
t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
if prefs.Preferences.Language != "fr" {
|
|
t.Errorf("expected language 'fr', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestPreferencesService_UpdatePreferences(t *testing.T) {
|
|
repo := newMockPreferencesRepository()
|
|
svc := NewPreferencesService(repo, logging.Nop())
|
|
|
|
t.Run("creates new preferences from defaults", func(t *testing.T) {
|
|
prefs, err := svc.UpdatePreferences(context.Background(), "new-user", UpdateInput{
|
|
Theme: strPtr("dark"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Theme != "dark" {
|
|
t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
// Other fields should be defaults
|
|
if prefs.Preferences.Language != "en" {
|
|
t.Errorf("expected language 'en', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
if prefs.Preferences.Notifications.Digest != "weekly" {
|
|
t.Errorf("expected digest 'weekly', got '%s'", prefs.Preferences.Notifications.Digest)
|
|
}
|
|
})
|
|
|
|
t.Run("merges partial data with existing preferences", func(t *testing.T) {
|
|
// First set full preferences
|
|
_, _ = svc.UpdatePreferences(context.Background(), "merge-user", UpdateInput{
|
|
Theme: strPtr("light"),
|
|
Language: strPtr("es"),
|
|
Notifications: &NotificationsInput{
|
|
Email: boolPtr(false),
|
|
Push: boolPtr(false),
|
|
Digest: strPtr("daily"),
|
|
},
|
|
})
|
|
|
|
// Now partial update - only change push notification
|
|
prefs, err := svc.UpdatePreferences(context.Background(), "merge-user", UpdateInput{
|
|
Notifications: &NotificationsInput{
|
|
Push: boolPtr(true),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
// Push should be updated
|
|
if prefs.Preferences.Notifications.Push != true {
|
|
t.Error("expected notifications.push true")
|
|
}
|
|
// Other fields should be unchanged
|
|
if prefs.Preferences.Theme != "light" {
|
|
t.Errorf("expected theme 'light', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
if prefs.Preferences.Language != "es" {
|
|
t.Errorf("expected language 'es', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
if prefs.Preferences.Notifications.Email != false {
|
|
t.Error("expected notifications.email false")
|
|
}
|
|
if prefs.Preferences.Notifications.Digest != "daily" {
|
|
t.Errorf("expected digest 'daily', got '%s'", prefs.Preferences.Notifications.Digest)
|
|
}
|
|
})
|
|
|
|
t.Run("updates theme only", func(t *testing.T) {
|
|
prefs, err := svc.UpdatePreferences(context.Background(), "theme-user", UpdateInput{
|
|
Theme: strPtr("light"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Theme != "light" {
|
|
t.Errorf("expected theme 'light', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
})
|
|
|
|
t.Run("updates language only", func(t *testing.T) {
|
|
prefs, err := svc.UpdatePreferences(context.Background(), "lang-user", UpdateInput{
|
|
Language: strPtr("fr"),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Language != "fr" {
|
|
t.Errorf("expected language 'fr', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
})
|
|
|
|
t.Run("updates all fields together", func(t *testing.T) {
|
|
prefs, err := svc.UpdatePreferences(context.Background(), "all-user", UpdateInput{
|
|
Theme: strPtr("dark"),
|
|
Language: strPtr("de"),
|
|
Notifications: &NotificationsInput{
|
|
Email: boolPtr(false),
|
|
Push: boolPtr(true),
|
|
Digest: strPtr("never"),
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if prefs.Preferences.Theme != "dark" {
|
|
t.Errorf("expected theme 'dark', got '%s'", prefs.Preferences.Theme)
|
|
}
|
|
if prefs.Preferences.Language != "de" {
|
|
t.Errorf("expected language 'de', got '%s'", prefs.Preferences.Language)
|
|
}
|
|
if prefs.Preferences.Notifications.Email != false {
|
|
t.Error("expected notifications.email false")
|
|
}
|
|
if prefs.Preferences.Notifications.Push != true {
|
|
t.Error("expected notifications.push true")
|
|
}
|
|
if prefs.Preferences.Notifications.Digest != "never" {
|
|
t.Errorf("expected digest 'never', got '%s'", prefs.Preferences.Notifications.Digest)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects invalid theme", func(t *testing.T) {
|
|
_, err := svc.UpdatePreferences(context.Background(), "invalid-theme-user", UpdateInput{
|
|
Theme: strPtr("purple"),
|
|
})
|
|
if err != domain.ErrInvalidTheme {
|
|
t.Errorf("expected ErrInvalidTheme, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects invalid digest", func(t *testing.T) {
|
|
_, err := svc.UpdatePreferences(context.Background(), "invalid-digest-user", UpdateInput{
|
|
Notifications: &NotificationsInput{
|
|
Digest: strPtr("monthly"),
|
|
},
|
|
})
|
|
if err != domain.ErrInvalidDigest {
|
|
t.Errorf("expected ErrInvalidDigest, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects empty language", func(t *testing.T) {
|
|
_, err := svc.UpdatePreferences(context.Background(), "empty-lang-user", UpdateInput{
|
|
Language: strPtr(""),
|
|
})
|
|
if err != domain.ErrInvalidLanguage {
|
|
t.Errorf("expected ErrInvalidLanguage, got %v", err)
|
|
}
|
|
})
|
|
}
|