//go:build unit package service import ( "context" "errors" "testing" "time" "github.com/stretchr/testify/require" ) type visibilityUserRepo struct { UserRepository user *User err error } func (r *visibilityUserRepo) GetByID(context.Context, int64) (*User, error) { return r.user, r.err } type visibilitySubRepo struct { UserSubscriptionRepository subscriptions []UserSubscription err error calls int } func (r *visibilitySubRepo) ListActiveByUserID(_ context.Context, userID int64) ([]UserSubscription, error) { r.calls++ // Match the repository's active-status and expiry predicates. active := make([]UserSubscription, 0) for _, sub := range r.subscriptions { if sub.UserID == userID && sub.IsActive() { active = append(active, sub) } } return active, r.err } type visibilityGroupRepo struct { GroupRepository groups []Group } func (r *visibilityGroupRepo) ListActive(context.Context) ([]Group, error) { return r.groups, nil } func TestGetUserGroupVisibilityIncludesActiveSubscriptions(t *testing.T) { for _, restricted := range []bool{false, true} { t.Run(map[bool]string{false: "unrestricted", true: "restricted"}[restricted], func(t *testing.T) { now := time.Now() subs := &visibilitySubRepo{subscriptions: []UserSubscription{ {UserID: 1, GroupID: 42, Status: SubscriptionStatusActive, ExpiresAt: now.Add(time.Hour)}, {UserID: 1, GroupID: 43, Status: SubscriptionStatusActive, ExpiresAt: now.Add(-time.Hour)}, {UserID: 1, GroupID: 44, Status: "expired", ExpiresAt: now.Add(time.Hour)}, {UserID: 2, GroupID: 45, Status: SubscriptionStatusActive, ExpiresAt: now.Add(time.Hour)}, }} svc := &APIKeyService{ userRepo: &visibilityUserRepo{user: &User{ID: 1, AllowedGroups: []int64{7}, RestrictPublicGroups: restricted}}, userSubRepo: subs, groupRepo: &visibilityGroupRepo{groups: []Group{{ID: 42, IsExclusive: true, SubscriptionType: "subscription"}}}, } available, err := svc.GetAvailableGroups(context.Background(), 1) require.NoError(t, err) require.Len(t, available, 1) visible, restrict, err := svc.GetUserGroupVisibility(context.Background(), 1) require.NoError(t, err) require.Equal(t, restricted, restrict) require.Equal(t, map[int64]struct{}{7: {}, 42: {}}, visible) require.Contains(t, visible, available[0].ID, "a subscribed group that can be bound must be visible") }) } } func TestGetUserGroupVisibilityEmptyAndErrors(t *testing.T) { failure := errors.New("repository unavailable") for _, tc := range []struct { name string userErr, subErr error }{ {name: "empty"}, {name: "user failure", userErr: failure}, {name: "subscription failure", subErr: failure}, } { t.Run(tc.name, func(t *testing.T) { subs := &visibilitySubRepo{err: tc.subErr} svc := &APIKeyService{userRepo: &visibilityUserRepo{user: &User{ID: 1}, err: tc.userErr}, userSubRepo: subs} got, _, err := svc.GetUserGroupVisibility(context.Background(), 1) if tc.userErr != nil || tc.subErr != nil { require.ErrorIs(t, err, failure) require.Nil(t, got, "repository failures must not become anonymous visibility") } else { require.NoError(t, err) require.NotNil(t, got, "an empty logged-in user must not become anonymous") require.Empty(t, got) } if tc.userErr != nil { require.Zero(t, subs.calls) } }) } }