slate-v3-1770514618/services/preferences-api/internal/service/preferences_test.go
rdev-worker 1afe983cd6
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature user-preferences
2026-02-08 02:02:18 +00:00

154 lines
4.3 KiB
Go

package service
import (
"context"
"testing"
"git.threesix.ai/jordan/slate-v3-1770514618/pkg/logging"
"git.threesix.ai/jordan/slate-v3-1770514618/services/preferences-api/internal/adapter/memory"
"git.threesix.ai/jordan/slate-v3-1770514618/services/preferences-api/internal/domain"
)
func strPtr(s string) *string { return &s }
func boolPtr(b bool) *bool { return &b }
func TestPreferencesService_Get(t *testing.T) {
repo := memory.NewPreferencesRepository()
svc := NewPreferencesService(repo, logging.Nop())
ctx := context.Background()
t.Run("returns not found when no preferences exist", func(t *testing.T) {
_, err := svc.Get(ctx, "user-1")
if err != domain.ErrPreferencesNotFound {
t.Errorf("expected ErrPreferencesNotFound, got %v", err)
}
})
t.Run("returns preferences after upsert", func(t *testing.T) {
_, _ = svc.Upsert(ctx, "user-1", &domain.PreferencesUpdate{
Theme: strPtr("dark"),
})
prefs, err := svc.Get(ctx, "user-1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.Theme != "dark" {
t.Errorf("expected theme 'dark', got '%s'", prefs.Theme)
}
})
}
func TestPreferencesService_Upsert(t *testing.T) {
repo := memory.NewPreferencesRepository()
svc := NewPreferencesService(repo, logging.Nop())
ctx := context.Background()
t.Run("creates new with defaults and merges", func(t *testing.T) {
prefs, err := svc.Upsert(ctx, "user-new", &domain.PreferencesUpdate{
Theme: strPtr("light"),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.Theme != "light" {
t.Errorf("expected theme 'light', got '%s'", prefs.Theme)
}
if prefs.Language != "en" {
t.Errorf("expected default language 'en', got '%s'", prefs.Language)
}
if !prefs.Notifications.Email {
t.Error("expected default email=true")
}
})
t.Run("updates existing with merge", func(t *testing.T) {
// First upsert
_, _ = svc.Upsert(ctx, "user-merge", &domain.PreferencesUpdate{
Theme: strPtr("dark"),
Language: strPtr("fr"),
})
// Second upsert - only change language
prefs, err := svc.Upsert(ctx, "user-merge", &domain.PreferencesUpdate{
Language: strPtr("es"),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.Theme != "dark" {
t.Errorf("expected theme 'dark' unchanged, got '%s'", prefs.Theme)
}
if prefs.Language != "es" {
t.Errorf("expected language 'es', got '%s'", prefs.Language)
}
})
t.Run("rejects invalid theme", func(t *testing.T) {
_, err := svc.Upsert(ctx, "user-invalid", &domain.PreferencesUpdate{
Theme: strPtr("blue"),
})
if err != domain.ErrInvalidTheme {
t.Errorf("expected ErrInvalidTheme, got %v", err)
}
})
t.Run("rejects invalid language", func(t *testing.T) {
_, err := svc.Upsert(ctx, "user-invalid2", &domain.PreferencesUpdate{
Language: strPtr("XYZ"),
})
if err != domain.ErrInvalidLanguage {
t.Errorf("expected ErrInvalidLanguage, got %v", err)
}
})
t.Run("rejects invalid digest", func(t *testing.T) {
_, err := svc.Upsert(ctx, "user-invalid3", &domain.PreferencesUpdate{
Notifications: &domain.NotificationSettingsUpdate{
Digest: strPtr("monthly"),
},
})
if err != domain.ErrInvalidDigest {
t.Errorf("expected ErrInvalidDigest, got %v", err)
}
})
t.Run("sets UpdatedAt", func(t *testing.T) {
prefs, err := svc.Upsert(ctx, "user-time", &domain.PreferencesUpdate{
Theme: strPtr("dark"),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if prefs.UpdatedAt.IsZero() {
t.Error("expected non-zero UpdatedAt")
}
})
t.Run("notification sub-field merge preserves unset fields", func(t *testing.T) {
_, _ = svc.Upsert(ctx, "user-notif", &domain.PreferencesUpdate{
Notifications: &domain.NotificationSettingsUpdate{
Email: boolPtr(false),
Push: boolPtr(false),
Digest: strPtr("daily"),
},
})
prefs, err := svc.Upsert(ctx, "user-notif", &domain.PreferencesUpdate{
Notifications: &domain.NotificationSettingsUpdate{
Email: boolPtr(true),
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !prefs.Notifications.Email {
t.Error("expected email=true")
}
if prefs.Notifications.Push != false {
t.Error("expected push=false preserved")
}
if prefs.Notifications.Digest != "daily" {
t.Errorf("expected digest 'daily' preserved, got '%s'", prefs.Notifications.Digest)
}
})
}