package repository import ( "baron-sso-backend/internal/domain" "context" "testing" "time" "github.com/google/uuid" "github.com/stretchr/testify/require" ) func TestCheckDataIntegrityDetectsTenantAndUserProblems(t *testing.T) { ctx := context.Background() suffix := uuid.NewString() parent := domain.Tenant{ ID: uuid.NewString(), Name: "Deleted Parent " + suffix, Slug: "deleted-parent-" + suffix, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } child := domain.Tenant{ ID: uuid.NewString(), Name: "Orphan Child " + suffix, Slug: "orphan-child-" + suffix, Type: domain.TenantTypeOrganization, ParentID: &parent.ID, Status: domain.TenantStatusActive, } dupA := domain.Tenant{ ID: uuid.NewString(), Name: "Duplicate A " + suffix, Slug: "Dup-" + suffix, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } dupB := domain.Tenant{ ID: uuid.NewString(), Name: "Duplicate B " + suffix, Slug: "dup-" + suffix, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } require.NoError(t, testDB.Create(&parent).Error) require.NoError(t, testDB.Create(&child).Error) require.NoError(t, testDB.Create(&dupA).Error) require.NoError(t, testDB.Create(&dupB).Error) require.NoError(t, testDB.Delete(&domain.Tenant{}, "id = ?", parent.ID).Error) orphanUser := domain.User{ ID: uuid.NewString(), Email: "orphan-" + suffix + "@example.com", Name: "Orphan User", Role: domain.RoleUser, TenantID: &parent.ID, Status: domain.UserStatusActive, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } require.NoError(t, testDB.Create(&orphanUser).Error) require.NoError(t, testDB.Create(&domain.UserLoginID{ ID: uuid.NewString(), UserID: orphanUser.ID, TenantID: parent.ID, FieldKey: "emp_id", LoginID: "EMP-" + suffix, }).Error) // Missing UserID for UserLoginID cannot be inserted due to FK constraint fk_users_user_login_ids. // So we don't test orphan_user_login_id_users here. report, err := CheckDataIntegrity(ctx, testDB) require.NoError(t, err) require.Equal(t, domain.DataIntegrityStatusFail, report.Status) require.Equal(t, int64(4), report.Summary.Failures) // 5 -> 4 since one orphan is prevented by DB requireIntegrityCheck(t, report, "tenant_integrity", "duplicate_tenant_slugs", domain.DataIntegrityStatusFail, 1) requireIntegrityCheck(t, report, "tenant_integrity", "orphan_tenant_parents", domain.DataIntegrityStatusFail, 1) requireIntegrityCheck(t, report, "user_integrity", "orphan_user_tenant_memberships", domain.DataIntegrityStatusFail, 1) requireIntegrityCheck(t, report, "user_integrity", "orphan_user_login_id_tenants", domain.DataIntegrityStatusFail, 1) requireIntegrityCheck(t, report, "user_integrity", "orphan_user_login_id_users", domain.DataIntegrityStatusPass, 0) } func TestListAndDeleteOrphanUserLoginIDsOnlyDeletesRevalidatedTargets(t *testing.T) { ctx := context.Background() suffix := uuid.NewString() validTenant := domain.Tenant{ ID: uuid.NewString(), Name: "Valid Tenant " + suffix, Slug: "valid-tenant-" + suffix, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } deletedTenant := domain.Tenant{ ID: uuid.NewString(), Name: "Deleted Tenant " + suffix, Slug: "deleted-tenant-" + suffix, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } require.NoError(t, testDB.Create(&validTenant).Error) require.NoError(t, testDB.Create(&deletedTenant).Error) validUser := domain.User{ ID: uuid.NewString(), Email: "valid-login-" + suffix + "@example.com", Name: "Valid Login User", Role: domain.RoleUser, TenantID: &validTenant.ID, Status: domain.UserStatusActive, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } deletedUser := domain.User{ ID: uuid.NewString(), Email: "deleted-login-" + suffix + "@example.com", Name: "Deleted Login User", Role: domain.RoleUser, TenantID: &validTenant.ID, Status: domain.UserStatusActive, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), } require.NoError(t, testDB.Create(&validUser).Error) require.NoError(t, testDB.Create(&deletedUser).Error) validLogin := domain.UserLoginID{ ID: uuid.NewString(), UserID: validUser.ID, TenantID: validTenant.ID, FieldKey: "emp_id", LoginID: "VALID-" + suffix, } deletedTenantLogin := domain.UserLoginID{ ID: uuid.NewString(), UserID: validUser.ID, TenantID: deletedTenant.ID, FieldKey: "emp_id", LoginID: "DELETED-TENANT-" + suffix, } deletedUserLogin := domain.UserLoginID{ ID: uuid.NewString(), UserID: deletedUser.ID, TenantID: validTenant.ID, FieldKey: "emp_id", LoginID: "DELETED-USER-" + suffix, } require.NoError(t, testDB.Create(&validLogin).Error) require.NoError(t, testDB.Create(&deletedTenantLogin).Error) require.NoError(t, testDB.Create(&deletedUserLogin).Error) require.NoError(t, testDB.Delete(&domain.Tenant{}, "id = ?", deletedTenant.ID).Error) require.NoError(t, testDB.Delete(&domain.User{}, "id = ?", deletedUser.ID).Error) items, err := ListOrphanUserLoginIDs(ctx, testDB, nil) require.NoError(t, err) orphanReasons := map[string][]string{} for _, item := range items { orphanReasons[item.ID] = item.Reasons } require.Equal(t, []string{"deleted_tenant"}, orphanReasons[deletedTenantLogin.ID]) require.Equal(t, []string{"deleted_user"}, orphanReasons[deletedUserLogin.ID]) require.NotContains(t, orphanReasons, validLogin.ID) result, err := DeleteOrphanUserLoginIDs(ctx, testDB, []string{ deletedTenantLogin.ID, validLogin.ID, "00000000-0000-0000-0000-000000000000", }) require.NoError(t, err) require.Equal(t, int64(1), result.DeletedCount) require.Len(t, result.Deleted, 1) require.Equal(t, deletedTenantLogin.ID, result.Deleted[0].ID) require.ElementsMatch(t, []string{ validLogin.ID, "00000000-0000-0000-0000-000000000000", }, result.SkippedIDs) var deletedTenantLoginCount int64 require.NoError(t, testDB.Model(&domain.UserLoginID{}).Where("id = ?", deletedTenantLogin.ID).Count(&deletedTenantLoginCount).Error) require.Equal(t, int64(0), deletedTenantLoginCount) var validLoginCount int64 require.NoError(t, testDB.Model(&domain.UserLoginID{}).Where("id = ?", validLogin.ID).Count(&validLoginCount).Error) require.Equal(t, int64(1), validLoginCount) } func requireIntegrityCheck(t *testing.T, report domain.DataIntegrityReport, sectionKey, checkKey string, status domain.DataIntegrityStatus, count int64) { t.Helper() for _, section := range report.Sections { if section.Key != sectionKey { continue } for _, check := range section.Checks { if check.Key == checkKey { require.Equal(t, status, check.Status) require.Equal(t, count, check.Count) return } } } t.Fatalf("integrity check %s/%s not found", sectionKey, checkKey) }