package notifications import ( "context" "testing" "git.gobha.me/xcaliber/chat-switchboard/models" ) // mockPrefStore implements store.NotificationPreferenceStore for testing. type mockPrefStore struct { prefs map[string]map[string]*models.NotificationPreference // userID → type → pref } func newMockPrefStore() *mockPrefStore { return &mockPrefStore{prefs: make(map[string]map[string]*models.NotificationPreference)} } func (m *mockPrefStore) Get(_ context.Context, userID, notifType string) (*models.NotificationPreference, error) { if userPrefs, ok := m.prefs[userID]; ok { if p, ok := userPrefs[notifType]; ok { return p, nil } } return nil, nil } func (m *mockPrefStore) ListForUser(_ context.Context, userID string) ([]models.NotificationPreference, error) { var result []models.NotificationPreference if userPrefs, ok := m.prefs[userID]; ok { for _, p := range userPrefs { result = append(result, *p) } } return result, nil } func (m *mockPrefStore) Upsert(_ context.Context, pref *models.NotificationPreference) error { if m.prefs[pref.UserID] == nil { m.prefs[pref.UserID] = make(map[string]*models.NotificationPreference) } m.prefs[pref.UserID][pref.Type] = pref return nil } func (m *mockPrefStore) Delete(_ context.Context, userID, notifType string) error { if userPrefs, ok := m.prefs[userID]; ok { delete(userPrefs, notifType) } return nil } func (m *mockPrefStore) set(userID, notifType string, inApp, email bool) { if m.prefs[userID] == nil { m.prefs[userID] = make(map[string]*models.NotificationPreference) } m.prefs[userID][notifType] = &models.NotificationPreference{ UserID: userID, Type: notifType, InApp: inApp, Email: email, } } func TestResolvePrefs_SystemDefault(t *testing.T) { svc := &Service{prefStore: newMockPrefStore()} p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if !p.InApp || p.Email { t.Errorf("expected system default (in_app=true, email=false), got %+v", p) } } func TestResolvePrefs_NoPrefStore(t *testing.T) { svc := &Service{prefStore: nil} p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if !p.InApp || p.Email { t.Errorf("expected system default when no pref store, got %+v", p) } } func TestResolvePrefs_SpecificType(t *testing.T) { ps := newMockPrefStore() ps.set("user1", "kb.ready", true, true) svc := &Service{prefStore: ps} p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if !p.InApp || !p.Email { t.Errorf("expected specific pref (in_app=true, email=true), got %+v", p) } } func TestResolvePrefs_WildcardFallback(t *testing.T) { ps := newMockPrefStore() ps.set("user1", "*", false, true) // user turned off in-app, turned on email globally svc := &Service{prefStore: ps} // No specific "kb.ready" pref → falls to wildcard p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if p.InApp || !p.Email { t.Errorf("expected wildcard pref (in_app=false, email=true), got %+v", p) } } func TestResolvePrefs_SpecificOverridesWildcard(t *testing.T) { ps := newMockPrefStore() ps.set("user1", "*", false, true) // wildcard: no in-app, yes email ps.set("user1", "kb.ready", true, false) // specific: yes in-app, no email svc := &Service{prefStore: ps} p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if !p.InApp || p.Email { t.Errorf("specific should override wildcard, got %+v", p) } } func TestResolvePrefs_DifferentUsersIsolated(t *testing.T) { ps := newMockPrefStore() ps.set("user1", "kb.ready", false, true) svc := &Service{prefStore: ps} // user2 has no prefs → system default p := svc.resolvePrefs(context.Background(), "user2", "kb.ready") if !p.InApp || p.Email { t.Errorf("user2 should get system default, got %+v", p) } } func TestResolvePrefs_DisableBoth(t *testing.T) { ps := newMockPrefStore() ps.set("user1", "kb.ready", false, false) svc := &Service{prefStore: ps} p := svc.resolvePrefs(context.Background(), "user1", "kb.ready") if p.InApp || p.Email { t.Errorf("expected both disabled, got %+v", p) } }