package api import ( "net/http" "testing" "time" "github.com/gofiber/fiber/v2" "wucher/internal/config" "wucher/internal/service" "wucher/internal/transport/http/handlers" ) func TestForgotPasswordIPLimiter(t *testing.T) { cfg := config.AuthConfig{ ForgotPasswordIPRateMax: 1, ForgotPasswordIPRateWindow: time.Minute, } svc := service.NewAuthService(nil, nil, nil, nil, service.AuthServiceDependencies{SSO: nil, Protector: nil, Config: cfg}) app := fiber.New() app.Post("/forgot", forgotPasswordIPLimiter(svc), func(c *fiber.Ctx) error { return c.SendStatus(http.StatusOK) }) req1, _ := http.NewRequest(http.MethodPost, "/forgot", nil) resp1, err := app.Test(req1) if err != nil { t.Fatalf("first request failed: %v", err) } if resp1.StatusCode != http.StatusOK { t.Fatalf("expected first request 200, got %d", resp1.StatusCode) } req2, _ := http.NewRequest(http.MethodPost, "/forgot", nil) resp2, err := app.Test(req2) if err != nil { t.Fatalf("second request failed: %v", err) } if resp2.StatusCode != http.StatusTooManyRequests { t.Fatalf("expected second request 429, got %d", resp2.StatusCode) } } func TestLoginIPLimiter(t *testing.T) { cfg := config.AuthConfig{ LoginIPRateMax: 1, LoginIPRateWindow: time.Minute, } svc := service.NewAuthService(nil, nil, nil, nil, service.AuthServiceDependencies{SSO: nil, Protector: nil, Config: cfg}) app := fiber.New() app.Post("/login", loginIPLimiter(svc), func(c *fiber.Ctx) error { return c.SendStatus(http.StatusOK) }) req1, _ := http.NewRequest(http.MethodPost, "/login", nil) resp1, err := app.Test(req1) if err != nil { t.Fatalf("first request failed: %v", err) } if resp1.StatusCode != http.StatusOK { t.Fatalf("expected first request 200, got %d", resp1.StatusCode) } req2, _ := http.NewRequest(http.MethodPost, "/login", nil) resp2, err := app.Test(req2) if err != nil { t.Fatalf("second request failed: %v", err) } if resp2.StatusCode != http.StatusTooManyRequests { t.Fatalf("expected second request 429, got %d", resp2.StatusCode) } } func TestRegisterAuthRoutes(t *testing.T) { app := fiber.New() v1 := app.Group("/api/v1") svc := service.NewAuthService(nil, nil, nil, nil, service.AuthServiceDependencies{SSO: nil, Protector: nil, Config: config.AuthConfig{}}) authHandler := handlers.NewAuthHandler(svc) registerAuthRoutes(v1, authHandler, svc, nil) routes := app.GetRoutes(true) required := []struct { method string path string }{ {method: http.MethodPost, path: "/api/v1/auth/register"}, {method: http.MethodPost, path: "/api/v1/auth/invite"}, {method: http.MethodPost, path: "/api/v1/auth/invite/resend-set-password"}, {method: http.MethodPost, path: "/api/v1/auth/forgot-password"}, {method: http.MethodPost, path: "/api/v1/auth/reset-password"}, {method: http.MethodPost, path: "/api/v1/auth/pin/setup"}, {method: http.MethodPost, path: "/api/v1/auth/pin/verify"}, {method: http.MethodPost, path: "/api/v1/auth/pin/change"}, {method: http.MethodPost, path: "/api/v1/auth/pin/request-reset"}, {method: http.MethodPost, path: "/api/v1/auth/pin/forgot"}, {method: http.MethodPost, path: "/api/v1/auth/pin/reset"}, {method: http.MethodPost, path: "/api/v1/auth/login"}, {method: http.MethodPost, path: "/api/v1/auth/exchange"}, {method: http.MethodPost, path: "/api/v1/auth/webauthn/register/begin"}, {method: http.MethodPost, path: "/api/v1/auth/webauthn/register/finish"}, {method: http.MethodPost, path: "/api/v1/auth/webauthn/login/begin"}, {method: http.MethodPost, path: "/api/v1/auth/webauthn/login/finish"}, {method: http.MethodPost, path: "/api/v1/auth/webauthn/reset-setup"}, {method: http.MethodPost, path: "/api/v1/auth/refresh"}, {method: http.MethodGet, path: "/api/v1/auth/sessions"}, {method: http.MethodDelete, path: "/api/v1/auth/sessions/:session_id"}, {method: http.MethodGet, path: "/api/v1/auth/me"}, {method: http.MethodGet, path: "/api/v1/auth/microsoft/login"}, {method: http.MethodGet, path: "/api/v1/auth/microsoft/login-check"}, {method: http.MethodGet, path: "/api/v1/auth/microsoft/link"}, {method: http.MethodPost, path: "/api/v1/auth/sso/link"}, {method: http.MethodDelete, path: "/api/v1/auth/sso/unlink"}, {method: http.MethodPost, path: "/api/v1/auth/totp/verify"}, {method: http.MethodPost, path: "/api/v1/auth/totp/reset-setup"}, } for _, tc := range required { if !hasRoute(routes, tc.method, tc.path) { t.Fatalf("route %s %s not registered", tc.method, tc.path) } } var forgotRoute *fiber.Route for i := range routes { if routes[i].Method == http.MethodPost && routes[i].Path == "/api/v1/auth/forgot-password" { forgotRoute = &routes[i] break } } if forgotRoute == nil { t.Fatalf("forgot password route not found") } if len(forgotRoute.Handlers) < 2 { t.Fatalf("expected forgot password route to have limiter and handler, got %d handlers", len(forgotRoute.Handlers)) } var loginRoute *fiber.Route for i := range routes { if routes[i].Method == http.MethodPost && routes[i].Path == "/api/v1/auth/login" { loginRoute = &routes[i] break } } if loginRoute == nil { t.Fatalf("login route not found") } if len(loginRoute.Handlers) < 2 { t.Fatalf("expected login route to have limiter and handler, got %d handlers", len(loginRoute.Handlers)) } }