package testutil import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/treytartt/casera-api/internal/models" ) // SetupTestDB creates an in-memory SQLite database for testing func SetupTestDB(t *testing.T) *gorm.DB { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) // Migrate all models err = db.AutoMigrate( &models.User{}, &models.UserProfile{}, &models.AuthToken{}, &models.ConfirmationCode{}, &models.PasswordResetCode{}, &models.AdminUser{}, &models.Residence{}, &models.ResidenceType{}, &models.ResidenceShareCode{}, &models.Task{}, &models.TaskCategory{}, &models.TaskPriority{}, &models.TaskStatus{}, &models.TaskFrequency{}, &models.TaskCompletion{}, &models.Contractor{}, &models.ContractorSpecialty{}, &models.Document{}, &models.Notification{}, &models.NotificationPreference{}, &models.APNSDevice{}, &models.GCMDevice{}, &models.UserSubscription{}, &models.TierLimits{}, &models.FeatureBenefit{}, &models.UpgradeTrigger{}, ) require.NoError(t, err) return db } // SetupTestRouter creates a test Gin router func SetupTestRouter() *gin.Engine { gin.SetMode(gin.TestMode) return gin.New() } // MakeRequest makes a test HTTP request and returns the response func MakeRequest(router *gin.Engine, method, path string, body interface{}, token string) *httptest.ResponseRecorder { var reqBody *bytes.Buffer if body != nil { jsonBody, _ := json.Marshal(body) reqBody = bytes.NewBuffer(jsonBody) } else { reqBody = bytes.NewBuffer(nil) } req, _ := http.NewRequest(method, path, reqBody) req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Token "+token) } w := httptest.NewRecorder() router.ServeHTTP(w, req) return w } // ParseJSON parses JSON response body into a map func ParseJSON(t *testing.T, body []byte) map[string]interface{} { var result map[string]interface{} err := json.Unmarshal(body, &result) require.NoError(t, err) return result } // ParseJSONArray parses JSON response body into an array func ParseJSONArray(t *testing.T, body []byte) []map[string]interface{} { var result []map[string]interface{} err := json.Unmarshal(body, &result) require.NoError(t, err) return result } // CreateTestUser creates a test user in the database func CreateTestUser(t *testing.T, db *gorm.DB, username, email, password string) *models.User { user := &models.User{ Username: username, Email: email, IsActive: true, } err := user.SetPassword(password) require.NoError(t, err) err = db.Create(user).Error require.NoError(t, err) return user } // CreateTestToken creates an auth token for a user func CreateTestToken(t *testing.T, db *gorm.DB, userID uint) *models.AuthToken { token, err := models.GetOrCreateToken(db, userID) require.NoError(t, err) return token } // CreateTestResidenceType creates a test residence type func CreateTestResidenceType(t *testing.T, db *gorm.DB, name string) *models.ResidenceType { rt := &models.ResidenceType{Name: name} err := db.Create(rt).Error require.NoError(t, err) return rt } // CreateTestResidence creates a test residence func CreateTestResidence(t *testing.T, db *gorm.DB, ownerID uint, name string) *models.Residence { residence := &models.Residence{ OwnerID: ownerID, Name: name, StreetAddress: "123 Test St", City: "Test City", StateProvince: "TS", PostalCode: "12345", Country: "USA", IsActive: true, IsPrimary: true, } err := db.Create(residence).Error require.NoError(t, err) return residence } // CreateTestTaskCategory creates a test task category func CreateTestTaskCategory(t *testing.T, db *gorm.DB, name string) *models.TaskCategory { cat := &models.TaskCategory{ Name: name, DisplayOrder: 1, } err := db.Create(cat).Error require.NoError(t, err) return cat } // CreateTestTaskPriority creates a test task priority func CreateTestTaskPriority(t *testing.T, db *gorm.DB, name string, level int) *models.TaskPriority { priority := &models.TaskPriority{ Name: name, Level: level, DisplayOrder: level, } err := db.Create(priority).Error require.NoError(t, err) return priority } // CreateTestTaskStatus creates a test task status func CreateTestTaskStatus(t *testing.T, db *gorm.DB, name string) *models.TaskStatus { status := &models.TaskStatus{ Name: name, DisplayOrder: 1, } err := db.Create(status).Error require.NoError(t, err) return status } // CreateTestTaskFrequency creates a test task frequency func CreateTestTaskFrequency(t *testing.T, db *gorm.DB, name string, days *int) *models.TaskFrequency { freq := &models.TaskFrequency{ Name: name, Days: days, DisplayOrder: 1, } err := db.Create(freq).Error require.NoError(t, err) return freq } // CreateTestTask creates a test task func CreateTestTask(t *testing.T, db *gorm.DB, residenceID, createdByID uint, title string) *models.Task { task := &models.Task{ ResidenceID: residenceID, CreatedByID: createdByID, Title: title, IsCancelled: false, IsArchived: false, } err := db.Create(task).Error require.NoError(t, err) return task } // SeedLookupData seeds all lookup tables with test data func SeedLookupData(t *testing.T, db *gorm.DB) { // Residence types residenceTypes := []models.ResidenceType{ {Name: "House"}, {Name: "Apartment"}, {Name: "Condo"}, {Name: "Townhouse"}, } for _, rt := range residenceTypes { db.Create(&rt) } // Task categories categories := []models.TaskCategory{ {Name: "Plumbing", DisplayOrder: 1}, {Name: "Electrical", DisplayOrder: 2}, {Name: "HVAC", DisplayOrder: 3}, {Name: "General", DisplayOrder: 99}, } for _, c := range categories { db.Create(&c) } // Task priorities priorities := []models.TaskPriority{ {Name: "Low", Level: 1, DisplayOrder: 1}, {Name: "Medium", Level: 2, DisplayOrder: 2}, {Name: "High", Level: 3, DisplayOrder: 3}, {Name: "Urgent", Level: 4, DisplayOrder: 4}, } for _, p := range priorities { db.Create(&p) } // Task statuses statuses := []models.TaskStatus{ {Name: "Pending", DisplayOrder: 1}, {Name: "In Progress", DisplayOrder: 2}, {Name: "Completed", DisplayOrder: 3}, {Name: "Cancelled", DisplayOrder: 4}, } for _, s := range statuses { db.Create(&s) } // Task frequencies days7 := 7 days30 := 30 frequencies := []models.TaskFrequency{ {Name: "Once", Days: nil, DisplayOrder: 1}, {Name: "Weekly", Days: &days7, DisplayOrder: 2}, {Name: "Monthly", Days: &days30, DisplayOrder: 3}, } for _, f := range frequencies { db.Create(&f) } // Contractor specialties specialties := []models.ContractorSpecialty{ {Name: "Plumber"}, {Name: "Electrician"}, {Name: "HVAC Technician"}, {Name: "Handyman"}, } for _, s := range specialties { db.Create(&s) } } // AssertJSONField asserts that a JSON field has the expected value func AssertJSONField(t *testing.T, data map[string]interface{}, field string, expected interface{}) { actual, ok := data[field] require.True(t, ok, "field %s not found in response", field) require.Equal(t, expected, actual, "field %s has unexpected value", field) } // AssertJSONFieldExists asserts that a JSON field exists func AssertJSONFieldExists(t *testing.T, data map[string]interface{}, field string) { _, ok := data[field] require.True(t, ok, "field %s not found in response", field) } // AssertStatusCode asserts the HTTP status code func AssertStatusCode(t *testing.T, w *httptest.ResponseRecorder, expected int) { require.Equal(t, expected, w.Code, "unexpected status code: %s", w.Body.String()) } // MockAuthMiddleware creates middleware that sets a test user in context func MockAuthMiddleware(user *models.User) gin.HandlerFunc { return func(c *gin.Context) { c.Set("auth_user", user) c.Set("auth_token", "test-token") c.Next() } } // CreateTestContractor creates a test contractor func CreateTestContractor(t *testing.T, db *gorm.DB, residenceID, createdByID uint, name string) *models.Contractor { contractor := &models.Contractor{ ResidenceID: &residenceID, CreatedByID: createdByID, Name: name, IsActive: true, } err := db.Create(contractor).Error require.NoError(t, err) return contractor } // CreateTestContractorSpecialty creates a test contractor specialty func CreateTestContractorSpecialty(t *testing.T, db *gorm.DB, name string) *models.ContractorSpecialty { specialty := &models.ContractorSpecialty{Name: name} err := db.Create(specialty).Error require.NoError(t, err) return specialty } // CreateTestDocument creates a test document func CreateTestDocument(t *testing.T, db *gorm.DB, residenceID, createdByID uint, title string) *models.Document { doc := &models.Document{ ResidenceID: residenceID, CreatedByID: createdByID, Title: title, DocumentType: "general", FileURL: "https://example.com/doc.pdf", } err := db.Create(doc).Error require.NoError(t, err) return doc }