package repositories import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/honeydue-api/internal/models" "github.com/treytartt/honeydue-api/internal/testutil" ) func TestUserRepository_Create(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) user := &models.User{ Username: "testuser", Email: "test@example.com", IsActive: true, } user.SetPassword("password123") err := repo.Create(user) require.NoError(t, err) assert.NotZero(t, user.ID) } func TestUserRepository_FindByID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") // Find by ID found, err := repo.FindByID(user.ID) require.NoError(t, err) assert.Equal(t, user.ID, found.ID) assert.Equal(t, "testuser", found.Username) assert.Equal(t, "test@example.com", found.Email) } func TestUserRepository_FindByID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) _, err := repo.FindByID(9999) assert.Error(t, err) } func TestUserRepository_FindByUsername(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") // Find by username found, err := repo.FindByUsername("testuser") require.NoError(t, err) assert.Equal(t, user.ID, found.ID) } func TestUserRepository_FindByEmail(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") // Find by email found, err := repo.FindByEmail("test@example.com") require.NoError(t, err) assert.Equal(t, user.ID, found.ID) } func TestUserRepository_FindByUsernameOrEmail(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") tests := []struct { name string input string expected uint }{ {"find by username", "testuser", user.ID}, {"find by email", "test@example.com", user.ID}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { found, err := repo.FindByUsernameOrEmail(tt.input) require.NoError(t, err) assert.Equal(t, tt.expected, found.ID) }) } } func TestUserRepository_Update(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") // Update user user.FirstName = "John" user.LastName = "Doe" err := repo.Update(user) require.NoError(t, err) // Verify update found, err := repo.FindByID(user.ID) require.NoError(t, err) assert.Equal(t, "John", found.FirstName) assert.Equal(t, "Doe", found.LastName) } func TestUserRepository_ExistsByUsername(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "password123") tests := []struct { name string username string expected bool }{ {"existing user", "existinguser", true}, {"non-existing user", "nonexistent", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exists, err := repo.ExistsByUsername(tt.username) require.NoError(t, err) assert.Equal(t, tt.expected, exists) }) } } func TestUserRepository_ExistsByEmail(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "password123") tests := []struct { name string email string expected bool }{ {"existing email", "existing@example.com", true}, {"non-existing email", "nonexistent@example.com", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exists, err := repo.ExistsByEmail(tt.email) require.NoError(t, err) assert.Equal(t, tt.expected, exists) }) } } func TestUserRepository_GetOrCreateProfile(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) // Create user user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123") // First call should create profile1, err := repo.GetOrCreateProfile(user.ID) require.NoError(t, err) assert.NotZero(t, profile1.ID) // Second call should return same profile profile2, err := repo.GetOrCreateProfile(user.ID) require.NoError(t, err) assert.Equal(t, profile1.ID, profile2.ID) } func TestUserRepository_FindAuthProvider(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) t.Run("email user", func(t *testing.T) { user := testutil.CreateTestUser(t, db, "emailuser", "email@test.com", "password123") provider, err := repo.FindAuthProvider(user.ID) require.NoError(t, err) assert.Equal(t, "email", provider) }) t.Run("apple user", func(t *testing.T) { user := testutil.CreateTestUser(t, db, "appleuser", "apple@test.com", "password123") appleAuth := &models.AppleSocialAuth{ UserID: user.ID, AppleID: "apple_sub_test", Email: "apple@test.com", } require.NoError(t, db.Create(appleAuth).Error) provider, err := repo.FindAuthProvider(user.ID) require.NoError(t, err) assert.Equal(t, "apple", provider) }) t.Run("google user", func(t *testing.T) { user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "password123") googleAuth := &models.GoogleSocialAuth{ UserID: user.ID, GoogleID: "google_sub_test", Email: "google@test.com", } require.NoError(t, db.Create(googleAuth).Error) provider, err := repo.FindAuthProvider(user.ID) require.NoError(t, err) assert.Equal(t, "google", provider) }) } func TestUserRepository_DeleteUserCascade(t *testing.T) { t.Run("deletes user with no residences", func(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) user := testutil.CreateTestUser(t, db, "deletebare", "deletebare@test.com", "password123") // Create profile and token profile := &models.UserProfile{UserID: user.ID, Verified: true} require.NoError(t, db.Create(profile).Error) _, err := models.GetOrCreateToken(db, user.ID) require.NoError(t, err) var fileURLs []string txErr := repo.Transaction(func(txRepo *UserRepository) error { urls, err := txRepo.DeleteUserCascade(user.ID) if err != nil { return err } fileURLs = urls return nil }) require.NoError(t, txErr) assert.Empty(t, fileURLs) // Verify user is gone var count int64 db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) // Verify profile is gone db.Model(&models.UserProfile{}).Where("user_id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) // Verify token is gone db.Model(&models.AuthToken{}).Where("user_id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) }) t.Run("returns file URLs for cleanup", func(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) user := testutil.CreateTestUser(t, db, "deletefiles", "deletefiles@test.com", "password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test Home") // Create document with file doc := &models.Document{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Test Doc", FileURL: "/uploads/documents/test.pdf", } require.NoError(t, db.Create(doc).Error) // Create document image docImage := &models.DocumentImage{ DocumentID: doc.ID, ImageURL: "/uploads/images/docimg.jpg", } require.NoError(t, db.Create(docImage).Error) var fileURLs []string txErr := repo.Transaction(func(txRepo *UserRepository) error { urls, err := txRepo.DeleteUserCascade(user.ID) if err != nil { return err } fileURLs = urls return nil }) require.NoError(t, txErr) // Should return the file URLs assert.Contains(t, fileURLs, "/uploads/documents/test.pdf") assert.Contains(t, fileURLs, "/uploads/images/docimg.jpg") // Verify everything deleted var count int64 db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) db.Model(&models.Residence{}).Where("id = ?", residence.ID).Count(&count) assert.Equal(t, int64(0), count) db.Model(&models.Document{}).Where("id = ?", doc.ID).Count(&count) assert.Equal(t, int64(0), count) }) t.Run("handles user with owned and shared residences", func(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewUserRepository(db) owner := testutil.CreateTestUser(t, db, "deleteowner", "deleteowner@test.com", "password123") otherUser := testutil.CreateTestUser(t, db, "otheruser", "other@test.com", "password123") otherResidence := testutil.CreateTestResidence(t, db, otherUser.ID, "Other Home") // Owner's residence ownedResidence := testutil.CreateTestResidence(t, db, owner.ID, "Owner Home") // Add owner as member of other user's residence db.Exec("INSERT INTO residence_residence_users (residence_id, user_id) VALUES (?, ?)", otherResidence.ID, owner.ID) txErr := repo.Transaction(func(txRepo *UserRepository) error { _, err := txRepo.DeleteUserCascade(owner.ID) return err }) require.NoError(t, txErr) // Owner's residence should be deleted var count int64 db.Model(&models.Residence{}).Where("id = ?", ownedResidence.ID).Count(&count) assert.Equal(t, int64(0), count) // Other user's residence should still exist db.Model(&models.Residence{}).Where("id = ?", otherResidence.ID).Count(&count) assert.Equal(t, int64(1), count) // Owner should no longer be a member of other's residence db.Raw("SELECT COUNT(*) FROM residence_residence_users WHERE user_id = ?", owner.ID).Count(&count) assert.Equal(t, int64(0), count) // Other user should still exist db.Model(&models.User{}).Where("id = ?", otherUser.ID).Count(&count) assert.Equal(t, int64(1), count) }) }