slack5-1770541397/services/preferences-api/internal/service/preferences_test.go
rdev-worker e3e19a3fa8
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
build: /implement-feature user-preferences
2026-02-08 09:29:22 +00:00

182 lines
5.2 KiB
Go

package service
import (
"context"
"sync"
"testing"
"git.threesix.ai/jordan/slack5-1770541397/pkg/logging"
"git.threesix.ai/jordan/slack5-1770541397/services/preferences-api/internal/domain"
"git.threesix.ai/jordan/slack5-1770541397/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()
prefs, ok := m.store[userID]
if !ok {
return nil, domain.ErrPreferencesNotFound
}
copy := *prefs
return &copy, nil
}
func (m *mockPreferencesRepository) Upsert(ctx context.Context, prefs *domain.UserPreferences) error {
m.mu.Lock()
defer m.mu.Unlock()
copy := *prefs
m.store[prefs.UserID] = &copy
return nil
}
func TestPreferencesService_GetPreferences(t *testing.T) {
repo := newMockPreferencesRepository()
svc := NewPreferencesService(repo, logging.Nop())
t.Run("returns defaults when no preferences exist", func(t *testing.T) {
prefs, err := svc.GetPreferences(context.Background(), "user-123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.UserID != "user-123" {
t.Errorf("expected user_id 'user-123', got '%s'", prefs.UserID)
}
if prefs.Preferences.Theme != "system" {
t.Errorf("expected default theme 'system', got '%s'", prefs.Preferences.Theme)
}
if prefs.Preferences.Language != "en" {
t.Errorf("expected default language 'en', got '%s'", prefs.Preferences.Language)
}
if !prefs.Preferences.Notifications.Email {
t.Error("expected default notifications.email to be true")
}
if !prefs.Preferences.Notifications.Push {
t.Error("expected default notifications.push to be true")
}
if prefs.Preferences.Notifications.SMS {
t.Error("expected default notifications.sms to be false")
}
})
t.Run("returns stored preferences", func(t *testing.T) {
// Store preferences first
_, err := svc.SetPreferences(context.Background(), "user-456", domain.Preferences{
Theme: "dark",
Language: "fr",
Notifications: domain.NotificationPreferences{
Email: false,
Push: true,
SMS: true,
},
})
if err != nil {
t.Fatalf("unexpected error setting preferences: %v", err)
}
prefs, err := svc.GetPreferences(context.Background(), "user-456")
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)
}
if prefs.Preferences.Notifications.Email {
t.Error("expected notifications.email to be false")
}
if prefs.Preferences.Notifications.SMS != true {
t.Error("expected notifications.sms to be true")
}
})
}
func TestPreferencesService_SetPreferences(t *testing.T) {
repo := newMockPreferencesRepository()
svc := NewPreferencesService(repo, logging.Nop())
t.Run("sets valid preferences", func(t *testing.T) {
prefs, err := svc.SetPreferences(context.Background(), "user-789", domain.Preferences{
Theme: "light",
Language: "en",
Notifications: domain.NotificationPreferences{
Email: true,
Push: false,
SMS: false,
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.UserID != "user-789" {
t.Errorf("expected user_id 'user-789', got '%s'", prefs.UserID)
}
if prefs.Preferences.Theme != "light" {
t.Errorf("expected theme 'light', got '%s'", prefs.Preferences.Theme)
}
if prefs.UpdatedAt.IsZero() {
t.Error("expected non-zero updated_at")
}
})
t.Run("rejects invalid theme", func(t *testing.T) {
_, err := svc.SetPreferences(context.Background(), "user-789", domain.Preferences{
Theme: "neon",
Language: "en",
})
if err != domain.ErrInvalidTheme {
t.Errorf("expected ErrInvalidTheme, got %v", err)
}
})
t.Run("rejects language exceeding max length", func(t *testing.T) {
_, err := svc.SetPreferences(context.Background(), "user-789", domain.Preferences{
Theme: "dark",
Language: "abcdefghijk", // 11 chars
})
if err != domain.ErrInvalidLanguage {
t.Errorf("expected ErrInvalidLanguage, got %v", err)
}
})
t.Run("allows empty theme", func(t *testing.T) {
prefs, err := svc.SetPreferences(context.Background(), "user-000", domain.Preferences{
Theme: "",
Language: "en",
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.Preferences.Theme != "" {
t.Errorf("expected empty theme, got '%s'", prefs.Preferences.Theme)
}
})
t.Run("allows max length language", func(t *testing.T) {
_, err := svc.SetPreferences(context.Background(), "user-000", domain.Preferences{
Theme: "dark",
Language: "abcdefghij", // exactly 10 chars
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}