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 TestContractorRepository_Create(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := &models.Contractor{ ResidenceID: &residence.ID, CreatedByID: user.ID, Name: "Mike's Plumbing", Company: "Mike's Plumbing Co.", Phone: "+1-555-1234", Email: "mike@plumbing.com", IsActive: true, } err := repo.Create(contractor) require.NoError(t, err) assert.NotZero(t, contractor.ID) } func TestContractorRepository_FindByID(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Test Contractor") found, err := repo.FindByID(contractor.ID) require.NoError(t, err) assert.Equal(t, contractor.ID, found.ID) assert.Equal(t, "Test Contractor", found.Name) } func TestContractorRepository_FindByID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) _, err := repo.FindByID(9999) assert.Error(t, err) } func TestContractorRepository_FindByID_InactiveNotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Inactive Contractor") // Soft-delete db.Model(&models.Contractor{}).Where("id = ?", contractor.ID).Update("is_active", false) _, err := repo.FindByID(contractor.ID) assert.Error(t, err) } func TestContractorRepository_FindByResidence(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") otherResidence := testutil.CreateTestResidence(t, db, user.ID, "Other House") testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor A") testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor B") testutil.CreateTestContractor(t, db, otherResidence.ID, user.ID, "Other Contractor") contractors, err := repo.FindByResidence(residence.ID) require.NoError(t, err) assert.Len(t, contractors, 2) } func TestContractorRepository_FindByResidence_ExcludesInactive(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") active := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Active Contractor") inactive := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Inactive Contractor") db.Model(&models.Contractor{}).Where("id = ?", inactive.ID).Update("is_active", false) contractors, err := repo.FindByResidence(residence.ID) require.NoError(t, err) assert.Len(t, contractors, 1) assert.Equal(t, active.ID, contractors[0].ID) } func TestContractorRepository_FindByUser_WithResidences(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Residence Contractor") contractors, err := repo.FindByUser(user.ID, []uint{residence.ID}) require.NoError(t, err) assert.Len(t, contractors, 1) assert.Equal(t, "Residence Contractor", contractors[0].Name) } func TestContractorRepository_FindByUser_NoResidences(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") // Personal contractor with no residence personal := &models.Contractor{ ResidenceID: nil, CreatedByID: user.ID, Name: "Personal Contractor", IsActive: true, } err := db.Create(personal).Error require.NoError(t, err) contractors, err := repo.FindByUser(user.ID, []uint{}) require.NoError(t, err) assert.Len(t, contractors, 1) assert.Equal(t, "Personal Contractor", contractors[0].Name) } func TestContractorRepository_Update(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Original Name") contractor.Name = "Updated Name" contractor.Phone = "+1-555-9999" err := repo.Update(contractor) require.NoError(t, err) found, err := repo.FindByID(contractor.ID) require.NoError(t, err) assert.Equal(t, "Updated Name", found.Name) assert.Equal(t, "+1-555-9999", found.Phone) } func TestContractorRepository_Delete(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "To Delete") err := repo.Delete(contractor.ID) require.NoError(t, err) // Should not be found (soft delete) _, err = repo.FindByID(contractor.ID) assert.Error(t, err) } func TestContractorRepository_CountByResidence(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor 1") testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Contractor 2") count, err := repo.CountByResidence(residence.ID) require.NoError(t, err) assert.Equal(t, int64(2), count) } func TestContractorRepository_CountByResidenceIDs(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") r1 := testutil.CreateTestResidence(t, db, user.ID, "House 1") r2 := testutil.CreateTestResidence(t, db, user.ID, "House 2") testutil.CreateTestContractor(t, db, r1.ID, user.ID, "Contractor A") testutil.CreateTestContractor(t, db, r2.ID, user.ID, "Contractor B") testutil.CreateTestContractor(t, db, r2.ID, user.ID, "Contractor C") count, err := repo.CountByResidenceIDs([]uint{r1.ID, r2.ID}) require.NoError(t, err) assert.Equal(t, int64(3), count) } func TestContractorRepository_CountByResidenceIDs_Empty(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) count, err := repo.CountByResidenceIDs([]uint{}) require.NoError(t, err) assert.Equal(t, int64(0), count) } func TestContractorRepository_SetSpecialties(t *testing.T) { db := testutil.SetupTestDB(t) testutil.SeedLookupData(t, db) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Skilled Contractor") // Get seeded specialties var specialties []models.ContractorSpecialty err := db.Find(&specialties).Error require.NoError(t, err) require.GreaterOrEqual(t, len(specialties), 2) // Set specialties err = repo.SetSpecialties(contractor.ID, []uint{specialties[0].ID, specialties[1].ID}) require.NoError(t, err) // Verify via FindByID found, err := repo.FindByID(contractor.ID) require.NoError(t, err) assert.Len(t, found.Specialties, 2) } func TestContractorRepository_SetSpecialties_ClearsExisting(t *testing.T) { db := testutil.SetupTestDB(t) testutil.SeedLookupData(t, db) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Skilled Contractor") var specialties []models.ContractorSpecialty err := db.Find(&specialties).Error require.NoError(t, err) require.GreaterOrEqual(t, len(specialties), 2) // Set initial specialties err = repo.SetSpecialties(contractor.ID, []uint{specialties[0].ID, specialties[1].ID}) require.NoError(t, err) // Clear all specialties err = repo.SetSpecialties(contractor.ID, []uint{}) require.NoError(t, err) found, err := repo.FindByID(contractor.ID) require.NoError(t, err) assert.Len(t, found.Specialties, 0) } func TestContractorRepository_GetAllSpecialties(t *testing.T) { db := testutil.SetupTestDB(t) testutil.SeedLookupData(t, db) repo := NewContractorRepository(db) specialties, err := repo.GetAllSpecialties() require.NoError(t, err) assert.GreaterOrEqual(t, len(specialties), 4) // Seeded 4 specialties } func TestContractorRepository_FindSpecialtyByID(t *testing.T) { db := testutil.SetupTestDB(t) testutil.SeedLookupData(t, db) repo := NewContractorRepository(db) var seeded models.ContractorSpecialty err := db.First(&seeded).Error require.NoError(t, err) found, err := repo.FindSpecialtyByID(seeded.ID) require.NoError(t, err) assert.Equal(t, seeded.Name, found.Name) } func TestContractorRepository_FindSpecialtyByID_NotFound(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) _, err := repo.FindSpecialtyByID(9999) assert.Error(t, err) } func TestContractorRepository_GetTasksForContractor(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") contractor := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Task Contractor") // Create tasks linked to contractor task1 := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Task 1", ContractorID: &contractor.ID, Version: 1, } err := db.Create(task1).Error require.NoError(t, err) task2 := &models.Task{ ResidenceID: residence.ID, CreatedByID: user.ID, Title: "Task 2", ContractorID: &contractor.ID, Version: 1, } err = db.Create(task2).Error require.NoError(t, err) tasks, err := repo.GetTasksForContractor(contractor.ID) require.NoError(t, err) assert.Len(t, tasks, 2) } func TestContractorRepository_FindByResidence_FavoritesFirst(t *testing.T) { db := testutil.SetupTestDB(t) repo := NewContractorRepository(db) user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123") residence := testutil.CreateTestResidence(t, db, user.ID, "Test House") // Create non-favorite first (alphabetically first) testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Alpha Contractor") // Create favorite fav := testutil.CreateTestContractor(t, db, residence.ID, user.ID, "Zeta Contractor") db.Model(&models.Contractor{}).Where("id = ?", fav.ID).Update("is_favorite", true) contractors, err := repo.FindByResidence(residence.ID) require.NoError(t, err) assert.Len(t, contractors, 2) // Favorite should be first assert.Equal(t, fav.ID, contractors[0].ID) }