package repository import ( "baron-sso-backend/internal/domain" "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestUserRepository(t *testing.T) { repo := NewUserRepository(testDB) ctx := context.Background() // Ensure User table exists and clean for tests _ = testDB.AutoMigrate(&domain.User{}) t.Run("Create and FindByEmail", func(t *testing.T) { user := &domain.User{ Email: "test@example.com", Name: "Test User", Role: "user", } err := repo.Create(ctx, user) assert.NoError(t, err) assert.NotEmpty(t, user.ID) found, err := repo.FindByEmail(ctx, "test@example.com") assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Equal(t, "Test User", found.Name) }) t.Run("Update User Info", func(t *testing.T) { user := &domain.User{ Email: "update@example.com", Name: "Before Update", Role: "user", } _ = repo.Create(ctx, user) user.Name = "After Update" user.Phone = "010-1234-5678" err := repo.Update(ctx, user) assert.NoError(t, err) found, err := repo.FindByEmail(ctx, "update@example.com") assert.NoError(t, err) assert.Equal(t, "After Update", found.Name) assert.Equal(t, "010-1234-5678", found.Phone) }) t.Run("Update preserves archived email reservation", func(t *testing.T) { testDB.Exec("DELETE FROM user_login_ids") testDB.Exec("DELETE FROM users") archived := &domain.User{ ID: "00000000-0000-0000-0000-00000000a001", Email: "reserved@example.com", Name: "Archived User", Role: domain.RoleUser, Status: domain.UserStatusArchived, } replacement := &domain.User{ ID: "00000000-0000-0000-0000-00000000a002", Email: "reserved@example.com", Name: "Replacement User", Role: domain.RoleUser, Status: domain.UserStatusActive, } require.NoError(t, repo.Create(ctx, archived)) err := repo.Update(ctx, replacement) require.Error(t, err) require.Contains(t, err.Error(), "archived user") found, err := repo.FindByEmail(ctx, archived.Email) require.NoError(t, err) require.Equal(t, archived.ID, found.ID) require.Equal(t, domain.UserStatusArchived, found.Status) }) t.Run("List Users with Search", func(t *testing.T) { // Add some users _ = repo.Create(ctx, &domain.User{Email: "alice@test.com", Name: "Alice", Role: "user"}) _ = repo.Create(ctx, &domain.User{Email: "bob@test.com", Name: "Bob", Role: "user"}) users, total, err := repo.List(ctx, 0, 10, "Alice", "") assert.NoError(t, err) assert.True(t, total >= 1) assert.Equal(t, "Alice", users[0].Name) }) t.Run("Delete User", func(t *testing.T) { user := &domain.User{Email: "delete@example.com", Name: "To Delete"} _ = repo.Create(ctx, user) err := repo.Delete(ctx, user.ID) assert.NoError(t, err) found, err := repo.FindByEmail(ctx, "delete@example.com") assert.Error(t, err) // Should not be found assert.Nil(t, found) }) t.Run("CountByCompanyCodes", func(t *testing.T) { // Clean start for this subtest testDB.Exec("DELETE FROM user_login_ids") testDB.Exec("DELETE FROM users") testDB.Exec("DELETE FROM tenant_domains") tenantA := createUserRepositoryTestTenant(t, "tenant-a") tenantB := createUserRepositoryTestTenant(t, "tenant-b") users := []domain.User{ {Email: "u1@a.com", Name: "U1", TenantID: &tenantA.ID}, {Email: "u2@a.com", Name: "U2", TenantID: &tenantA.ID}, {Email: "u3@b.com", Name: "U3", TenantID: &tenantB.ID}, {Email: "u4@none.com", Name: "U4"}, } for _, u := range users { _ = repo.Create(ctx, &u) } counts, err := repo.CountByCompanyCodes(ctx, []string{"tenant-a", "tenant-b", "tenant-c"}) assert.NoError(t, err) assert.Equal(t, int64(2), counts["tenant-a"]) assert.Equal(t, int64(1), counts["tenant-b"]) assert.Equal(t, int64(0), counts["tenant-c"]) }) t.Run("CountByCompanyCodes excludes soft deleted cache rows", func(t *testing.T) { testDB.Exec("DELETE FROM user_login_ids") testDB.Exec("DELETE FROM users") testDB.Exec("DELETE FROM tenant_domains") tenantA := createUserRepositoryTestTenant(t, "tenant-a") active := &domain.User{Email: "active@a.com", Name: "Active", TenantID: &tenantA.ID} deleted := &domain.User{Email: "deleted@a.com", Name: "Deleted", TenantID: &tenantA.ID} secondDeleted := &domain.User{Email: "second-deleted@a.com", Name: "Second Deleted", TenantID: &tenantA.ID} assert.NoError(t, repo.Create(ctx, active)) assert.NoError(t, repo.Create(ctx, deleted)) assert.NoError(t, repo.Create(ctx, secondDeleted)) assert.NoError(t, repo.Delete(ctx, deleted.ID)) assert.NoError(t, repo.Delete(ctx, secondDeleted.ID)) counts, err := repo.CountByCompanyCodes(ctx, []string{"tenant-a"}) assert.NoError(t, err) assert.Equal(t, int64(1), counts["tenant-a"]) }) t.Run("Multi-Identifier Support", func(t *testing.T) { _ = testDB.AutoMigrate(&domain.UserLoginID{}) testDB.Exec("DELETE FROM user_login_ids") testDB.Exec("DELETE FROM users") user := &domain.User{Email: "multi@test.com", Name: "Multi"} _ = repo.Create(ctx, user) t1 := "00000000-0000-0000-0000-000000000001" t2 := "00000000-0000-0000-0000-000000000002" loginIDs := []domain.UserLoginID{ {UserID: user.ID, TenantID: t1, FieldKey: "emp_id", LoginID: "E001"}, {UserID: user.ID, TenantID: t2, FieldKey: "student_id", LoginID: "S001"}, } err := repo.UpdateUserLoginIDs(ctx, user.ID, loginIDs) assert.NoError(t, err) // Get and Verify saved, err := repo.GetUserLoginIDs(ctx, user.ID) assert.NoError(t, err) assert.Len(t, saved, 2) // IsLoginIDTaken taken, err := repo.IsLoginIDTaken(ctx, "E001") assert.NoError(t, err) assert.True(t, taken) taken, err = repo.IsLoginIDTaken(ctx, "UNKNOWN") assert.NoError(t, err) assert.False(t, taken) // FindTenantIDByLoginID tid, err := repo.FindTenantIDByLoginID(ctx, "S001") assert.NoError(t, err) assert.Equal(t, t2, tid) // Update (Replace) newList := []domain.UserLoginID{ {UserID: user.ID, TenantID: t1, FieldKey: "emp_id", LoginID: "E002"}, } err = repo.UpdateUserLoginIDs(ctx, user.ID, newList) assert.NoError(t, err) saved, _ = repo.GetUserLoginIDs(ctx, user.ID) assert.Len(t, saved, 1) assert.Equal(t, "E002", saved[0].LoginID) }) } func createUserRepositoryTestTenant(t *testing.T, slug string) domain.Tenant { t.Helper() require.NoError(t, testDB.Unscoped().Where("slug = ?", slug).Delete(&domain.Tenant{}).Error) tenant := domain.Tenant{ ID: uuid.NewString(), Name: "Tenant " + slug, Slug: slug, Type: domain.TenantTypeCompany, Status: domain.TenantStatusActive, } require.NoError(t, testDB.Create(&tenant).Error) return tenant }