package service import ( "baron-sso-backend/internal/domain" "context" "errors" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type fakeUserProjectionRepo struct { replacedUsers []domain.User failedErr error replaceErr error } func (f *fakeUserProjectionRepo) IsReady(ctx context.Context) (bool, error) { return false, nil } func (f *fakeUserProjectionRepo) GetStatus(ctx context.Context) (domain.UserProjectionStatus, error) { return domain.UserProjectionStatus{}, nil } func (f *fakeUserProjectionRepo) CountTenantMembers(ctx context.Context, tenants []domain.Tenant) (map[string]int64, error) { return nil, nil } func (f *fakeUserProjectionRepo) ReplaceAllFromKratos(ctx context.Context, users []domain.User) error { f.replacedUsers = append([]domain.User(nil), users...) return f.replaceErr } func (f *fakeUserProjectionRepo) MarkFailed(ctx context.Context, syncErr error) error { f.failedErr = syncErr return nil } func TestUserProjectionSyncService_ReconcileReplacesProjectionFromKratos(t *testing.T) { ctx := context.Background() kratos := new(MockKratosAdminServiceShared) repo := &fakeUserProjectionRepo{} svc := NewUserProjectionSyncService(kratos, repo) tenantID := "00000000-0000-0000-0000-000000000001" kratos.On("ListIdentities", ctx).Return([]KratosIdentity{ { ID: "00000000-0000-0000-0000-000000000101", Traits: map[string]any{ "email": "one@example.com", "name": "One", "phone_number": "+821012345678", "companyCode": "saman", "companyCodes": []any{"saman", "group-a"}, "tenant_id": tenantID, "department": "DX", "customAttr": "kept", }, State: "active", }, }, nil).Once() count, err := svc.Reconcile(ctx) require.NoError(t, err) assert.Equal(t, 1, count) require.Len(t, repo.replacedUsers, 1) assert.Equal(t, "one@example.com", repo.replacedUsers[0].Email) assert.Equal(t, "One", repo.replacedUsers[0].Name) assert.Equal(t, "+821012345678", repo.replacedUsers[0].Phone) assert.Empty(t, repo.replacedUsers[0].CompanyCode) assert.Empty(t, repo.replacedUsers[0].CompanyCodes) require.NotNil(t, repo.replacedUsers[0].TenantID) assert.Equal(t, tenantID, *repo.replacedUsers[0].TenantID) assert.Equal(t, "kept", repo.replacedUsers[0].Metadata["customAttr"]) assert.NoError(t, repo.failedErr) kratos.AssertExpectations(t) } func TestUserProjectionSyncService_ReconcileMarksFailedWhenKratosFails(t *testing.T) { ctx := context.Background() kratos := new(MockKratosAdminServiceShared) repo := &fakeUserProjectionRepo{} svc := NewUserProjectionSyncService(kratos, repo) expectedErr := errors.New("kratos down") kratos.On("ListIdentities", ctx).Return([]KratosIdentity{}, expectedErr).Once() count, err := svc.Reconcile(ctx) assert.Equal(t, 0, count) assert.ErrorIs(t, err, expectedErr) assert.ErrorIs(t, repo.failedErr, expectedErr) assert.Empty(t, repo.replacedUsers) kratos.AssertExpectations(t) } func TestMapKratosIdentityToLocalUserPreservesArchivedStatus(t *testing.T) { user := MapKratosIdentityToLocalUser(KratosIdentity{ ID: "00000000-0000-0000-0000-000000000201", State: domain.UserStatusArchived, Traits: map[string]any{ "email": "archived@example.com", "name": "Archived User", }, }) assert.Equal(t, domain.UserStatusArchived, user.Status) }