package repository import ( "baron-sso-backend/internal/domain" "context" "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUserProjectionRepository_ReplaceAllFromKratosMarksReadyWithoutDeletingUsersMissingFromPartialList(t *testing.T) { ctx := context.Background() repo := NewUserProjectionRepository(testDB) require.NoError(t, testDB.Exec("DELETE FROM user_projection_states").Error) require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error) require.NoError(t, testDB.Exec("DELETE FROM users").Error) tenantID := "10000000-0000-0000-0000-000000000001" tenantSlug := "projection-saman" require.NoError(t, testDB.Create(&domain.Tenant{ ID: tenantID, Name: "Projection Saman", Slug: tenantSlug, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, }).Error) existing := &domain.User{ ID: "00000000-0000-0000-0000-000000000099", Email: "existing@example.com", Name: "Existing", CompanyCode: tenantSlug, TenantID: &tenantID, } require.NoError(t, NewUserRepository(testDB).Create(ctx, existing)) users := []domain.User{ { ID: "00000000-0000-0000-0000-000000000101", Email: "one@example.com", Name: "One", CompanyCode: tenantSlug, TenantID: &tenantID, CreatedAt: time.Now(), UpdatedAt: time.Now(), }, { ID: "00000000-0000-0000-0000-000000000102", Email: "two@example.com", Name: "Two", TenantID: &tenantID, CreatedAt: time.Now(), UpdatedAt: time.Now(), }, } require.NoError(t, repo.ReplaceAllFromKratos(ctx, users)) ready, err := repo.IsReady(ctx) require.NoError(t, err) assert.True(t, ready) counts, err := repo.CountTenantMembers(ctx, []domain.Tenant{ {ID: tenantID, Slug: tenantSlug}, }) require.NoError(t, err) assert.Equal(t, int64(3), counts[tenantID]) var activeCount int64 require.NoError(t, testDB.Model(&domain.User{}).Count(&activeCount).Error) assert.Equal(t, int64(3), activeCount) var existingCount int64 require.NoError(t, testDB.Model(&domain.User{}).Where("id = ?", existing.ID).Count(&existingCount).Error) assert.Equal(t, int64(1), existingCount) var existingRow domain.User require.NoError(t, testDB.Unscoped().First(&existingRow, "id = ?", existing.ID).Error) assert.False(t, existingRow.DeletedAt.Valid) } func TestUserProjectionRepository_CountTenantMembersRecursiveIncludesDescendantsAndExcludesSoftDeletedUsers(t *testing.T) { ctx := context.Background() repo := NewUserProjectionRepository(testDB) parentID := "20000000-0000-0000-0000-000000000001" childID := "20000000-0000-0000-0000-000000000002" grandchildID := "20000000-0000-0000-0000-000000000003" siblingID := "20000000-0000-0000-0000-000000000004" tenantIDs := []string{parentID, childID, grandchildID, siblingID} require.NoError(t, testDB.Exec("DELETE FROM user_login_ids").Error) require.NoError(t, testDB.Exec("DELETE FROM users").Error) require.NoError(t, testDB.Unscoped().Where("id IN ?", tenantIDs).Delete(&domain.Tenant{}).Error) require.NoError(t, testDB.Create(&domain.Tenant{ ID: parentID, Name: "Recursive Parent", Slug: "recursive-parent", Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, }).Error) require.NoError(t, testDB.Create(&domain.Tenant{ ID: childID, Name: "Recursive Child", Slug: "recursive-child", Type: domain.TenantTypeOrganization, Status: domain.TenantStatusActive, ParentID: &parentID, }).Error) require.NoError(t, testDB.Create(&domain.Tenant{ ID: grandchildID, Name: "Recursive Grandchild", Slug: "recursive-grandchild", Type: domain.TenantTypeUserGroup, Status: domain.TenantStatusActive, ParentID: &childID, }).Error) require.NoError(t, testDB.Create(&domain.Tenant{ ID: siblingID, Name: "Recursive Sibling", Slug: "recursive-sibling", Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, }).Error) users := []domain.User{ {ID: "21000000-0000-0000-0000-000000000001", Email: "parent@example.com", Name: "Parent", TenantID: &parentID}, {ID: "21000000-0000-0000-0000-000000000002", Email: "child@example.com", Name: "Child", TenantID: &childID}, {ID: "21000000-0000-0000-0000-000000000003", Email: "grandchild@example.com", Name: "Grandchild", TenantID: &grandchildID}, {ID: "21000000-0000-0000-0000-000000000004", Email: "deleted-grandchild@example.com", Name: "Deleted Grandchild", TenantID: &grandchildID}, {ID: "21000000-0000-0000-0000-000000000005", Email: "sibling@example.com", Name: "Sibling", TenantID: &siblingID}, } for i := range users { require.NoError(t, testDB.Create(&users[i]).Error) } require.NoError(t, testDB.Delete(&domain.User{}, "id = ?", users[3].ID).Error) directCounts, err := repo.CountTenantMembers(ctx, []domain.Tenant{{ID: parentID}, {ID: childID}, {ID: grandchildID}, {ID: siblingID}}) require.NoError(t, err) assert.Equal(t, int64(1), directCounts[parentID]) assert.Equal(t, int64(1), directCounts[childID]) assert.Equal(t, int64(1), directCounts[grandchildID]) assert.Equal(t, int64(1), directCounts[siblingID]) recursiveCounts, err := repo.CountTenantMembersRecursive(ctx, []domain.Tenant{{ID: parentID}, {ID: childID}, {ID: grandchildID}, {ID: siblingID}}) require.NoError(t, err) assert.Equal(t, int64(3), recursiveCounts[parentID]) assert.Equal(t, int64(2), recursiveCounts[childID]) assert.Equal(t, int64(1), recursiveCounts[grandchildID]) assert.Equal(t, int64(1), recursiveCounts[siblingID]) } func TestUserProjectionRepository_MarkFailedMakesProjectionNotReady(t *testing.T) { ctx := context.Background() repo := NewUserProjectionRepository(testDB) require.NoError(t, testDB.Exec("DELETE FROM user_projection_states").Error) require.NoError(t, repo.MarkFailed(ctx, errors.New("kratos down"))) ready, err := repo.IsReady(ctx) require.NoError(t, err) assert.False(t, ready) }