|
| 1 | +// cors_maxage_test.go — pins BUG-API-303 (QA 2026-05-29): the CORS |
| 2 | +// preflight response must carry Access-Control-Max-Age so browsers cache |
| 3 | +// the preflight result instead of re-issuing one before every CORS |
| 4 | +// request. Without this, an SPA making 5 cross-origin API calls fires 5 |
| 5 | +// extra preflight roundtrips. |
| 6 | +// |
| 7 | +// We reconstruct just the fiberCORS middleware exactly as router.New |
| 8 | +// configures it (same allow-origins / methods / headers / max-age) and |
| 9 | +// drive a single OPTIONS preflight through it. The assertion is the live |
| 10 | +// Access-Control-Max-Age header on the response. |
| 11 | + |
| 12 | +package router_test |
| 13 | + |
| 14 | +import ( |
| 15 | + "net/http/httptest" |
| 16 | + "testing" |
| 17 | + |
| 18 | + "github.com/gofiber/fiber/v2" |
| 19 | + fiberCORS "github.com/gofiber/fiber/v2/middleware/cors" |
| 20 | + "github.com/stretchr/testify/assert" |
| 21 | + "github.com/stretchr/testify/require" |
| 22 | +) |
| 23 | + |
| 24 | +// TestCORSPreflight_HasMaxAgeHeader pins BUG-API-303: the fiberCORS |
| 25 | +// preflight response on any cross-origin OPTIONS must carry |
| 26 | +// Access-Control-Max-Age=86400 so browsers (and cooperative proxies) |
| 27 | +// cache the preflight result. |
| 28 | +// |
| 29 | +// Mirrors the production fiberCORS config in router.New verbatim. A |
| 30 | +// future router.New edit that drops MaxAge regresses BUG-API-303 and |
| 31 | +// fails this test. |
| 32 | +func TestCORSPreflight_HasMaxAgeHeader(t *testing.T) { |
| 33 | + const ( |
| 34 | + corsAllowOrigins = "https://instanode.dev,https://www.instanode.dev" |
| 35 | + corsAllowMethods = "GET,POST,PUT,PATCH,DELETE,OPTIONS" |
| 36 | + corsAllowHeaders = "Content-Type,Authorization,X-Request-ID,X-E2E-Test-Token,X-E2E-Source-IP" |
| 37 | + corsMaxAgeSeconds = 86400 |
| 38 | + ) |
| 39 | + |
| 40 | + app := fiber.New() |
| 41 | + app.Use(fiberCORS.New(fiberCORS.Config{ |
| 42 | + AllowOrigins: corsAllowOrigins, |
| 43 | + AllowMethods: corsAllowMethods, |
| 44 | + AllowHeaders: corsAllowHeaders, |
| 45 | + ExposeHeaders: "X-Request-ID,X-Instant-Upgrade,X-Instant-Notice", |
| 46 | + MaxAge: corsMaxAgeSeconds, |
| 47 | + })) |
| 48 | + app.Get("/api/v1/whoami", func(c *fiber.Ctx) error { return c.JSON(fiber.Map{"ok": true}) }) |
| 49 | + |
| 50 | + req := httptest.NewRequest("OPTIONS", "/api/v1/whoami", nil) |
| 51 | + req.Header.Set("Origin", "https://instanode.dev") |
| 52 | + req.Header.Set("Access-Control-Request-Method", "GET") |
| 53 | + req.Header.Set("Access-Control-Request-Headers", "Content-Type") |
| 54 | + |
| 55 | + resp, err := app.Test(req, -1) |
| 56 | + require.NoError(t, err) |
| 57 | + defer resp.Body.Close() |
| 58 | + |
| 59 | + // Status — preflight should 204 (or 200) and emit the CORS-allow set. |
| 60 | + require.True(t, resp.StatusCode == fiber.StatusNoContent || resp.StatusCode == fiber.StatusOK, |
| 61 | + "preflight expected 204/200; got %d", resp.StatusCode) |
| 62 | + |
| 63 | + // BUG-API-303: the Max-Age header is what closes the regression. |
| 64 | + maxAge := resp.Header.Get("Access-Control-Max-Age") |
| 65 | + assert.Equal(t, "86400", maxAge, |
| 66 | + "BUG-API-303: Access-Control-Max-Age must be 86400 (24h) — without it browsers re-preflight every CORS request; got %q", maxAge) |
| 67 | +} |
0 commit comments