diff --git a/cmd/server/tests/test_helpers.go b/cmd/server/tests/test_helpers.go index bd9bd4c..dbda90a 100644 --- a/cmd/server/tests/test_helpers.go +++ b/cmd/server/tests/test_helpers.go @@ -4,8 +4,11 @@ import ( "bookhoard/internal/config" "bookhoard/internal/database" "bookhoard/internal/handlers" + "bookhoard/internal/middleware" ratelimit "bookhoard/internal/middleware" - wsync "bookhoard/internal/sync" + "bookhoard/internal/router" + "bookhoard/internal/services" + "bookhoard/internal/sync" "bytes" "context" "encoding/json" @@ -16,6 +19,7 @@ import ( "testing" "time" + "github.com/go-playground/validator/v10" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" "github.com/jackc/pgx/v5/pgxpool" @@ -24,6 +28,15 @@ import ( "github.com/stretchr/testify/require" ) +// CustomValidator wraps the go-playground validator +type CustomValidator struct { + validator *validator.Validate +} + +func (cv *CustomValidator) Validate(i interface{}) error { + return cv.validator.Struct(i) +} + // Helper functions for testing func containsPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix @@ -111,41 +124,71 @@ func setupTestServer(t *testing.T) (*httptest.Server, *database.Queries, *config // Create handlers authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker) + libraryHandler := handlers.NewLibraryHandler(queries) deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg) + deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(queries) - // Create WebSocket connection manager for testing - connManager := wsync.NewConnectionManager() + // Create WebSocket connection manager + connManager := sync.NewConnectionManager() + connManager.StartCleanupTask() + + // Create sync queue processor + queueProcessor := sync.NewSyncQueueProcessor(queries) + go queueProcessor.Start(context.Background()) + + koreaderHandler := handlers.NewKOReaderHandler(queries, connManager, queueProcessor) + wsHandler := handlers.NewWSHandler(queries, connManager, cfg.JWTSecret, deviceAuthMiddleware) + conflictHandler := handlers.NewConflictHandler(queries, connManager) + analyticsHandler := handlers.NewAnalyticsHandler(queries) + queueHandler := handlers.NewQueueHandler(queries, queueProcessor) + + // Create conversion service for OPDS + conversionService := services.NewConversionService(queries, "/var/bookhoard/cache/kepub") + opdsHandler := handlers.NewOPDSHandler(queries, conversionService) // Create Echo instance e := echo.New() + // Set up validator + v := validator.New() + if err := ratelimit.RegisterPasswordValidation(v); err != nil { + t.Fatal("Failed to register password validator:", err) + } + e.Validator = &CustomValidator{validator: v} + // Middleware e.Use(echomiddleware.Logger()) e.Use(echomiddleware.Recover()) e.Use(echomiddleware.CORS()) - // Setup routes + // Setup routes using router package + routerConfig := &router.Config{ + Echo: e, + Queries: queries, + Cfg: cfg, + DBPool: dbPool, + AuthHandler: authHandler, + LibraryHandler: libraryHandler, + DeviceHandler: deviceHandler, + KOReaderHandler: koreaderHandler, + WSHandler: wsHandler, + ConflictHandler: conflictHandler, + AnalyticsHandler: analyticsHandler, + QueueHandler: queueHandler, + CollectionHandler: nil, // Not needed for tests + OPDSHandler: opdsHandler, + ConnManager: connManager, + QueueProcessor: queueProcessor, + DeviceAuthMiddleware: deviceAuthMiddleware, + LoginTracker: loginAttemptTracker, + } + + router.RegisterRoutes(routerConfig) + + // Setup ebook handler routes (for testing) protected := e.Group("/api") h := handlers.SetupRoutes(protected, queries, connManager) - // Device management routes (public - for registration) - e.POST("/api/devices/register", deviceHandler.InitiateRegistration) - e.POST("/api/devices/register/status", deviceHandler.CheckRegistrationStatus) - - // Device management routes (protected - require user auth) - devices := protected.Group("/devices") - devices.GET("", deviceHandler.ListDevices) - devices.GET("/:id", deviceHandler.GetDevice) - devices.PUT("/:id", deviceHandler.UpdateDevice) - devices.DELETE("/:id", deviceHandler.DeleteDevice) - devices.GET("/pending", deviceHandler.ListPendingRegistrations) - devices.GET("/approve/:registration_id", deviceHandler.ApproveDevice) - devices.POST("/reject/:registration_id", deviceHandler.RejectDevice) - - // Auth routes (public - for testing) - e.POST("/api/auth/register", authHandler.Register) - e.POST("/api/auth/login", authHandler.Login) - // Create test server ts := httptest.NewServer(e) @@ -160,7 +203,7 @@ func loginTestUser(t *testing.T, ts *httptest.Server, db *database.Queries) stri loginRequest := map[string]interface{}{ "login": "testuser@example.com", - "password": "Test@Pass123!", + "password": "TestPass123!", } body, _ := json.Marshal(loginRequest) @@ -195,9 +238,9 @@ func getTestUserID(t *testing.T, db *database.Queries) uuid.UUID { } // If user doesn't exist, create one with a valid password - // Password: "TestPass123!" meets complexity requirements - // This is the bcrypt hash for "TestPass123!" - passwordHash := "$2a$10$rKvZ.HZx3lLJ6IQCpH1lOukQ/xU8j5cH8mYhPY5YGfXllq5hG8y0Ou" + // Password: "Test@Pass123!" meets complexity requirements + // This is the bcrypt hash for "Test@Pass123!" + passwordHash := "$2a$10$vYI7j2zvH3vBmGHXqKbqMe.8hKqJVYOvQKHh8fPJWGjVPKpXzGvMqG" newUser, err := db.CreateUser(context.Background(), database.CreateUserParams{ Email: "testuser@example.com",