package auth import ( "context" "strconv" "testing" "time" "base/internal/pkg/oauth" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" domainAuth "base/internal/domain/auth" ) func TestUserRepository_Create(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("create user successfully", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "John", LastName: "Doe", Email: "john.doe@example.com", EmailVerified: false, Status: domainAuth.UserStatusActive, PhoneNumber: "1234567890", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) assert.NoError(t, err) assert.NotEqual(t, uuid.Nil, user.ID) // Verify user was created found, err := repo.FindByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, user.Email, found.Email) assert.Equal(t, user.FirstName, found.FirstName) assert.Equal(t, user.LastName, found.LastName) }) t.Run("create user with duplicate email fails", func(t *testing.T) { email := "duplicate@example.com" user1 := &domainAuth.User{ ID: uuid.New(), FirstName: "User", LastName: "One", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user1) assert.NoError(t, err) user2 := &domainAuth.User{ ID: uuid.New(), FirstName: "User", LastName: "Two", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = repo.Create(ctx, user2) assert.Error(t, err) }) } func TestUserRepository_UpsertWithAccount(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("upsert creates new user and account", func(t *testing.T) { email := "newuser@example.com" user := &domainAuth.User{ ID: uuid.New(), FirstName: "New", LastName: "User", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } account := &domainAuth.Account{ ID: uuid.New(), Provider: oauth.Google, Scope: []string{"read"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } isNew, err := repo.UpsertWithAccount(ctx, email, user, account) assert.NoError(t, err) assert.True(t, isNew) // For new users, UserID is set by UpsertWithAccount assert.Equal(t, user.ID, account.UserID) // Verify user was created foundUser, err := repo.FindByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, user.Email, foundUser.Email) // Note: For new users, UpsertWithAccount sets account.UserID but doesn't create the account // The account needs to be created separately if needed }) t.Run("upsert updates existing user with new account", func(t *testing.T) { email := "existing@example.com" user := &domainAuth.User{ ID: uuid.New(), FirstName: "Existing", LastName: "User", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } // Create user first err := repo.Create(ctx, user) require.NoError(t, err) // Get the user from DB to ensure we have the correct ID foundUser, err := repo.FindByEmail(ctx, email) require.NoError(t, err) user.ID = foundUser.ID // Create first account with Google provider account1 := &domainAuth.Account{ ID: uuid.New(), UserID: user.ID, Provider: oauth.Google, Scope: []string{"read"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } accountRepo := createTestAccountRepository(db) err = accountRepo.Create(ctx, account1) require.NoError(t, err) // Upsert with different provider (GitHub) - should create new account account2 := &domainAuth.Account{ ID: uuid.New(), UserID: user.ID, // Set UserID before upsert Provider: oauth.GitHub, Scope: []string{"read", "write"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } // Use the same user object but ensure it has the correct ID userForUpsert := &domainAuth.User{ ID: user.ID, FirstName: user.FirstName, LastName: user.LastName, Email: user.Email, Status: user.Status, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, } isNew, err := repo.UpsertWithAccount(ctx, email, userForUpsert, account2) assert.NoError(t, err) assert.False(t, isNew) // Note: account.UserID is not updated by UpsertWithAccount when user exists, // but it should already be set correctly assert.Equal(t, user.ID, account2.UserID) // Account ID should be set after creation assert.NotEqual(t, uuid.Nil, account2.ID) // Verify the GitHub account was created by finding it by ID foundAccount2, err := accountRepo.FindByID(ctx, account2.ID) assert.NoError(t, err) assert.NotNil(t, foundAccount2) assert.Equal(t, account2.UserID, foundAccount2.UserID) assert.Equal(t, account2.Provider, foundAccount2.Provider) assert.Equal(t, account2.Scope, foundAccount2.Scope) // Verify both accounts exist accounts, err := accountRepo.FindByUserID(ctx, user.ID) assert.NoError(t, err) assert.GreaterOrEqual(t, len(accounts), 2) // At least Google and GitHub accounts }) } func TestUserRepository_FindByID(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("find existing user by id", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Find", LastName: "User", Email: "find@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) found, err := repo.FindByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Equal(t, user.Email, found.Email) }) t.Run("find user with roles", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Role", LastName: "User", Email: "role@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) // Create role roleRepo := createTestRoleRepository(db) role := &domainAuth.Role{ ID: uuid.New(), Name: "admin", Description: "Administrator", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = roleRepo.Create(ctx, role) require.NoError(t, err) // Assign role to user userRoleRepo := createTestUserRoleRepository(db) err = userRoleRepo.Create(ctx, user.ID, role.ID) require.NoError(t, err) // Find user with roles found, err := repo.FindByID(ctx, user.ID, domainAuth.WithRoles()) assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Len(t, found.Roles, 1) assert.Equal(t, role.Name, found.Roles[0].Name) }) t.Run("find non-existent user", func(t *testing.T) { nonExistentID := uuid.New() found, err := repo.FindByID(ctx, nonExistentID) assert.Error(t, err) assert.Nil(t, found) }) } func TestUserRepository_FindByEmail(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("find existing user by email", func(t *testing.T) { email := "email@example.com" user := &domainAuth.User{ ID: uuid.New(), FirstName: "Email", LastName: "User", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) found, err := repo.FindByEmail(ctx, email) assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Equal(t, email, found.Email) }) t.Run("find user with accounts", func(t *testing.T) { email := "accounts@example.com" user := &domainAuth.User{ ID: uuid.New(), FirstName: "Accounts", LastName: "User", Email: email, Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) // Create account accountRepo := createTestAccountRepository(db) account := &domainAuth.Account{ ID: uuid.New(), UserID: user.ID, Provider: oauth.Google, Scope: []string{"read"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = accountRepo.Create(ctx, account) require.NoError(t, err) // Find user with accounts found, err := repo.FindByEmail(ctx, email, domainAuth.WithAccounts()) assert.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Len(t, found.Accounts, 1) assert.Equal(t, account.Provider, found.Accounts[0].Provider) }) t.Run("find non-existent user by email", func(t *testing.T) { found, err := repo.FindByEmail(ctx, "nonexistent@example.com") assert.Error(t, err) assert.Nil(t, found) }) } func TestUserRepository_Update(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("update user successfully", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Update", LastName: "User", Email: "update@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) // Update user user.FirstName = "Updated" user.EmailVerified = true user.Status = domainAuth.UserStatusInactive err = repo.Update(ctx, user) assert.NoError(t, err) // Verify update found, err := repo.FindByID(ctx, user.ID) assert.NoError(t, err) assert.Equal(t, "Updated", found.FirstName) assert.True(t, found.EmailVerified) assert.Equal(t, domainAuth.UserStatusInactive, found.Status) }) } func TestUserRepository_Delete(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("delete user successfully", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Delete", LastName: "User", Email: "delete@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) err = repo.Delete(ctx, user.ID) assert.NoError(t, err) // Verify deletion (soft delete) found, err := repo.FindByID(ctx, user.ID) assert.Error(t, err) assert.Nil(t, found) }) } func TestUserRepository_List(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() // Create multiple users for i := 0; i < 5; i++ { user := &domainAuth.User{ ID: uuid.New(), FirstName: "User", LastName: "Test", Email: "user" + strconv.Itoa(i) + "@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) } t.Run("list users with limit and offset", func(t *testing.T) { users, err := repo.List(ctx, 3, 0) assert.NoError(t, err) assert.Len(t, users, 3) users, err = repo.List(ctx, 3, 3) assert.NoError(t, err) assert.Len(t, users, 2) // Remaining 2 users }) t.Run("list users with relations", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Relation", LastName: "User", Email: "relation@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) // Create role and assign roleRepo := createTestRoleRepository(db) role := &domainAuth.Role{ ID: uuid.New(), Name: "user", Description: "Regular user", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = roleRepo.Create(ctx, role) require.NoError(t, err) userRoleRepo := createTestUserRoleRepository(db) err = userRoleRepo.Create(ctx, user.ID, role.ID) require.NoError(t, err) users, err := repo.List(ctx, 10, 0, domainAuth.WithRoles()) assert.NoError(t, err) assert.Greater(t, len(users), 0) // Find our user in the list var foundUser *domainAuth.User for _, u := range users { if u.ID == user.ID { foundUser = u break } } require.NotNil(t, foundUser) assert.Len(t, foundUser.Roles, 1) }) } func TestUserRepository_Count(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("count users", func(t *testing.T) { initialCount, err := repo.Count(ctx) assert.NoError(t, err) assert.Equal(t, int64(0), initialCount) // Create users for i := 0; i < 3; i++ { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Count", LastName: "User", Email: "count" + strconv.Itoa(i) + "@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) } count, err := repo.Count(ctx) assert.NoError(t, err) assert.Equal(t, int64(3), count) }) } func TestUserRepository_UserRoles(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("get user roles", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Roles", LastName: "User", Email: "roles@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) roleRepo := createTestRoleRepository(db) role1 := &domainAuth.Role{ ID: uuid.New(), Name: "admin", Description: "Admin role", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = roleRepo.Create(ctx, role1) require.NoError(t, err) role2 := &domainAuth.Role{ ID: uuid.New(), Name: "user", Description: "User role", CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = roleRepo.Create(ctx, role2) require.NoError(t, err) userRoleRepo := createTestUserRoleRepository(db) err = userRoleRepo.Create(ctx, user.ID, role1.ID) require.NoError(t, err) err = userRoleRepo.Create(ctx, user.ID, role2.ID) require.NoError(t, err) roles, err := repo.UserRoles(ctx, user.ID) assert.NoError(t, err) assert.Len(t, roles, 2) }) } func TestUserRepository_UserAccounts(t *testing.T) { db := setupTestDB(t) repo := createTestUserRepository(db) ctx := context.Background() t.Run("get user accounts", func(t *testing.T) { user := &domainAuth.User{ ID: uuid.New(), FirstName: "Accounts", LastName: "User", Email: "accounts@example.com", Status: domainAuth.UserStatusActive, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err := repo.Create(ctx, user) require.NoError(t, err) accountRepo := createTestAccountRepository(db) account1 := &domainAuth.Account{ ID: uuid.New(), UserID: user.ID, Provider: oauth.Google, Scope: []string{"read"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = accountRepo.Create(ctx, account1) require.NoError(t, err) account2 := &domainAuth.Account{ ID: uuid.New(), UserID: user.ID, Provider: oauth.GitHub, Scope: []string{"read", "write"}, CreatedAt: time.Now(), UpdatedAt: time.Now(), } err = accountRepo.Create(ctx, account2) require.NoError(t, err) accounts, err := repo.UserAccounts(ctx, user.ID) assert.NoError(t, err) assert.Len(t, accounts, 2) }) }