Coverage priorities 1-5: test pure functions, extract interfaces, mock-based handler tests
- Priority 1: Test NewSendEmailTask + NewSendPushTask (5 tests) - Priority 2: Test customHTTPErrorHandler — all 15+ branches (21 tests) - Priority 3: Extract Enqueuer interface + payload builders in worker pkg (5 tests) - Priority 4: Extract ClassifyFile/ComputeRelPath in migrate-encrypt (6 tests) - Priority 5: Define Handler interfaces, refactor to accept them, mock-based tests (14 tests) - Fix .gitignore: /worker instead of worker to stop ignoring internal/worker/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
205
internal/repositories/admin_repo_test.go
Normal file
205
internal/repositories/admin_repo_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
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 TestAdminRepository_Create(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{
|
||||
Email: "admin@test.com",
|
||||
FirstName: "Test",
|
||||
LastName: "Admin",
|
||||
Role: models.AdminRoleAdmin,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
|
||||
err := repo.Create(admin)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, admin.ID)
|
||||
}
|
||||
|
||||
func TestAdminRepository_Create_Duplicate(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin1 := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin1.SetPassword("Password123"))
|
||||
err := repo.Create(admin1)
|
||||
require.NoError(t, err)
|
||||
|
||||
admin2 := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin2.SetPassword("Password123"))
|
||||
err = repo.Create(admin2)
|
||||
assert.ErrorIs(t, err, ErrAdminExists)
|
||||
}
|
||||
|
||||
func TestAdminRepository_FindByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
found, err := repo.FindByID(admin.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "admin@test.com", found.Email)
|
||||
}
|
||||
|
||||
func TestAdminRepository_FindByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
_, err := repo.FindByID(9999)
|
||||
assert.ErrorIs(t, err, ErrAdminNotFound)
|
||||
}
|
||||
|
||||
func TestAdminRepository_FindByEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
found, err := repo.FindByEmail("admin@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, admin.ID, found.ID)
|
||||
}
|
||||
|
||||
func TestAdminRepository_FindByEmail_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "Admin@Test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
found, err := repo.FindByEmail("admin@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, admin.ID, found.ID)
|
||||
}
|
||||
|
||||
func TestAdminRepository_FindByEmail_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
_, err := repo.FindByEmail("nonexistent@test.com")
|
||||
assert.ErrorIs(t, err, ErrAdminNotFound)
|
||||
}
|
||||
|
||||
func TestAdminRepository_Update(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", FirstName: "Old", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
admin.FirstName = "New"
|
||||
err := repo.Update(admin)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(admin.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "New", found.FirstName)
|
||||
}
|
||||
|
||||
func TestAdminRepository_Delete(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
err := repo.Delete(admin.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindByID(admin.ID)
|
||||
assert.ErrorIs(t, err, ErrAdminNotFound)
|
||||
}
|
||||
|
||||
func TestAdminRepository_UpdateLastLogin(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
assert.Nil(t, admin.LastLogin)
|
||||
|
||||
err := repo.UpdateLastLogin(admin.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(admin.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, found.LastLogin)
|
||||
}
|
||||
|
||||
func TestAdminRepository_List(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
admin := &models.AdminUser{
|
||||
Email: "admin" + string(rune('0'+i)) + "@test.com",
|
||||
Role: models.AdminRoleAdmin,
|
||||
}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
}
|
||||
|
||||
// Page 1
|
||||
admins, total, err := repo.List(1, 3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(5), total)
|
||||
assert.Len(t, admins, 3)
|
||||
|
||||
// Page 2
|
||||
admins, total, err = repo.List(2, 3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(5), total)
|
||||
assert.Len(t, admins, 2)
|
||||
}
|
||||
|
||||
func TestAdminRepository_ExistsByEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "admin@test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
exists, err := repo.ExistsByEmail("admin@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
exists, err = repo.ExistsByEmail("nonexistent@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestAdminRepository_ExistsByEmail_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewAdminRepository(db)
|
||||
|
||||
admin := &models.AdminUser{Email: "Admin@Test.com", Role: models.AdminRoleAdmin}
|
||||
require.NoError(t, admin.SetPassword("Password123"))
|
||||
require.NoError(t, repo.Create(admin))
|
||||
|
||||
exists, err := repo.ExistsByEmail("admin@test.com")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
}
|
||||
356
internal/repositories/contractor_repo_coverage_test.go
Normal file
356
internal/repositories/contractor_repo_coverage_test.go
Normal file
@@ -0,0 +1,356 @@
|
||||
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)
|
||||
}
|
||||
384
internal/repositories/document_repo_coverage_test.go
Normal file
384
internal/repositories/document_repo_coverage_test.go
Normal file
@@ -0,0 +1,384 @@
|
||||
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 TestDocumentRepository_Create(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
doc := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "HVAC Warranty",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/hvac.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
err := repo.Create(doc)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, doc.ID)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
|
||||
|
||||
found, err := repo.FindByID(doc.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, doc.ID, found.ID)
|
||||
assert.Equal(t, "Test Doc", found.Title)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
_, err := repo.FindByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByID_InactiveNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Inactive Doc")
|
||||
|
||||
db.Model(&models.Document{}).Where("id = ?", doc.ID).Update("is_active", false)
|
||||
|
||||
_, err := repo.FindByID(doc.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(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.CreateTestDocument(t, db, residence.ID, user.ID, "Doc A")
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc B")
|
||||
testutil.CreateTestDocument(t, db, otherResidence.ID, user.ID, "Other Doc")
|
||||
|
||||
docs, err := repo.FindByResidence(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 2)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByResidence_ExcludesInactive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
active := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Active Doc")
|
||||
inactive := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Inactive Doc")
|
||||
db.Model(&models.Document{}).Where("id = ?", inactive.ID).Update("is_active", false)
|
||||
|
||||
docs, err := repo.FindByResidence(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, active.ID, docs[0].ID)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(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.CreateTestDocument(t, db, r1.ID, user.ID, "Doc 1")
|
||||
testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc 2")
|
||||
|
||||
docs, err := repo.FindByUser([]uint{r1.ID, r2.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 2)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_Update(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Original Title")
|
||||
|
||||
doc.Title = "Updated Title"
|
||||
doc.Description = "Updated description"
|
||||
err := repo.Update(doc)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(doc.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Title", found.Title)
|
||||
assert.Equal(t, "Updated description", found.Description)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_Delete(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "To Delete")
|
||||
|
||||
err := repo.Delete(doc.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindByID(doc.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_ActivateDeactivate(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Toggle Doc")
|
||||
|
||||
// Deactivate
|
||||
err := repo.Deactivate(doc.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindByID(doc.ID)
|
||||
assert.Error(t, err) // Not found when inactive
|
||||
|
||||
// Activate
|
||||
err = repo.Activate(doc.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(doc.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, doc.ID, found.ID)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_CountByResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 1")
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 2")
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 3")
|
||||
|
||||
count, err := repo.CountByResidence(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_CountByResidenceIDs(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(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.CreateTestDocument(t, db, r1.ID, user.ID, "Doc A")
|
||||
testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc B")
|
||||
testutil.CreateTestDocument(t, db, r2.ID, user.ID, "Doc C")
|
||||
|
||||
count, err := repo.CountByResidenceIDs([]uint{r1.ID, r2.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_CountByResidenceIDs_Empty(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
count, err := repo.CountByResidenceIDs([]uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_DocumentImages(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc with Images")
|
||||
|
||||
// Create image
|
||||
img := &models.DocumentImage{
|
||||
DocumentID: doc.ID,
|
||||
ImageURL: "https://example.com/img1.jpg",
|
||||
Caption: "Front page",
|
||||
}
|
||||
err := repo.CreateDocumentImage(img)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, img.ID)
|
||||
|
||||
// Find image by ID
|
||||
found, err := repo.FindImageByID(img.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://example.com/img1.jpg", found.ImageURL)
|
||||
assert.Equal(t, "Front page", found.Caption)
|
||||
|
||||
// Delete single image
|
||||
err = repo.DeleteDocumentImage(img.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindImageByID(img.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_DeleteDocumentImages(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc with Images")
|
||||
|
||||
// Create multiple images
|
||||
for i := 0; i < 3; i++ {
|
||||
img := &models.DocumentImage{
|
||||
DocumentID: doc.ID,
|
||||
ImageURL: "https://example.com/img.jpg",
|
||||
}
|
||||
err := repo.CreateDocumentImage(img)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Delete all images for document
|
||||
err := repo.DeleteDocumentImages(doc.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify all deleted
|
||||
var count int64
|
||||
db.Model(&models.DocumentImage{}).Where("document_id = ?", doc.ID).Count(&count)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByIDIncludingInactive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Deactivated Doc")
|
||||
|
||||
// Deactivate
|
||||
db.Model(&models.Document{}).Where("id = ?", doc.ID).Update("is_active", false)
|
||||
|
||||
// FindByID should not find it
|
||||
_, err := repo.FindByID(doc.ID)
|
||||
assert.Error(t, err)
|
||||
|
||||
// FindByIDIncludingInactive should find it
|
||||
var found models.Document
|
||||
err = repo.FindByIDIncludingInactive(doc.ID, &found)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, doc.ID, found.ID)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUserFiltered_ByType(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
// Create general document
|
||||
generalDoc := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "General Doc",
|
||||
DocumentType: models.DocumentTypeGeneral,
|
||||
FileURL: "https://example.com/gen.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
err := db.Create(generalDoc).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create warranty document
|
||||
warrantyDoc := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Warranty Doc",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/warranty.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
err = db.Create(warrantyDoc).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Filter by warranty type
|
||||
filter := &DocumentFilter{
|
||||
DocumentType: string(models.DocumentTypeWarranty),
|
||||
}
|
||||
docs, err := repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "Warranty Doc", docs[0].Title)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUserFiltered_NilFilter(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 1")
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 2")
|
||||
|
||||
docs, err := repo.FindByUserFiltered([]uint{residence.ID}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 2)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindWarranties(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
// General doc (should not be returned)
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "General Doc")
|
||||
|
||||
// Warranty doc
|
||||
warranty := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Warranty",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/warranty.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
err := db.Create(warranty).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
docs, err := repo.FindWarranties([]uint{residence.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "Warranty", docs[0].Title)
|
||||
}
|
||||
207
internal/repositories/document_repo_extended_test.go
Normal file
207
internal/repositories/document_repo_extended_test.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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 TestDocumentRepository_FindExpiringWarranties(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
now := time.Now().UTC()
|
||||
expiringSoon := now.AddDate(0, 0, 15) // 15 days from now
|
||||
expiringLater := now.AddDate(0, 0, 60) // 60 days from now
|
||||
alreadyExpired := now.AddDate(0, 0, -5) // 5 days ago
|
||||
|
||||
// Warranty expiring in 15 days (within 30 day threshold)
|
||||
w1 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Expiring Warranty",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/w1.pdf",
|
||||
ExpiryDate: &expiringSoon,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(w1).Error)
|
||||
|
||||
// Warranty expiring in 60 days (outside 30 day threshold)
|
||||
w2 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Later Warranty",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/w2.pdf",
|
||||
ExpiryDate: &expiringLater,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(w2).Error)
|
||||
|
||||
// Already expired warranty
|
||||
w3 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Expired Warranty",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/w3.pdf",
|
||||
ExpiryDate: &alreadyExpired,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(w3).Error)
|
||||
|
||||
// General document (not warranty)
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "General Doc")
|
||||
|
||||
docs, err := repo.FindExpiringWarranties([]uint{residence.ID}, 30)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "Expiring Warranty", docs[0].Title)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUserFiltered_Search(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite does not support ILIKE")
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
d1 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "HVAC Warranty Certificate",
|
||||
Description: "For the main HVAC unit",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/hvac.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(d1).Error)
|
||||
|
||||
d2 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Plumbing Receipt",
|
||||
Description: "Bathroom plumbing fix",
|
||||
DocumentType: models.DocumentTypeReceipt,
|
||||
FileURL: "https://example.com/plumbing.pdf",
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(d2).Error)
|
||||
|
||||
// Search by title
|
||||
filter := &DocumentFilter{Search: "HVAC"}
|
||||
docs, err := repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "HVAC Warranty Certificate", docs[0].Title)
|
||||
|
||||
// Search by description
|
||||
filter = &DocumentFilter{Search: "bathroom"}
|
||||
docs, err = repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "Plumbing Receipt", docs[0].Title)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUserFiltered_IsActiveOverride(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
active := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Active Doc")
|
||||
inactive := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Inactive Doc")
|
||||
db.Model(&models.Document{}).Where("id = ?", inactive.ID).Update("is_active", false)
|
||||
|
||||
// With IsActive = false, should get only inactive
|
||||
isActiveFalse := false
|
||||
filter := &DocumentFilter{IsActive: &isActiveFalse}
|
||||
docs, err := repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, inactive.ID, docs[0].ID)
|
||||
|
||||
// With IsActive = true, should get only active
|
||||
isActiveTrue := true
|
||||
filter = &DocumentFilter{IsActive: &isActiveTrue}
|
||||
docs, err = repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, active.ID, docs[0].ID)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUserFiltered_ExpiringSoon(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
now := time.Now().UTC()
|
||||
expiringSoon := now.AddDate(0, 0, 10)
|
||||
notExpiring := now.AddDate(0, 0, 90)
|
||||
|
||||
d1 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Expiring Doc",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/exp.pdf",
|
||||
ExpiryDate: &expiringSoon,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(d1).Error)
|
||||
|
||||
d2 := &models.Document{
|
||||
ResidenceID: residence.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Not Expiring Doc",
|
||||
DocumentType: models.DocumentTypeWarranty,
|
||||
FileURL: "https://example.com/noexp.pdf",
|
||||
ExpiryDate: ¬Expiring,
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(d2).Error)
|
||||
|
||||
days := 30
|
||||
filter := &DocumentFilter{ExpiringSoon: &days}
|
||||
docs, err := repo.FindByUserFiltered([]uint{residence.ID}, filter)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
assert.Equal(t, "Expiring Doc", docs[0].Title)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindImageByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
_, err := repo.FindImageByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDocumentRepository_FindByUser_ExcludesInactive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewDocumentRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Active Doc")
|
||||
inactive := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Inactive Doc")
|
||||
db.Model(&models.Document{}).Where("id = ?", inactive.ID).Update("is_active", false)
|
||||
|
||||
docs, err := repo.FindByUser([]uint{residence.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, docs, 1)
|
||||
}
|
||||
510
internal/repositories/notification_repo_coverage_test.go
Normal file
510
internal/repositories/notification_repo_coverage_test.go
Normal file
@@ -0,0 +1,510 @@
|
||||
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 TestNotificationRepository_Create(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
notification := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Task Due Soon",
|
||||
Body: "Your task is due tomorrow",
|
||||
}
|
||||
|
||||
err := repo.Create(notification)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, notification.ID)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_FindByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
notification := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskOverdue,
|
||||
Title: "Task Overdue",
|
||||
Body: "Your task is overdue",
|
||||
}
|
||||
err := db.Create(notification).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(notification.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Task Overdue", found.Title)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_FindByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
_, err := repo.FindByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_FindByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
otherUser := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
|
||||
// Create notifications for user
|
||||
for i := 0; i < 5; i++ {
|
||||
n := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Notification",
|
||||
Body: "Body",
|
||||
}
|
||||
err := db.Create(n).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create notification for other user
|
||||
otherN := &models.Notification{
|
||||
UserID: otherUser.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Other",
|
||||
Body: "Body",
|
||||
}
|
||||
err := db.Create(otherN).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Find all for user
|
||||
notifications, err := repo.FindByUser(user.ID, 0, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, notifications, 5)
|
||||
|
||||
// Find with limit
|
||||
notifications, err = repo.FindByUser(user.ID, 3, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, notifications, 3)
|
||||
|
||||
// Find with offset
|
||||
notifications, err = repo.FindByUser(user.ID, 3, 3)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, notifications, 2) // Only 2 remaining
|
||||
}
|
||||
|
||||
func TestNotificationRepository_MarkAsRead(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
notification := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskCompleted,
|
||||
Title: "Task Completed",
|
||||
Body: "Your task was completed",
|
||||
Read: false,
|
||||
}
|
||||
err := db.Create(notification).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.MarkAsRead(notification.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(notification.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, found.Read)
|
||||
assert.NotNil(t, found.ReadAt)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_MarkAllAsRead(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create multiple unread notifications
|
||||
for i := 0; i < 3; i++ {
|
||||
n := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Unread",
|
||||
Body: "Body",
|
||||
Read: false,
|
||||
}
|
||||
err := db.Create(n).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err := repo.MarkAllAsRead(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify all are read
|
||||
count, err := repo.CountUnread(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_CountUnread(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create 3 unread and 2 read notifications
|
||||
for i := 0; i < 3; i++ {
|
||||
n := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Unread",
|
||||
Body: "Body",
|
||||
Read: false,
|
||||
}
|
||||
err := db.Create(n).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
n := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskCompleted,
|
||||
Title: "Read",
|
||||
Body: "Body",
|
||||
Read: true,
|
||||
}
|
||||
err := db.Create(n).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := repo.CountUnread(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_MarkAsSent(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
notification := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "To Send",
|
||||
Body: "Body",
|
||||
Sent: false,
|
||||
}
|
||||
err := db.Create(notification).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.MarkAsSent(notification.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(notification.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, found.Sent)
|
||||
assert.NotNil(t, found.SentAt)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_SetError(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
notification := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Error Notification",
|
||||
Body: "Body",
|
||||
}
|
||||
err := db.Create(notification).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.SetError(notification.ID, "failed to send: connection timeout")
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(notification.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "failed to send: connection timeout", found.ErrorMessage)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_GetPendingNotifications(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create pending (unsent) notifications
|
||||
for i := 0; i < 5; i++ {
|
||||
n := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Pending",
|
||||
Body: "Body",
|
||||
Sent: false,
|
||||
}
|
||||
err := db.Create(n).Error
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create sent notification
|
||||
sent := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskCompleted,
|
||||
Title: "Already Sent",
|
||||
Body: "Body",
|
||||
Sent: true,
|
||||
}
|
||||
err := db.Create(sent).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
pending, err := repo.GetPendingNotifications(10)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pending, 5) // Only unsent
|
||||
|
||||
// Test with limit
|
||||
pending, err = repo.GetPendingNotifications(3)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, pending, 3)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_APNSDevice_CRUD(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create APNS device
|
||||
device := &models.APNSDevice{
|
||||
Name: "iPhone 15",
|
||||
Active: true,
|
||||
UserID: &user.ID,
|
||||
DeviceID: "device-uuid-123",
|
||||
RegistrationID: "apns-token-abc123",
|
||||
}
|
||||
err := repo.CreateAPNSDevice(device)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, device.ID)
|
||||
|
||||
// Find by ID
|
||||
found, err := repo.FindAPNSDeviceByID(device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "iPhone 15", found.Name)
|
||||
assert.Equal(t, "apns-token-abc123", found.RegistrationID)
|
||||
|
||||
// Find by token
|
||||
foundByToken, err := repo.FindAPNSDeviceByToken("apns-token-abc123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, device.ID, foundByToken.ID)
|
||||
|
||||
// Find by user
|
||||
devices, err := repo.FindAPNSDevicesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, devices, 1)
|
||||
|
||||
// Update
|
||||
device.Name = "iPhone 16"
|
||||
err = repo.UpdateAPNSDevice(device)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err = repo.FindAPNSDeviceByID(device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "iPhone 16", found.Name)
|
||||
|
||||
// Deactivate
|
||||
err = repo.DeactivateAPNSDevice(device.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should not appear in active devices
|
||||
devices, err = repo.FindAPNSDevicesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, devices, 0)
|
||||
|
||||
// Delete
|
||||
err = repo.DeleteAPNSDevice(device.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindAPNSDeviceByID(device.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_GCMDevice_CRUD(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create GCM device
|
||||
device := &models.GCMDevice{
|
||||
Name: "Pixel 8",
|
||||
Active: true,
|
||||
UserID: &user.ID,
|
||||
DeviceID: "android-device-uuid",
|
||||
RegistrationID: "fcm-token-xyz789",
|
||||
CloudMessageType: "FCM",
|
||||
}
|
||||
err := repo.CreateGCMDevice(device)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, device.ID)
|
||||
|
||||
// Find by ID
|
||||
found, err := repo.FindGCMDeviceByID(device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Pixel 8", found.Name)
|
||||
assert.Equal(t, "fcm-token-xyz789", found.RegistrationID)
|
||||
|
||||
// Find by token
|
||||
foundByToken, err := repo.FindGCMDeviceByToken("fcm-token-xyz789")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, device.ID, foundByToken.ID)
|
||||
|
||||
// Find by user
|
||||
devices, err := repo.FindGCMDevicesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, devices, 1)
|
||||
|
||||
// Update
|
||||
device.Name = "Pixel 9"
|
||||
err = repo.UpdateGCMDevice(device)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err = repo.FindGCMDeviceByID(device.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Pixel 9", found.Name)
|
||||
|
||||
// Deactivate
|
||||
err = repo.DeactivateGCMDevice(device.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
devices, err = repo.FindGCMDevicesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, devices, 0)
|
||||
|
||||
// Delete
|
||||
err = repo.DeleteGCMDevice(device.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindGCMDeviceByID(device.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_GetActiveTokensForUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create APNS devices
|
||||
apns1 := &models.APNSDevice{
|
||||
Active: true,
|
||||
UserID: &user.ID,
|
||||
RegistrationID: "ios-token-1",
|
||||
}
|
||||
err := db.Create(apns1).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
apns2 := &models.APNSDevice{
|
||||
Active: true,
|
||||
UserID: &user.ID,
|
||||
RegistrationID: "ios-token-2",
|
||||
}
|
||||
err = db.Create(apns2).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create inactive APNS device (should not be returned)
|
||||
// Insert inactive device via raw SQL to bypass GORM's default:true override
|
||||
err = db.Exec("INSERT INTO push_notifications_apnsdevice (active, user_id, registration_id, date_created) VALUES (false, ?, 'ios-token-inactive', CURRENT_TIMESTAMP)", user.ID).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create GCM device
|
||||
gcm1 := &models.GCMDevice{
|
||||
Active: true,
|
||||
UserID: &user.ID,
|
||||
RegistrationID: "android-token-1",
|
||||
CloudMessageType: "FCM",
|
||||
}
|
||||
err = db.Create(gcm1).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
iosTokens, androidTokens, err := repo.GetActiveTokensForUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, iosTokens, 2)
|
||||
assert.Contains(t, iosTokens, "ios-token-1")
|
||||
assert.Contains(t, iosTokens, "ios-token-2")
|
||||
assert.Len(t, androidTokens, 1)
|
||||
assert.Contains(t, androidTokens, "android-token-1")
|
||||
}
|
||||
|
||||
func TestNotificationRepository_GetActiveTokensForUser_NoDevices(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
iosTokens, androidTokens, err := repo.GetActiveTokensForUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, iosTokens)
|
||||
assert.Empty(t, androidTokens)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_FindPreferencesByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
// Create preferences with defaults (GORM applies default:true for bools)
|
||||
prefs := &models.NotificationPreference{
|
||||
UserID: user.ID,
|
||||
}
|
||||
err := db.Create(prefs).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update one field to false via raw SQL to bypass GORM default
|
||||
err = db.Exec("UPDATE notifications_notificationpreference SET task_due_soon = false WHERE user_id = ?", user.ID).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindPreferencesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
assert.False(t, found.TaskDueSoon)
|
||||
assert.True(t, found.TaskOverdue)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_FindPreferencesByUser_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
_, err := repo.FindPreferencesByUser(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNotificationRepository_UpdatePreferences(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewNotificationRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
|
||||
prefs, err := repo.GetOrCreatePreferences(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
prefs.TaskDueSoon = false
|
||||
prefs.DailyDigest = false
|
||||
err = repo.UpdatePreferences(prefs)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindPreferencesByUser(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, found.TaskDueSoon)
|
||||
assert.False(t, found.DailyDigest)
|
||||
}
|
||||
217
internal/repositories/reminder_repo_test.go
Normal file
217
internal/repositories/reminder_repo_test.go
Normal file
@@ -0,0 +1,217 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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 TestReminderRepository_LogReminder(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
logEntry, err := repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, logEntry.ID)
|
||||
assert.Equal(t, task.ID, logEntry.TaskID)
|
||||
assert.Equal(t, user.ID, logEntry.UserID)
|
||||
assert.Equal(t, models.ReminderStage7Days, logEntry.ReminderStage)
|
||||
}
|
||||
|
||||
func TestReminderRepository_HasSentReminder(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 12, 30, 0, 0, time.UTC) // Has time component
|
||||
|
||||
// Not sent yet
|
||||
sent, err := repo.HasSentReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, sent)
|
||||
|
||||
// Log it
|
||||
_, err = repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now should be sent
|
||||
sent, err = repo.HasSentReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, sent)
|
||||
|
||||
// Different stage should not be sent
|
||||
sent, err = repo.HasSentReminder(task.ID, user.ID, dueDate, models.ReminderStage3Days)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, sent)
|
||||
}
|
||||
|
||||
func TestReminderRepository_HasSentReminderBatch(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task1 := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")
|
||||
task2 := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2")
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Log reminder for task1
|
||||
_, err := repo.LogReminder(task1.ID, user.ID, dueDate, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
keys := []ReminderKey{
|
||||
{TaskID: task1.ID, UserID: user.ID, DueDate: dueDate, Stage: models.ReminderStage7Days},
|
||||
{TaskID: task2.ID, UserID: user.ID, DueDate: dueDate, Stage: models.ReminderStage7Days},
|
||||
}
|
||||
|
||||
result, err := repo.HasSentReminderBatch(keys)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, result[0], "task1 reminder should be sent")
|
||||
assert.False(t, result[1], "task2 reminder should not be sent")
|
||||
}
|
||||
|
||||
func TestReminderRepository_HasSentReminderBatch_Empty(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
result, err := repo.HasSentReminderBatch([]ReminderKey{})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestReminderRepository_GetSentRemindersForTask(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
_, err := repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage3Days, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
logs, err := repo.GetSentRemindersForTask(task.ID, user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, logs, 2)
|
||||
}
|
||||
|
||||
func TestReminderRepository_GetSentRemindersForDueDate(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
dueDate1 := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
dueDate2 := time.Date(2026, 5, 15, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
_, err := repo.LogReminder(task.ID, user.ID, dueDate1, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.LogReminder(task.ID, user.ID, dueDate2, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
logs, err := repo.GetSentRemindersForDueDate(task.ID, user.ID, dueDate1)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, logs, 1)
|
||||
}
|
||||
|
||||
func TestReminderRepository_CleanupOldLogs(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
// Create old log entry (100 days ago)
|
||||
oldLog := &models.TaskReminderLog{
|
||||
TaskID: task.ID,
|
||||
UserID: user.ID,
|
||||
DueDate: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
ReminderStage: models.ReminderStage7Days,
|
||||
SentAt: time.Now().UTC().AddDate(0, 0, -100),
|
||||
}
|
||||
require.NoError(t, db.Create(oldLog).Error)
|
||||
|
||||
// Create recent log entry
|
||||
recentLog := &models.TaskReminderLog{
|
||||
TaskID: task.ID,
|
||||
UserID: user.ID,
|
||||
DueDate: time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC),
|
||||
ReminderStage: models.ReminderStage3Days,
|
||||
SentAt: time.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, db.Create(recentLog).Error)
|
||||
|
||||
deleted, err := repo.CleanupOldLogs(90)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), deleted)
|
||||
|
||||
// Recent log should still exist
|
||||
var count int64
|
||||
db.Model(&models.TaskReminderLog{}).Count(&count)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
func TestReminderRepository_GetRecentReminderStats(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
// Create recent reminders
|
||||
_, err := repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days, nil)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage3Days, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
stats, err := repo.GetRecentReminderStats(24) // last 24 hours
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), stats[string(models.ReminderStage7Days)])
|
||||
assert.Equal(t, int64(1), stats[string(models.ReminderStage3Days)])
|
||||
}
|
||||
|
||||
func TestReminderRepository_LogReminder_WithNotificationID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewReminderRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Fix Roof")
|
||||
|
||||
// Create a notification
|
||||
notif := &models.Notification{
|
||||
UserID: user.ID,
|
||||
NotificationType: models.NotificationTaskDueSoon,
|
||||
Title: "Test",
|
||||
Body: "Test body",
|
||||
}
|
||||
require.NoError(t, db.Create(notif).Error)
|
||||
|
||||
dueDate := time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC)
|
||||
logEntry, err := repo.LogReminder(task.ID, user.ID, dueDate, models.ReminderStage7Days, ¬if.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, logEntry.NotificationID)
|
||||
assert.Equal(t, notif.ID, *logEntry.NotificationID)
|
||||
}
|
||||
216
internal/repositories/residence_repo_coverage_test.go
Normal file
216
internal/repositories/residence_repo_coverage_test.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/treytartt/honeydue-api/internal/testutil"
|
||||
)
|
||||
|
||||
func TestResidenceRepository_FindByIDSimple(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
found, err := repo.FindByIDSimple(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, residence.ID, found.ID)
|
||||
assert.Equal(t, "Test House", found.Name)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindByIDSimple_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
_, err := repo.FindByIDSimple(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindByIDSimple_InactiveNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
db.Model(residence).Update("is_active", false)
|
||||
|
||||
_, err := repo.FindByIDSimple(residence.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindResidenceIDsByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
|
||||
|
||||
r1 := testutil.CreateTestResidence(t, db, owner.ID, "House 1")
|
||||
r2 := testutil.CreateTestResidence(t, db, owner.ID, "House 2")
|
||||
|
||||
repo.AddUser(r1.ID, sharedUser.ID)
|
||||
|
||||
// Owner should see both
|
||||
ownerIDs, err := repo.FindResidenceIDsByUser(owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, ownerIDs, 2)
|
||||
|
||||
// Shared user should see one
|
||||
sharedIDs, err := repo.FindResidenceIDsByUser(sharedUser.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, sharedIDs, 1)
|
||||
assert.Equal(t, r1.ID, sharedIDs[0])
|
||||
|
||||
_ = r2 // suppress unused
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindResidenceIDsByOwner(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
otherOwner := testutil.CreateTestUser(t, db, "other", "other@test.com", "Password123")
|
||||
|
||||
testutil.CreateTestResidence(t, db, owner.ID, "House 1")
|
||||
testutil.CreateTestResidence(t, db, owner.ID, "House 2")
|
||||
testutil.CreateTestResidence(t, db, otherOwner.ID, "Other House")
|
||||
|
||||
ids, err := repo.FindResidenceIDsByOwner(owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, ids, 2)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_DeactivateShareCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// Create a share code
|
||||
created, err := repo.CreateShareCode(residence.ID, owner.ID, 24*60*60*1000*1000*1000) // 24h in ns
|
||||
require.NoError(t, err)
|
||||
assert.True(t, created.IsActive)
|
||||
|
||||
// Deactivate it
|
||||
err = repo.DeactivateShareCode(created.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// FindShareCodeByCode should not find it
|
||||
_, err = repo.FindShareCodeByCode(created.Code)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_GetActiveShareCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// No active code initially
|
||||
code, err := repo.GetActiveShareCode(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, code)
|
||||
|
||||
// Create a share code
|
||||
created, err := repo.CreateShareCode(residence.ID, owner.ID, 24*60*60*1000*1000*1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should find the active code
|
||||
code, err = repo.GetActiveShareCode(residence.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, code)
|
||||
assert.Equal(t, created.Code, code.Code)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_CreateShareCode_DeactivatesPrevious(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// Create first code
|
||||
first, err := repo.CreateShareCode(residence.ID, owner.ID, 24*60*60*1000*1000*1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create second code (should deactivate first)
|
||||
second, err := repo.CreateShareCode(residence.ID, owner.ID, 24*60*60*1000*1000*1000)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, first.Code, second.Code)
|
||||
|
||||
// First code should no longer be findable
|
||||
_, err = repo.FindShareCodeByCode(first.Code)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Second code should be active
|
||||
found, err := repo.FindShareCodeByCode(second.Code)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, second.Code, found.Code)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindResidenceTypeByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
// Get first residence type
|
||||
types, err := repo.GetAllResidenceTypes()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, types)
|
||||
|
||||
found, err := repo.FindResidenceTypeByID(types[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, types[0].Name, found.Name)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_FindResidenceTypeByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
_, err := repo.FindResidenceTypeByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_GetTasksForReport(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
|
||||
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 1")
|
||||
testutil.CreateTestTask(t, db, residence.ID, user.ID, "Task 2")
|
||||
|
||||
tasks, err := repo.GetTasksForReport(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tasks, 2)
|
||||
}
|
||||
|
||||
func TestResidenceRepository_AddUser_Idempotent(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
sharedUser := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
|
||||
|
||||
// Add same user twice (should not error due to ON CONFLICT DO NOTHING)
|
||||
err := repo.AddUser(residence.ID, sharedUser.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.AddUser(residence.ID, sharedUser.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should still only count once
|
||||
users, err := repo.GetResidenceUsers(residence.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 2) // owner + sharedUser
|
||||
}
|
||||
418
internal/repositories/subscription_repo_coverage_test.go
Normal file
418
internal/repositories/subscription_repo_coverage_test.go
Normal file
@@ -0,0 +1,418 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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 TestSubscriptionRepository_FindByUserID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
sub := &models.UserSubscription{
|
||||
UserID: user.ID,
|
||||
Tier: models.TierPro,
|
||||
}
|
||||
require.NoError(t, db.Create(sub).Error)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.TierPro, found.Tier)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByUserID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.FindByUserID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_Update(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
sub, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
sub.Tier = models.TierPro
|
||||
err = repo.Update(sub)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.TierPro, found.Tier)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_UpgradeToPro(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expiresAt := time.Now().UTC().AddDate(1, 0, 0)
|
||||
err = repo.UpgradeToPro(user.ID, expiresAt, "apple")
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.TierPro, found.Tier)
|
||||
assert.Equal(t, "apple", found.Platform)
|
||||
assert.True(t, found.AutoRenew)
|
||||
require.NotNil(t, found.SubscribedAt)
|
||||
require.NotNil(t, found.ExpiresAt)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_DowngradeToFree(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First upgrade
|
||||
err = repo.UpgradeToPro(user.ID, time.Now().UTC().AddDate(1, 0, 0), "apple")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Then downgrade
|
||||
err = repo.DowngradeToFree(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, models.TierFree, found.Tier)
|
||||
assert.False(t, found.AutoRenew)
|
||||
require.NotNil(t, found.CancelledAt)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_SetAutoRenew(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.SetAutoRenew(user.ID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, found.AutoRenew)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_UpdateReceiptData(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.UpdateReceiptData(user.ID, "receipt_data_abc123")
|
||||
require.NoError(t, err)
|
||||
|
||||
var sub models.UserSubscription
|
||||
db.Where("user_id = ?", user.ID).First(&sub)
|
||||
require.NotNil(t, sub.AppleReceiptData)
|
||||
assert.Equal(t, "receipt_data_abc123", *sub.AppleReceiptData)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_UpdatePurchaseToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.UpdatePurchaseToken(user.ID, "google_token_xyz")
|
||||
require.NoError(t, err)
|
||||
|
||||
var sub models.UserSubscription
|
||||
db.Where("user_id = ?", user.ID).First(&sub)
|
||||
require.NotNil(t, sub.GooglePurchaseToken)
|
||||
assert.Equal(t, "google_token_xyz", *sub.GooglePurchaseToken)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByAppleReceiptContains(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
// Use raw SQL to ensure apple_receipt_data is stored correctly (GORM may omit pointer fields)
|
||||
require.NoError(t, db.Exec(
|
||||
"INSERT INTO subscription_usersubscription (user_id, tier, apple_receipt_data, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))",
|
||||
user.ID, models.TierPro, "transactionid=txnabc123data",
|
||||
).Error)
|
||||
|
||||
// Avoid underscores in search term: escapeLikeWildcards escapes _ with \
|
||||
// which PostgreSQL handles but SQLite does not (no default ESCAPE clause)
|
||||
found, err := repo.FindByAppleReceiptContains("txnabc123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByAppleReceiptContains_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.FindByAppleReceiptContains("nonexistent_txn")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByGoogleToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
purchaseToken := "google_purchase_abc"
|
||||
sub := &models.UserSubscription{
|
||||
UserID: user.ID,
|
||||
Tier: models.TierPro,
|
||||
GooglePurchaseToken: &purchaseToken,
|
||||
}
|
||||
require.NoError(t, db.Create(sub).Error)
|
||||
|
||||
found, err := repo.FindByGoogleToken("google_purchase_abc")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByGoogleToken_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.FindByGoogleToken("nonexistent_token")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_SetCancelledAt(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
cancelTime := time.Now().UTC()
|
||||
err = repo.SetCancelledAt(user.ID, cancelTime)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found.CancelledAt)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_ClearCancelledAt(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
_, err := repo.GetOrCreate(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set then clear
|
||||
err = repo.SetCancelledAt(user.ID, time.Now().UTC())
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.ClearCancelledAt(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, found.CancelledAt)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetTierLimits_Defaults(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
// No tier limits in DB, should return defaults
|
||||
freeLimits, err := repo.GetTierLimits(models.TierFree)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, freeLimits)
|
||||
|
||||
proLimits, err := repo.GetTierLimits(models.TierPro)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, proLimits)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetAllTierLimits(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
// No limits seeded = empty
|
||||
limits, err := repo.GetAllTierLimits()
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, limits)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetSettings_Defaults(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
// No settings in DB, should return default (limitations disabled)
|
||||
settings, err := repo.GetSettings()
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, settings)
|
||||
assert.False(t, settings.EnableLimitations)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetUpgradeTrigger(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
trigger := &models.UpgradeTrigger{
|
||||
TriggerKey: "max_residences",
|
||||
Title: "Upgrade for more homes",
|
||||
Message: "Free tier supports 1 home",
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(trigger).Error)
|
||||
|
||||
found, err := repo.GetUpgradeTrigger("max_residences")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "max_residences", found.TriggerKey)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetUpgradeTrigger_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.GetUpgradeTrigger("nonexistent_key")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetAllUpgradeTriggers(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
trigger1 := &models.UpgradeTrigger{TriggerKey: "key1", Title: "T1", Message: "M1", IsActive: true}
|
||||
trigger2 := &models.UpgradeTrigger{TriggerKey: "key2", Title: "T2", Message: "M2", IsActive: true}
|
||||
require.NoError(t, db.Create(trigger1).Error)
|
||||
require.NoError(t, db.Create(trigger2).Error)
|
||||
// Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false
|
||||
require.NoError(t, db.Exec(
|
||||
"INSERT INTO subscription_upgradetrigger (trigger_key, title, message, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, datetime('now'), datetime('now'))",
|
||||
"key3", "T3", "M3", false,
|
||||
).Error)
|
||||
|
||||
triggers, err := repo.GetAllUpgradeTriggers()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, triggers, 2) // Only active
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetFeatureBenefits(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
b1 := &models.FeatureBenefit{FeatureName: "Benefit 1", FreeTierText: "1 home", ProTierText: "Unlimited", DisplayOrder: 1, IsActive: true}
|
||||
require.NoError(t, db.Create(b1).Error)
|
||||
// Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false
|
||||
require.NoError(t, db.Exec(
|
||||
"INSERT INTO subscription_featurebenefit (feature_name, free_tier_text, pro_tier_text, display_order, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
|
||||
"Benefit 2", "3 tasks", "Unlimited", 2, false,
|
||||
).Error)
|
||||
|
||||
benefits, err := repo.GetFeatureBenefits()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, benefits, 1) // Only active
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetActivePromotions(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
now := time.Now().UTC()
|
||||
active := &models.Promotion{
|
||||
PromotionID: "promo_active",
|
||||
TargetTier: models.TierPro,
|
||||
Title: "Active Promo",
|
||||
Message: "Get Pro now!",
|
||||
StartDate: now.AddDate(0, 0, -1),
|
||||
EndDate: now.AddDate(0, 0, 1),
|
||||
IsActive: true,
|
||||
}
|
||||
expired := &models.Promotion{
|
||||
PromotionID: "promo_expired",
|
||||
TargetTier: models.TierPro,
|
||||
Title: "Expired Promo",
|
||||
Message: "Too late!",
|
||||
StartDate: now.AddDate(0, 0, -10),
|
||||
EndDate: now.AddDate(0, 0, -5),
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(active).Error)
|
||||
require.NoError(t, db.Create(expired).Error)
|
||||
|
||||
promos, err := repo.GetActivePromotions(models.TierPro)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, promos, 1)
|
||||
assert.Equal(t, "promo_active", promos[0].PromotionID)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetPromotionByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
promo := &models.Promotion{
|
||||
PromotionID: "promo_find",
|
||||
TargetTier: models.TierPro,
|
||||
Title: "Find Me",
|
||||
Message: "Found!",
|
||||
StartDate: time.Now().UTC(),
|
||||
EndDate: time.Now().UTC().AddDate(0, 0, 30),
|
||||
IsActive: true,
|
||||
}
|
||||
require.NoError(t, db.Create(promo).Error)
|
||||
|
||||
found, err := repo.GetPromotionByID("promo_find")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Find Me", found.Title)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_GetPromotionByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.GetPromotionByID("nonexistent")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByStripeSubscriptionID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
subID := "sub_test_123"
|
||||
sub := &models.UserSubscription{
|
||||
UserID: user.ID,
|
||||
Tier: models.TierPro,
|
||||
StripeSubscriptionID: &subID,
|
||||
}
|
||||
require.NoError(t, db.Create(sub).Error)
|
||||
|
||||
found, err := repo.FindByStripeSubscriptionID("sub_test_123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestSubscriptionRepository_FindByStripeSubscriptionID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewSubscriptionRepository(db)
|
||||
|
||||
_, err := repo.FindByStripeSubscriptionID("sub_nonexistent")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
516
internal/repositories/task_repo_coverage_test.go
Normal file
516
internal/repositories/task_repo_coverage_test.go
Normal file
@@ -0,0 +1,516 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/treytartt/honeydue-api/internal/models"
|
||||
"github.com/treytartt/honeydue-api/internal/testutil"
|
||||
)
|
||||
|
||||
// === UpdateTx / Version Conflict Tests ===
|
||||
|
||||
func TestTaskRepository_UpdateTx_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")
|
||||
|
||||
// Simulate stale version by setting wrong version
|
||||
task.Version = 999
|
||||
task.Title = "Should Fail"
|
||||
|
||||
tx := db.Begin()
|
||||
err := repo.UpdateTx(tx, task)
|
||||
tx.Rollback()
|
||||
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
func TestTaskRepository_UpdateTx_Success(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")
|
||||
|
||||
originalVersion := task.Version
|
||||
task.Title = "Updated via Tx"
|
||||
|
||||
tx := db.Begin()
|
||||
err := repo.UpdateTx(tx, task)
|
||||
require.NoError(t, err)
|
||||
tx.Commit()
|
||||
|
||||
assert.Equal(t, originalVersion+1, task.Version)
|
||||
|
||||
found, err := repo.FindByID(task.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated via Tx", found.Title)
|
||||
}
|
||||
|
||||
func TestTaskRepository_Update_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Versioned Task")
|
||||
|
||||
task.Version = 999 // stale version
|
||||
task.Title = "Should Fail"
|
||||
err := repo.Update(task)
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
// === Version Conflict on State Operations ===
|
||||
|
||||
func TestTaskRepository_Cancel_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.Cancel(task.ID, 999) // wrong version
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
func TestTaskRepository_Uncancel_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.Uncancel(task.ID, 999)
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
func TestTaskRepository_Archive_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.Archive(task.ID, 999)
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
func TestTaskRepository_Unarchive_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.Unarchive(task.ID, 999)
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
func TestTaskRepository_MarkInProgress(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.MarkInProgress(task.ID, task.Version)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(task.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, found.InProgress)
|
||||
}
|
||||
|
||||
func TestTaskRepository_MarkInProgress_VersionConflict(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
err := repo.MarkInProgress(task.ID, 999)
|
||||
assert.ErrorIs(t, err, ErrVersionConflict)
|
||||
}
|
||||
|
||||
// === CreateCompletionTx ===
|
||||
|
||||
func TestTaskRepository_CreateCompletionTx(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
tx := db.Begin()
|
||||
completion := &models.TaskCompletion{
|
||||
TaskID: task.ID,
|
||||
CompletedByID: user.ID,
|
||||
CompletedAt: time.Now().UTC(),
|
||||
Notes: "Done via tx",
|
||||
}
|
||||
err := repo.CreateCompletionTx(tx, completion)
|
||||
require.NoError(t, err)
|
||||
tx.Commit()
|
||||
|
||||
assert.NotZero(t, completion.ID)
|
||||
|
||||
found, err := repo.FindCompletionByID(completion.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Done via tx", found.Notes)
|
||||
}
|
||||
|
||||
// === GetFrequencyByID ===
|
||||
|
||||
func TestTaskRepository_GetFrequencyByID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
frequencies, err := repo.GetAllFrequencies()
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, frequencies)
|
||||
|
||||
found, err := repo.GetFrequencyByID(frequencies[0].ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, frequencies[0].Name, found.Name)
|
||||
}
|
||||
|
||||
func TestTaskRepository_GetFrequencyByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
_, err := repo.GetFrequencyByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === FindByUser ===
|
||||
|
||||
func TestTaskRepository_FindByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(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.CreateTestTask(t, db, r1.ID, user.ID, "Task A")
|
||||
testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task B")
|
||||
|
||||
tasks, err := repo.FindByUser(user.ID, []uint{r1.ID, r2.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, tasks, 2)
|
||||
}
|
||||
|
||||
// === CountByResidenceIDs ===
|
||||
|
||||
func TestTaskRepository_CountByResidenceIDs(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(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.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
|
||||
testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")
|
||||
testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 3")
|
||||
|
||||
count, err := repo.CountByResidenceIDs([]uint{r1.ID, r2.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
}
|
||||
|
||||
func TestTaskRepository_CountByResidenceIDs_Empty(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
count, err := repo.CountByResidenceIDs([]uint{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
// === FindCompletionsByUser ===
|
||||
|
||||
func TestTaskRepository_FindCompletionsByUser(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
c := &models.TaskCompletion{
|
||||
TaskID: task.ID,
|
||||
CompletedByID: user.ID,
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, db.Create(c).Error)
|
||||
|
||||
completions, err := repo.FindCompletionsByUser(user.ID, []uint{residence.ID})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, completions, 1)
|
||||
}
|
||||
|
||||
// === UpdateCompletion ===
|
||||
|
||||
func TestTaskRepository_UpdateCompletion(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
completion := &models.TaskCompletion{
|
||||
TaskID: task.ID,
|
||||
CompletedByID: user.ID,
|
||||
CompletedAt: time.Now().UTC(),
|
||||
Notes: "Original",
|
||||
}
|
||||
require.NoError(t, db.Create(completion).Error)
|
||||
|
||||
completion.Notes = "Updated notes"
|
||||
err := repo.UpdateCompletion(completion)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindCompletionByID(completion.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated notes", found.Notes)
|
||||
}
|
||||
|
||||
// === CompletionImage CRUD ===
|
||||
|
||||
func TestTaskRepository_CompletionImage_CRUD(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
completion := &models.TaskCompletion{
|
||||
TaskID: task.ID,
|
||||
CompletedByID: user.ID,
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, db.Create(completion).Error)
|
||||
|
||||
// Create image
|
||||
img := &models.TaskCompletionImage{
|
||||
CompletionID: completion.ID,
|
||||
ImageURL: "https://example.com/img.jpg",
|
||||
}
|
||||
err := repo.CreateCompletionImage(img)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, img.ID)
|
||||
|
||||
// Find image
|
||||
found, err := repo.FindCompletionImageByID(img.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://example.com/img.jpg", found.ImageURL)
|
||||
|
||||
// Delete image
|
||||
err = repo.DeleteCompletionImage(img.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindCompletionImageByID(img.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTaskRepository_FindCompletionImageByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
_, err := repo.FindCompletionImageByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === GetOverdueCountByResidence ===
|
||||
|
||||
func TestTaskRepository_GetOverdueCountByResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(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")
|
||||
|
||||
now := time.Now().UTC()
|
||||
pastDue := now.AddDate(0, 0, -5)
|
||||
|
||||
// Overdue task in r1
|
||||
t1 := &models.Task{
|
||||
ResidenceID: r1.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Overdue in r1",
|
||||
DueDate: &pastDue,
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
|
||||
// Not overdue task in r2 (future)
|
||||
futureDue := now.AddDate(0, 0, 30)
|
||||
t2 := &models.Task{
|
||||
ResidenceID: r2.ID,
|
||||
CreatedByID: user.ID,
|
||||
Title: "Future in r2",
|
||||
DueDate: &futureDue,
|
||||
Version: 1,
|
||||
}
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
countMap, err := repo.GetOverdueCountByResidence([]uint{r1.ID, r2.ID}, now)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, countMap[r1.ID])
|
||||
assert.Equal(t, 0, countMap[r2.ID])
|
||||
}
|
||||
|
||||
func TestTaskRepository_GetOverdueCountByResidence_EmptyIDs(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
countMap, err := repo.GetOverdueCountByResidence([]uint{}, time.Now().UTC())
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, countMap)
|
||||
}
|
||||
|
||||
// === GetKanbanDataForMultipleResidences ===
|
||||
|
||||
func TestTaskRepository_GetKanbanDataForMultipleResidences(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskRepository(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.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
|
||||
testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")
|
||||
|
||||
board, err := repo.GetKanbanDataForMultipleResidences([]uint{r1.ID, r2.ID}, 30, time.Now().UTC())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "all", board.ResidenceID)
|
||||
assert.Len(t, board.Columns, 5)
|
||||
|
||||
// Both tasks should appear in upcoming (no due date)
|
||||
var upcomingCol *models.KanbanColumn
|
||||
for i := range board.Columns {
|
||||
if board.Columns[i].Name == "upcoming_tasks" {
|
||||
upcomingCol = &board.Columns[i]
|
||||
}
|
||||
}
|
||||
require.NotNil(t, upcomingCol)
|
||||
assert.Equal(t, 2, upcomingCol.Count)
|
||||
}
|
||||
|
||||
// === DB() accessor ===
|
||||
|
||||
func TestTaskRepository_DB(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
assert.NotNil(t, repo.DB())
|
||||
}
|
||||
|
||||
// === GetBatchCompletionSummaries ===
|
||||
|
||||
func TestTaskRepository_GetBatchCompletionSummaries_Empty(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
result, err := repo.GetBatchCompletionSummaries([]uint{}, time.Now().UTC(), 10)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestTaskRepository_GetBatchCompletionSummaries_WithData(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(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")
|
||||
task1 := testutil.CreateTestTask(t, db, r1.ID, user.ID, "Task 1")
|
||||
task2 := testutil.CreateTestTask(t, db, r2.ID, user.ID, "Task 2")
|
||||
|
||||
now := time.Date(2026, 3, 15, 0, 0, 0, 0, time.UTC)
|
||||
c1 := models.TaskCompletion{
|
||||
TaskID: task1.ID, CompletedByID: user.ID,
|
||||
CompletedAt: time.Date(2026, 2, 1, 12, 0, 0, 0, time.UTC), CompletedFromColumn: "completed_tasks",
|
||||
}
|
||||
require.NoError(t, db.Create(&c1).Error)
|
||||
|
||||
c2 := models.TaskCompletion{
|
||||
TaskID: task2.ID, CompletedByID: user.ID,
|
||||
CompletedAt: time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC), CompletedFromColumn: "overdue_tasks",
|
||||
}
|
||||
require.NoError(t, db.Create(&c2).Error)
|
||||
|
||||
result, err := repo.GetBatchCompletionSummaries([]uint{r1.ID, r2.ID}, now, 10)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
|
||||
assert.Equal(t, 1, result[r1.ID].TotalAllTime)
|
||||
assert.Equal(t, 1, result[r2.ID].TotalAllTime)
|
||||
}
|
||||
|
||||
// === FindCompletionByID not found ===
|
||||
|
||||
func TestTaskRepository_FindCompletionByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
_, err := repo.FindCompletionByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
// === DeleteCompletion deletes images ===
|
||||
|
||||
func TestTaskRepository_DeleteCompletion_DeletesImages(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
|
||||
task := testutil.CreateTestTask(t, db, residence.ID, user.ID, "Test Task")
|
||||
|
||||
completion := &models.TaskCompletion{
|
||||
TaskID: task.ID,
|
||||
CompletedByID: user.ID,
|
||||
CompletedAt: time.Now().UTC(),
|
||||
}
|
||||
require.NoError(t, db.Create(completion).Error)
|
||||
|
||||
// Add images
|
||||
img := &models.TaskCompletionImage{
|
||||
CompletionID: completion.ID,
|
||||
ImageURL: "https://example.com/img.jpg",
|
||||
}
|
||||
require.NoError(t, db.Create(img).Error)
|
||||
|
||||
// Delete completion (should cascade to images)
|
||||
err := repo.DeleteCompletion(completion.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Images should be gone
|
||||
var count int64
|
||||
db.Model(&models.TaskCompletionImage{}).Where("completion_id = ?", completion.ID).Count(&count)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
236
internal/repositories/task_template_repo_test.go
Normal file
236
internal/repositories/task_template_repo_test.go
Normal file
@@ -0,0 +1,236 @@
|
||||
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 TestTaskTemplateRepository_Create(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
var cat models.TaskCategory
|
||||
require.NoError(t, db.First(&cat).Error)
|
||||
|
||||
var freq models.TaskFrequency
|
||||
require.NoError(t, db.First(&freq).Error)
|
||||
|
||||
template := &models.TaskTemplate{
|
||||
Title: "Change HVAC Filter",
|
||||
Description: "Replace the HVAC air filter",
|
||||
CategoryID: &cat.ID,
|
||||
FrequencyID: &freq.ID,
|
||||
IsActive: true,
|
||||
Tags: "hvac,filter,maintenance",
|
||||
}
|
||||
err := repo.Create(template)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, template.ID)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetAll(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
// Create active and inactive templates
|
||||
t1 := &models.TaskTemplate{Title: "Active Template", IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "Inactive Template", IsActive: false}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
templates, err := repo.GetAll()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templates, 1) // Only active
|
||||
assert.Equal(t, "Active Template", templates[0].Title)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetByCategory(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
var cat models.TaskCategory
|
||||
require.NoError(t, db.First(&cat).Error)
|
||||
|
||||
t1 := &models.TaskTemplate{Title: "Cat Template", CategoryID: &cat.ID, IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "No Cat Template", IsActive: true}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
templates, err := repo.GetByCategory(cat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templates, 1)
|
||||
assert.Equal(t, "Cat Template", templates[0].Title)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_Search(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
t1 := &models.TaskTemplate{Title: "Change HVAC Filter", Tags: "hvac,filter", IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "Fix Faucet", Tags: "plumbing", IsActive: true}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
// Search by title
|
||||
results, err := repo.Search("hvac")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, "Change HVAC Filter", results[0].Title)
|
||||
|
||||
// Search by tag
|
||||
results, err = repo.Search("plumbing")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, results, 1)
|
||||
assert.Equal(t, "Fix Faucet", results[0].Title)
|
||||
|
||||
// No match
|
||||
results, err = repo.Search("nonexistent")
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetByID(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
tmpl := &models.TaskTemplate{Title: "Test Template", IsActive: true}
|
||||
require.NoError(t, db.Create(tmpl).Error)
|
||||
|
||||
found, err := repo.GetByID(tmpl.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Test Template", found.Title)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetByID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
_, err := repo.GetByID(9999)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_Update(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
tmpl := &models.TaskTemplate{Title: "Original", IsActive: true}
|
||||
require.NoError(t, db.Create(tmpl).Error)
|
||||
|
||||
tmpl.Title = "Updated"
|
||||
err := repo.Update(tmpl)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.GetByID(tmpl.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated", found.Title)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_Delete(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
tmpl := &models.TaskTemplate{Title: "To Delete", IsActive: true}
|
||||
require.NoError(t, db.Create(tmpl).Error)
|
||||
|
||||
err := repo.Delete(tmpl.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.GetByID(tmpl.ID)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetAllIncludingInactive(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
t1 := &models.TaskTemplate{Title: "Active", IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "Inactive", IsActive: false}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
templates, err := repo.GetAllIncludingInactive()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templates, 2) // Both active and inactive
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_Count(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
t1 := &models.TaskTemplate{Title: "Active 1", IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "Active 2", IsActive: true}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
// Use raw SQL for inactive record to avoid GORM default:true overriding IsActive=false
|
||||
require.NoError(t, db.Exec(
|
||||
"INSERT INTO task_tasktemplate (title, is_active, display_order, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))",
|
||||
"Inactive", false, 0,
|
||||
).Error)
|
||||
|
||||
count, err := repo.Count()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), count) // Only active
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetByRegion(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
// Create a climate region
|
||||
region := &models.ClimateRegion{Name: "Hot-Humid", ZoneNumber: 1, IsActive: true}
|
||||
require.NoError(t, db.Create(region).Error)
|
||||
|
||||
// Create template with region association
|
||||
tmpl := &models.TaskTemplate{Title: "Regional Task", IsActive: true}
|
||||
require.NoError(t, db.Create(tmpl).Error)
|
||||
|
||||
// Associate template with region via join table
|
||||
err := db.Exec("INSERT INTO task_tasktemplate_regions (task_template_id, climate_region_id) VALUES (?, ?)", tmpl.ID, region.ID).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create template without region
|
||||
tmpl2 := &models.TaskTemplate{Title: "Non-Regional Task", IsActive: true}
|
||||
require.NoError(t, db.Create(tmpl2).Error)
|
||||
|
||||
templates, err := repo.GetByRegion(region.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templates, 1)
|
||||
assert.Equal(t, "Regional Task", templates[0].Title)
|
||||
}
|
||||
|
||||
func TestTaskTemplateRepository_GetGroupedByCategory(t *testing.T) {
|
||||
t.Skip("requires PostgreSQL: SQLite cannot scan jsonb default into json.RawMessage")
|
||||
db := testutil.SetupTestDB(t)
|
||||
testutil.SeedLookupData(t, db)
|
||||
repo := NewTaskTemplateRepository(db)
|
||||
|
||||
var cat models.TaskCategory
|
||||
require.NoError(t, db.First(&cat).Error)
|
||||
|
||||
t1 := &models.TaskTemplate{Title: "Categorized", CategoryID: &cat.ID, IsActive: true}
|
||||
t2 := &models.TaskTemplate{Title: "Uncategorized", IsActive: true}
|
||||
require.NoError(t, db.Create(t1).Error)
|
||||
require.NoError(t, db.Create(t2).Error)
|
||||
|
||||
grouped, err := repo.GetGroupedByCategory()
|
||||
require.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(grouped), 2) // At least the category + "Uncategorized"
|
||||
|
||||
// Uncategorized should have the template without category
|
||||
assert.Len(t, grouped["Uncategorized"], 1)
|
||||
assert.Equal(t, "Uncategorized", grouped["Uncategorized"][0].Title)
|
||||
}
|
||||
465
internal/repositories/user_repo_coverage_test.go
Normal file
465
internal/repositories/user_repo_coverage_test.go
Normal file
@@ -0,0 +1,465 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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_FindByUsername_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "TestUser", "test@example.com", "Password123")
|
||||
|
||||
// Should find with different cases
|
||||
found, err := repo.FindByUsername("testuser")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "TestUser", found.Username)
|
||||
|
||||
found, err = repo.FindByUsername("TESTUSER")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "TestUser", found.Username)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByUsername_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByUsername("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrUserNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByEmail_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "Test@Example.com", "Password123")
|
||||
|
||||
found, err := repo.FindByEmail("test@example.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Test@Example.com", found.Email)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByEmail_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByEmail("nonexistent@example.com")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrUserNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_ExistsByUsername_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "TestUser", "test@example.com", "Password123")
|
||||
|
||||
exists, err := repo.ExistsByUsername("TESTUSER")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
}
|
||||
|
||||
func TestUserRepository_ExistsByEmail_CaseInsensitive(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "Test@Example.com", "Password123")
|
||||
|
||||
exists, err := repo.ExistsByEmail("test@example.com")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
}
|
||||
|
||||
func TestUserRepository_GetOrCreateToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create token
|
||||
token1, err := repo.GetOrCreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, token1.Key)
|
||||
|
||||
// Should return same token
|
||||
token2, err := repo.GetOrCreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, token1.Key, token2.Key)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindTokenByKey(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
token, err := repo.GetOrCreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindTokenByKey(token.Key)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, token.Key, found.Key)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindTokenByKey_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindTokenByKey("nonexistent-token-key")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrTokenNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_DeleteToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
token, err := repo.GetOrCreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.DeleteToken(token.Key)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindTokenByKey(token.Key)
|
||||
assert.ErrorIs(t, err, ErrTokenNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_DeleteToken_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
err := repo.DeleteToken("nonexistent-key")
|
||||
assert.ErrorIs(t, err, ErrTokenNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_DeleteTokenByUserID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
_, err := repo.GetOrCreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.DeleteTokenByUserID(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Token should be gone
|
||||
var count int64
|
||||
db.Model(&models.AuthToken{}).Where("user_id = ?", user.ID).Count(&count)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestUserRepository_CreateToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
token, err := repo.CreateToken(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, token.Key)
|
||||
assert.Equal(t, user.ID, token.UserID)
|
||||
}
|
||||
|
||||
func TestUserRepository_UpdateLastLogin(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
err := repo.UpdateLastLogin(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, found.LastLogin)
|
||||
}
|
||||
|
||||
func TestUserRepository_UpdateProfile(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
profile, err := repo.GetOrCreateProfile(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
profile.Bio = "Test bio"
|
||||
profile.PhoneNumber = "+1-555-0100"
|
||||
err = repo.UpdateProfile(profile)
|
||||
require.NoError(t, err)
|
||||
|
||||
updated, err := repo.GetOrCreateProfile(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Test bio", updated.Bio)
|
||||
assert.Equal(t, "+1-555-0100", updated.PhoneNumber)
|
||||
}
|
||||
|
||||
func TestUserRepository_SetProfileVerified(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create profile
|
||||
_, err := repo.GetOrCreateProfile(user.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.SetProfileVerified(user.ID, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
profile, err := repo.GetOrCreateProfile(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, profile.Verified)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByIDWithProfile(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create profile first
|
||||
profile := &models.UserProfile{
|
||||
UserID: user.ID,
|
||||
Bio: "My bio",
|
||||
}
|
||||
err := db.Create(profile).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByIDWithProfile(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.ID)
|
||||
require.NotNil(t, found.Profile)
|
||||
assert.Equal(t, "My bio", found.Profile.Bio)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByIDWithProfile_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByIDWithProfile(9999)
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, ErrUserNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_ConfirmationCode_Lifecycle(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create confirmation code
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreateConfirmationCode(user.ID, "123456", expiresAt)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, code.ID)
|
||||
|
||||
// Find it
|
||||
found, err := repo.FindConfirmationCode(user.ID, "123456")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, code.ID, found.ID)
|
||||
|
||||
// Mark as used
|
||||
err = repo.MarkConfirmationCodeUsed(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should not find used code
|
||||
_, err = repo.FindConfirmationCode(user.ID, "123456")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserRepository_ConfirmationCode_InvalidatesExisting(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
|
||||
// Create first code
|
||||
code1, err := repo.CreateConfirmationCode(user.ID, "111111", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create second code (should invalidate first)
|
||||
_, err = repo.CreateConfirmationCode(user.ID, "222222", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First code should be used/invalidated
|
||||
var c models.ConfirmationCode
|
||||
db.First(&c, code1.ID)
|
||||
assert.True(t, c.IsUsed)
|
||||
}
|
||||
|
||||
func TestUserRepository_Transaction(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
err := repo.Transaction(func(txRepo *UserRepository) error {
|
||||
found, err := txRepo.FindByID(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found.FirstName = "Updated"
|
||||
return txRepo.Update(found)
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated", found.FirstName)
|
||||
}
|
||||
|
||||
func TestUserRepository_DB(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
assert.NotNil(t, repo.DB())
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByAppleID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "appleuser", "apple@test.com", "Password123")
|
||||
appleAuth := &models.AppleSocialAuth{
|
||||
UserID: user.ID,
|
||||
AppleID: "apple_sub_123",
|
||||
Email: "apple@test.com",
|
||||
}
|
||||
require.NoError(t, db.Create(appleAuth).Error)
|
||||
|
||||
found, err := repo.FindByAppleID("apple_sub_123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByAppleID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByAppleID("nonexistent_apple_id")
|
||||
assert.ErrorIs(t, err, ErrAppleAuthNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByGoogleID(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "Password123")
|
||||
googleAuth := &models.GoogleSocialAuth{
|
||||
UserID: user.ID,
|
||||
GoogleID: "google_sub_123",
|
||||
Email: "google@test.com",
|
||||
}
|
||||
require.NoError(t, db.Create(googleAuth).Error)
|
||||
|
||||
found, err := repo.FindByGoogleID("google_sub_123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.UserID)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByGoogleID_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByGoogleID("nonexistent_google_id")
|
||||
assert.ErrorIs(t, err, ErrGoogleAuthNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_CreateAndUpdateAppleSocialAuth(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "appleuser", "apple@test.com", "Password123")
|
||||
|
||||
auth := &models.AppleSocialAuth{
|
||||
UserID: user.ID,
|
||||
AppleID: "apple_sub_456",
|
||||
Email: "apple@test.com",
|
||||
}
|
||||
err := repo.CreateAppleSocialAuth(auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
|
||||
auth.Email = "updated@test.com"
|
||||
err = repo.UpdateAppleSocialAuth(auth)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByAppleID("apple_sub_456")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "updated@test.com", found.Email)
|
||||
}
|
||||
|
||||
func TestUserRepository_CreateAndUpdateGoogleSocialAuth(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "Password123")
|
||||
|
||||
auth := &models.GoogleSocialAuth{
|
||||
UserID: user.ID,
|
||||
GoogleID: "google_sub_456",
|
||||
Email: "google@test.com",
|
||||
Name: "Test User",
|
||||
}
|
||||
err := repo.CreateGoogleSocialAuth(auth)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, auth.ID)
|
||||
|
||||
auth.Name = "Updated Name"
|
||||
err = repo.UpdateGoogleSocialAuth(auth)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByGoogleID("google_sub_456")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Name", found.Name)
|
||||
}
|
||||
|
||||
func TestUserRepository_SearchUsers(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "john_doe", "john@example.com", "Password123")
|
||||
testutil.CreateTestUser(t, db, "jane_smith", "jane@example.com", "Password123")
|
||||
testutil.CreateTestUser(t, db, "bob_builder", "bob@example.com", "Password123")
|
||||
|
||||
users, total, err := repo.SearchUsers("john", 10, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), total)
|
||||
assert.Len(t, users, 1)
|
||||
assert.Equal(t, "john_doe", users[0].Username)
|
||||
}
|
||||
|
||||
func TestUserRepository_ListUsers(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "user1", "user1@example.com", "Password123")
|
||||
testutil.CreateTestUser(t, db, "user2", "user2@example.com", "Password123")
|
||||
testutil.CreateTestUser(t, db, "user3", "user3@example.com", "Password123")
|
||||
|
||||
users, total, err := repo.ListUsers(2, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, users, 2) // Limited to 2
|
||||
|
||||
users, total, err = repo.ListUsers(2, 2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(3), total)
|
||||
assert.Len(t, users, 1) // Only 1 remaining
|
||||
}
|
||||
367
internal/repositories/user_repo_extended_test.go
Normal file
367
internal/repositories/user_repo_extended_test.go
Normal file
@@ -0,0 +1,367 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/treytartt/honeydue-api/internal/models"
|
||||
"github.com/treytartt/honeydue-api/internal/testutil"
|
||||
)
|
||||
|
||||
// === Password Reset Code Lifecycle ===
|
||||
|
||||
func TestUserRepository_PasswordResetCode_Lifecycle(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_abc123", "reset_token_xyz", expiresAt)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, code.ID)
|
||||
assert.Equal(t, "hash_abc123", code.CodeHash)
|
||||
assert.Equal(t, "reset_token_xyz", code.ResetToken)
|
||||
assert.False(t, code.Used)
|
||||
assert.Equal(t, 0, code.Attempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_CreatePasswordResetCode_InvalidatesExisting(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
|
||||
code1, err := repo.CreatePasswordResetCode(user.ID, "hash1", "token1", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.CreatePasswordResetCode(user.ID, "hash2", "token2", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// First code should be marked as used
|
||||
var c models.PasswordResetCode
|
||||
db.First(&c, code1.ID)
|
||||
assert.True(t, c.Used)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_abc", "token_abc", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, foundUser, err := repo.FindPasswordResetCodeByEmail("test@example.com")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, user.ID, foundUser.ID)
|
||||
assert.Equal(t, "hash_abc", found.CodeHash)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail_UserNotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, _, err := repo.FindPasswordResetCodeByEmail("nonexistent@example.com")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByEmail_NoCode(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
_, _, err := repo.FindPasswordResetCodeByEmail("test@example.com")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_xyz", "token_xyz", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindPasswordResetCodeByToken("token_xyz")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "hash_xyz", found.CodeHash)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindPasswordResetCodeByToken("nonexistent_token")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_Expired(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Already expired
|
||||
expiresAt := time.Now().UTC().Add(-1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash_exp", "token_exp", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_exp")
|
||||
assert.ErrorIs(t, err, ErrCodeExpired)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_Used(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_used", "token_used", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Mark as used
|
||||
err = repo.MarkPasswordResetCodeUsed(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_used")
|
||||
assert.ErrorIs(t, err, ErrCodeUsed)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindPasswordResetCodeByToken_TooManyAttempts(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_attempts", "token_attempts", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Max out attempts
|
||||
for i := 0; i < 5; i++ {
|
||||
err = repo.IncrementResetCodeAttempts(code.ID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err = repo.FindPasswordResetCodeByToken("token_attempts")
|
||||
assert.ErrorIs(t, err, ErrTooManyAttempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_IncrementResetCodeAttempts(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_inc", "token_inc", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.IncrementResetCodeAttempts(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updated models.PasswordResetCode
|
||||
db.First(&updated, code.ID)
|
||||
assert.Equal(t, 1, updated.Attempts)
|
||||
}
|
||||
|
||||
func TestUserRepository_MarkPasswordResetCodeUsed(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
code, err := repo.CreatePasswordResetCode(user.ID, "hash_mark", "token_mark", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.MarkPasswordResetCodeUsed(code.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
var updated models.PasswordResetCode
|
||||
db.First(&updated, code.ID)
|
||||
assert.True(t, updated.Used)
|
||||
}
|
||||
|
||||
func TestUserRepository_CountRecentPasswordResetRequests(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
expiresAt := time.Now().UTC().Add(1 * time.Hour)
|
||||
_, err := repo.CreatePasswordResetCode(user.ID, "hash1", "token1", expiresAt)
|
||||
require.NoError(t, err)
|
||||
_, err = repo.CreatePasswordResetCode(user.ID, "hash2", "token2", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
count, err := repo.CountRecentPasswordResetRequests(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(2), count)
|
||||
}
|
||||
|
||||
// === FindUsersInSharedResidences ===
|
||||
|
||||
func TestUserRepository_FindUsersInSharedResidences(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := NewUserRepository(db)
|
||||
resRepo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
shared := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
|
||||
unrelated := testutil.CreateTestUser(t, db, "unrelated", "unrelated@test.com", "Password123")
|
||||
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Shared House")
|
||||
resRepo.AddUser(residence.ID, shared.ID)
|
||||
|
||||
// Owner should see shared user
|
||||
users, err := userRepo.FindUsersInSharedResidences(owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 1)
|
||||
assert.Equal(t, shared.ID, users[0].ID)
|
||||
|
||||
// Shared user should see owner
|
||||
users, err = userRepo.FindUsersInSharedResidences(shared.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, users, 1)
|
||||
assert.Equal(t, owner.ID, users[0].ID)
|
||||
|
||||
// Unrelated should see no one
|
||||
users, err = userRepo.FindUsersInSharedResidences(unrelated.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, users)
|
||||
}
|
||||
|
||||
// === FindUserIfSharedResidence ===
|
||||
|
||||
func TestUserRepository_FindUserIfSharedResidence(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := NewUserRepository(db)
|
||||
resRepo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
shared := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
|
||||
unrelated := testutil.CreateTestUser(t, db, "unrelated", "unrelated@test.com", "Password123")
|
||||
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Shared House")
|
||||
resRepo.AddUser(residence.ID, shared.ID)
|
||||
|
||||
// Owner requesting shared user => should find
|
||||
found, err := userRepo.FindUserIfSharedResidence(shared.ID, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found)
|
||||
assert.Equal(t, shared.ID, found.ID)
|
||||
|
||||
// Unrelated requesting shared user => should not find
|
||||
found, err = userRepo.FindUserIfSharedResidence(shared.ID, unrelated.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, found)
|
||||
|
||||
// Requesting self => should work
|
||||
found, err = userRepo.FindUserIfSharedResidence(owner.ID, owner.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, found)
|
||||
assert.Equal(t, owner.ID, found.ID)
|
||||
}
|
||||
|
||||
// === FindProfilesInSharedResidences ===
|
||||
|
||||
func TestUserRepository_FindProfilesInSharedResidences(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
userRepo := NewUserRepository(db)
|
||||
resRepo := NewResidenceRepository(db)
|
||||
|
||||
owner := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
|
||||
shared := testutil.CreateTestUser(t, db, "shared", "shared@test.com", "Password123")
|
||||
|
||||
// Create profiles
|
||||
ownerProfile := &models.UserProfile{UserID: owner.ID, Bio: "Owner bio"}
|
||||
sharedProfile := &models.UserProfile{UserID: shared.ID, Bio: "Shared bio"}
|
||||
require.NoError(t, db.Create(ownerProfile).Error)
|
||||
require.NoError(t, db.Create(sharedProfile).Error)
|
||||
|
||||
residence := testutil.CreateTestResidence(t, db, owner.ID, "Shared House")
|
||||
resRepo.AddUser(residence.ID, shared.ID)
|
||||
|
||||
// Owner sees own profile + shared user profile
|
||||
profiles, err := userRepo.FindProfilesInSharedResidences(owner.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, profiles, 2)
|
||||
}
|
||||
|
||||
// === ConfirmationCode Expired ===
|
||||
|
||||
func TestUserRepository_FindConfirmationCode_Expired(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Create already-expired code
|
||||
expiresAt := time.Now().UTC().Add(-1 * time.Hour)
|
||||
_, err := repo.CreateConfirmationCode(user.ID, "999999", expiresAt)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = repo.FindConfirmationCode(user.ID, "999999")
|
||||
assert.ErrorIs(t, err, ErrCodeExpired)
|
||||
}
|
||||
|
||||
func TestUserRepository_FindConfirmationCode_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
_, err := repo.FindConfirmationCode(user.ID, "000000")
|
||||
assert.ErrorIs(t, err, ErrCodeNotFound)
|
||||
}
|
||||
|
||||
// === Transaction Rollback ===
|
||||
|
||||
func TestUserRepository_Transaction_Rollback(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
err := repo.Transaction(func(txRepo *UserRepository) error {
|
||||
found, err := txRepo.FindByID(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found.FirstName = "ShouldRollback"
|
||||
if err := txRepo.Update(found); err != nil {
|
||||
return err
|
||||
}
|
||||
// Simulate an error to trigger rollback
|
||||
return ErrUserNotFound
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
// Name should NOT have been updated
|
||||
found, err := repo.FindByID(user.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, "ShouldRollback", found.FirstName)
|
||||
}
|
||||
|
||||
// === FindByUsernameOrEmail not found ===
|
||||
|
||||
func TestUserRepository_FindByUsernameOrEmail_NotFound(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
_, err := repo.FindByUsernameOrEmail("nonexistent")
|
||||
assert.ErrorIs(t, err, ErrUserNotFound)
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func TestUserRepository_Create(t *testing.T) {
|
||||
Email: "test@example.com",
|
||||
IsActive: true,
|
||||
}
|
||||
user.SetPassword("password123")
|
||||
user.SetPassword("Password123")
|
||||
|
||||
err := repo.Create(user)
|
||||
require.NoError(t, err)
|
||||
@@ -31,7 +31,7 @@ func TestUserRepository_FindByID(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Find by ID
|
||||
found, err := repo.FindByID(user.ID)
|
||||
@@ -54,7 +54,7 @@ func TestUserRepository_FindByUsername(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Find by username
|
||||
found, err := repo.FindByUsername("testuser")
|
||||
@@ -67,7 +67,7 @@ func TestUserRepository_FindByEmail(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Find by email
|
||||
found, err := repo.FindByEmail("test@example.com")
|
||||
@@ -80,7 +80,7 @@ func TestUserRepository_FindByUsernameOrEmail(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -105,7 +105,7 @@ func TestUserRepository_Update(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// Update user
|
||||
user.FirstName = "John"
|
||||
@@ -125,7 +125,7 @@ func TestUserRepository_ExistsByUsername(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "password123")
|
||||
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -150,7 +150,7 @@ func TestUserRepository_ExistsByEmail(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "password123")
|
||||
testutil.CreateTestUser(t, db, "existinguser", "existing@example.com", "Password123")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -175,7 +175,7 @@ func TestUserRepository_GetOrCreateProfile(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
// Create user
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "testuser", "test@example.com", "Password123")
|
||||
|
||||
// First call should create
|
||||
profile1, err := repo.GetOrCreateProfile(user.ID)
|
||||
@@ -193,14 +193,14 @@ func TestUserRepository_FindAuthProvider(t *testing.T) {
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
t.Run("email user", func(t *testing.T) {
|
||||
user := testutil.CreateTestUser(t, db, "emailuser", "email@test.com", "password123")
|
||||
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")
|
||||
user := testutil.CreateTestUser(t, db, "appleuser", "apple@test.com", "Password123")
|
||||
appleAuth := &models.AppleSocialAuth{
|
||||
UserID: user.ID,
|
||||
AppleID: "apple_sub_test",
|
||||
@@ -214,7 +214,7 @@ func TestUserRepository_FindAuthProvider(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("google user", func(t *testing.T) {
|
||||
user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "googleuser", "google@test.com", "Password123")
|
||||
googleAuth := &models.GoogleSocialAuth{
|
||||
UserID: user.ID,
|
||||
GoogleID: "google_sub_test",
|
||||
@@ -233,7 +233,7 @@ func TestUserRepository_DeleteUserCascade(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "deletebare", "deletebare@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "deletebare", "deletebare@test.com", "Password123")
|
||||
|
||||
// Create profile and token
|
||||
profile := &models.UserProfile{UserID: user.ID, Verified: true}
|
||||
@@ -271,7 +271,7 @@ func TestUserRepository_DeleteUserCascade(t *testing.T) {
|
||||
db := testutil.SetupTestDB(t)
|
||||
repo := NewUserRepository(db)
|
||||
|
||||
user := testutil.CreateTestUser(t, db, "deletefiles", "deletefiles@test.com", "password123")
|
||||
user := testutil.CreateTestUser(t, db, "deletefiles", "deletefiles@test.com", "Password123")
|
||||
residence := testutil.CreateTestResidence(t, db, user.ID, "Test Home")
|
||||
|
||||
// Create document with file
|
||||
@@ -319,8 +319,8 @@ func TestUserRepository_DeleteUserCascade(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")
|
||||
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
|
||||
|
||||
29
internal/repositories/util_test.go
Normal file
29
internal/repositories/util_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEscapeLikeWildcards(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"no wildcards", "hello", "hello"},
|
||||
{"percent sign", "50% off", "50\\% off"},
|
||||
{"underscore", "user_name", "user\\_name"},
|
||||
{"both wildcards", "50%_off", "50\\%\\_off"},
|
||||
{"empty string", "", ""},
|
||||
{"only wildcards", "%_", "\\%\\_"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := escapeLikeWildcards(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user