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:
Trey T
2026-04-01 20:30:09 -05:00
parent 00fd674b56
commit bec880886b
83 changed files with 19569 additions and 730 deletions

View 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)
}

View 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)
}

View 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)
}

View 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: &notExpiring,
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)
}

View 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)
}

View 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, &notif.ID)
require.NoError(t, err)
assert.NotNil(t, logEntry.NotificationID)
assert.Equal(t, notif.ID, *logEntry.NotificationID)
}

View 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
}

View 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)
}

View 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)
}

View 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)
}

View 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
}

View 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)
}

View File

@@ -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

View 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)
})
}
}