package userservice import ( "context" "errors" "testing" "galaxy/authsession/internal/domain/common" "galaxy/authsession/internal/domain/userresolution" "galaxy/authsession/internal/ports" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestStubDirectoryResolveByEmail(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedExisting(common.Email("existing@example.com"), common.UserID("user-existing"))) require.NoError(t, directory.SeedBlockedEmail(common.Email("blocked@example.com"), userresolution.BlockReasonCode("policy_block"))) tests := []struct { name string email common.Email wantKind userresolution.Kind wantUserID common.UserID wantReasonCode userresolution.BlockReasonCode }{ { name: "zero value unknown email is creatable", email: common.Email("new@example.com"), wantKind: userresolution.KindCreatable, }, { name: "existing email", email: common.Email("existing@example.com"), wantKind: userresolution.KindExisting, wantUserID: common.UserID("user-existing"), }, { name: "blocked email", email: common.Email("blocked@example.com"), wantKind: userresolution.KindBlocked, wantReasonCode: userresolution.BlockReasonCode("policy_block"), }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() result, err := directory.ResolveByEmail(context.Background(), tt.email) require.NoError(t, err) assert.Equal(t, tt.wantKind, result.Kind) assert.Equal(t, tt.wantUserID, result.UserID) assert.Equal(t, tt.wantReasonCode, result.BlockReasonCode) }) } } func TestStubDirectoryEnsureUserByEmail(t *testing.T) { t.Parallel() t.Run("existing", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedExisting(common.Email("existing@example.com"), common.UserID("user-existing"))) result, err := directory.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{ Email: common.Email("existing@example.com"), RegistrationContext: &ports.RegistrationContext{ PreferredLanguage: "en", TimeZone: "Europe/Kaliningrad", }, }) require.NoError(t, err) assert.Equal(t, ports.EnsureUserOutcomeExisting, result.Outcome) assert.Equal(t, common.UserID("user-existing"), result.UserID) }) t.Run("blocked", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedBlockedEmail(common.Email("blocked@example.com"), userresolution.BlockReasonCode("policy_block"))) result, err := directory.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{ Email: common.Email("blocked@example.com"), RegistrationContext: &ports.RegistrationContext{ PreferredLanguage: "en", TimeZone: "Europe/Kaliningrad", }, }) require.NoError(t, err) assert.Equal(t, ports.EnsureUserOutcomeBlocked, result.Outcome) assert.Equal(t, userresolution.BlockReasonCode("policy_block"), result.BlockReasonCode) }) t.Run("created queued then existing", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.QueueCreatedUserIDs(common.UserID("user-created"))) first, err := directory.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{ Email: common.Email("created@example.com"), RegistrationContext: &ports.RegistrationContext{ PreferredLanguage: "en", TimeZone: "Europe/Kaliningrad", }, }) require.NoError(t, err) assert.Equal(t, ports.EnsureUserOutcomeCreated, first.Outcome) assert.Equal(t, common.UserID("user-created"), first.UserID) second, err := directory.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{ Email: common.Email("created@example.com"), RegistrationContext: &ports.RegistrationContext{ PreferredLanguage: "fr", TimeZone: "Europe/Paris", }, }) require.NoError(t, err) assert.Equal(t, ports.EnsureUserOutcomeExisting, second.Outcome) assert.Equal(t, common.UserID("user-created"), second.UserID) }) t.Run("created fallback id", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} result, err := directory.EnsureUserByEmail(context.Background(), ports.EnsureUserInput{ Email: common.Email("fallback@example.com"), RegistrationContext: &ports.RegistrationContext{ PreferredLanguage: "en", TimeZone: "Europe/Kaliningrad", }, }) require.NoError(t, err) assert.Equal(t, ports.EnsureUserOutcomeCreated, result.Outcome) assert.Equal(t, common.UserID("user-1"), result.UserID) }) } func TestStubDirectoryExistsByUserID(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedExisting(common.Email("existing@example.com"), common.UserID("user-existing"))) exists, err := directory.ExistsByUserID(context.Background(), common.UserID("user-existing")) require.NoError(t, err) assert.True(t, exists) exists, err = directory.ExistsByUserID(context.Background(), common.UserID("missing")) require.NoError(t, err) assert.False(t, exists) } func TestStubDirectoryBlockByEmail(t *testing.T) { t.Parallel() t.Run("unknown email becomes blocked without user id", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} result, err := directory.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{ Email: common.Email("blocked@example.com"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeBlocked, result.Outcome) assert.True(t, result.UserID.IsZero()) resolution, err := directory.ResolveByEmail(context.Background(), common.Email("blocked@example.com")) require.NoError(t, err) assert.Equal(t, userresolution.KindBlocked, resolution.Kind) }) t.Run("existing user preserves linked user id and repeat is already blocked", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1"))) first, err := directory.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{ Email: common.Email("pilot@example.com"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeBlocked, first.Outcome) assert.Equal(t, common.UserID("user-1"), first.UserID) second, err := directory.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{ Email: common.Email("pilot@example.com"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeAlreadyBlocked, second.Outcome) assert.Equal(t, common.UserID("user-1"), second.UserID) }) } func TestStubDirectoryBlockByUserID(t *testing.T) { t.Parallel() t.Run("unknown user wraps ErrNotFound", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} _, err := directory.BlockByUserID(context.Background(), ports.BlockUserByIDInput{ UserID: common.UserID("missing"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.Error(t, err) assert.ErrorIs(t, err, ports.ErrNotFound) }) t.Run("existing user blocks then returns already blocked", func(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedExisting(common.Email("pilot@example.com"), common.UserID("user-1"))) first, err := directory.BlockByUserID(context.Background(), ports.BlockUserByIDInput{ UserID: common.UserID("user-1"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeBlocked, first.Outcome) assert.Equal(t, common.UserID("user-1"), first.UserID) second, err := directory.BlockByUserID(context.Background(), ports.BlockUserByIDInput{ UserID: common.UserID("user-1"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeAlreadyBlocked, second.Outcome) assert.Equal(t, common.UserID("user-1"), second.UserID) }) } func TestStubDirectoryContextAndValidation(t *testing.T) { t.Parallel() directory := &StubDirectory{} cancelledCtx, cancel := context.WithCancel(context.Background()) cancel() tests := []struct { name string run func() error want string }{ { name: "resolve nil context", run: func() error { _, err := directory.ResolveByEmail(nil, common.Email("pilot@example.com")) return err }, want: "nil context", }, { name: "ensure cancelled context", run: func() error { _, err := directory.EnsureUserByEmail(cancelledCtx, ports.EnsureUserInput{ Email: common.Email("pilot@example.com"), }) return err }, want: context.Canceled.Error(), }, { name: "exists invalid user id", run: func() error { _, err := directory.ExistsByUserID(context.Background(), common.UserID(" bad ")) return err }, want: "exists by user id", }, { name: "block by email invalid email", run: func() error { _, err := directory.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{ Email: common.Email("bad"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) return err }, want: "block by email", }, { name: "seed invalid user id", run: func() error { return directory.SeedExisting(common.Email("pilot@example.com"), common.UserID(" bad ")) }, want: "seed existing user id", }, { name: "queue invalid created user id", run: func() error { return directory.QueueCreatedUserIDs(common.UserID(" bad ")) }, want: "queue created user id 0", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() err := tt.run() require.Error(t, err) assert.ErrorContains(t, err, tt.want) }) } } func TestStubDirectorySeedBlockedUser(t *testing.T) { t.Parallel() directory := &StubDirectory{} require.NoError(t, directory.SeedBlockedUser( common.Email("pilot@example.com"), common.UserID("user-1"), userresolution.BlockReasonCode("policy_block"), )) result, err := directory.BlockByEmail(context.Background(), ports.BlockUserByEmailInput{ Email: common.Email("pilot@example.com"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.NoError(t, err) assert.Equal(t, ports.BlockUserOutcomeAlreadyBlocked, result.Outcome) assert.Equal(t, common.UserID("user-1"), result.UserID) } func TestStubDirectoryCancelledContextWrapsContextError(t *testing.T) { t.Parallel() directory := &StubDirectory{} cancelledCtx, cancel := context.WithCancel(context.Background()) cancel() _, err := directory.BlockByUserID(cancelledCtx, ports.BlockUserByIDInput{ UserID: common.UserID("user-1"), ReasonCode: userresolution.BlockReasonCode("policy_block"), }) require.Error(t, err) assert.True(t, errors.Is(err, context.Canceled)) assert.ErrorContains(t, err, "block by user id") }