Add Apple Sign In welcome email, notification preferences on registration, and contractor sharing tests

- Send welcome email to new users who sign up via Apple Sign In
- Create notification preferences (all enabled) when new accounts are created
- Add comprehensive integration tests for contractor sharing:
  - Personal contractors only visible to creator
  - Residence-tied contractors visible to all users with residence access
  - Update/delete access control for shared contractors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-01 17:42:37 -06:00
parent be507561f1
commit 0a708c092d
7 changed files with 527 additions and 11 deletions

View File

@@ -24,15 +24,17 @@ import (
// TestApp holds all components for integration testing
type TestApp struct {
DB *gorm.DB
Router *gin.Engine
AuthHandler *handlers.AuthHandler
ResidenceHandler *handlers.ResidenceHandler
TaskHandler *handlers.TaskHandler
UserRepo *repositories.UserRepository
ResidenceRepo *repositories.ResidenceRepository
TaskRepo *repositories.TaskRepository
AuthService *services.AuthService
DB *gorm.DB
Router *gin.Engine
AuthHandler *handlers.AuthHandler
ResidenceHandler *handlers.ResidenceHandler
TaskHandler *handlers.TaskHandler
ContractorHandler *handlers.ContractorHandler
UserRepo *repositories.UserRepository
ResidenceRepo *repositories.ResidenceRepository
TaskRepo *repositories.TaskRepository
ContractorRepo *repositories.ContractorRepository
AuthService *services.AuthService
}
func setupIntegrationTest(t *testing.T) *TestApp {
@@ -713,3 +715,103 @@ func TestIntegration_ResponseStructure(t *testing.T) {
func formatID(id float64) string {
return fmt.Sprintf("%d", uint(id))
}
// setupContractorTest sets up a test environment including contractor routes
func setupContractorTest(t *testing.T) *TestApp {
gin.SetMode(gin.TestMode)
db := testutil.SetupTestDB(t)
testutil.SeedLookupData(t, db)
// Create repositories
userRepo := repositories.NewUserRepository(db)
residenceRepo := repositories.NewResidenceRepository(db)
taskRepo := repositories.NewTaskRepository(db)
contractorRepo := repositories.NewContractorRepository(db)
// Create config
cfg := &config.Config{
Security: config.SecurityConfig{
SecretKey: "test-secret-key-for-integration-tests",
PasswordResetExpiry: 15 * time.Minute,
ConfirmationExpiry: 24 * time.Hour,
MaxPasswordResetRate: 3,
},
}
// Create services
authService := services.NewAuthService(userRepo, cfg)
residenceService := services.NewResidenceService(residenceRepo, userRepo, cfg)
taskService := services.NewTaskService(taskRepo, residenceRepo)
contractorService := services.NewContractorService(contractorRepo, residenceRepo)
// Create handlers
authHandler := handlers.NewAuthHandler(authService, nil, nil)
residenceHandler := handlers.NewResidenceHandler(residenceService, nil, nil)
taskHandler := handlers.NewTaskHandler(taskService, nil)
contractorHandler := handlers.NewContractorHandler(contractorService)
// Create router with real middleware
router := gin.New()
// Public routes
auth := router.Group("/api/auth")
{
auth.POST("/register", authHandler.Register)
auth.POST("/login", authHandler.Login)
}
// Protected routes
authMiddleware := middleware.NewAuthMiddleware(db, nil)
api := router.Group("/api")
api.Use(authMiddleware.TokenAuth())
{
api.GET("/auth/me", authHandler.CurrentUser)
api.POST("/auth/logout", authHandler.Logout)
residences := api.Group("/residences")
{
residences.GET("", residenceHandler.ListResidences)
residences.POST("", residenceHandler.CreateResidence)
residences.GET("/:id", residenceHandler.GetResidence)
residences.PUT("/:id", residenceHandler.UpdateResidence)
residences.DELETE("/:id", residenceHandler.DeleteResidence)
residences.POST("/:id/generate-share-code", residenceHandler.GenerateShareCode)
residences.GET("/:id/users", residenceHandler.GetResidenceUsers)
residences.DELETE("/:id/users/:userId", residenceHandler.RemoveResidenceUser)
}
api.POST("/residences/join-with-code", residenceHandler.JoinWithCode)
tasks := api.Group("/tasks")
{
tasks.GET("", taskHandler.ListTasks)
tasks.POST("", taskHandler.CreateTask)
tasks.GET("/:id", taskHandler.GetTask)
tasks.PUT("/:id", taskHandler.UpdateTask)
tasks.DELETE("/:id", taskHandler.DeleteTask)
}
contractors := api.Group("/contractors")
{
contractors.GET("", contractorHandler.ListContractors)
contractors.POST("", contractorHandler.CreateContractor)
contractors.GET("/:id", contractorHandler.GetContractor)
contractors.PUT("/:id", contractorHandler.UpdateContractor)
contractors.DELETE("/:id", contractorHandler.DeleteContractor)
}
}
return &TestApp{
DB: db,
Router: router,
AuthHandler: authHandler,
ResidenceHandler: residenceHandler,
TaskHandler: taskHandler,
ContractorHandler: contractorHandler,
UserRepo: userRepo,
ResidenceRepo: residenceRepo,
TaskRepo: taskRepo,
ContractorRepo: contractorRepo,
AuthService: authService,
}
}