diff --git a/nitro.config.ts b/nitro.config.ts index 805474f..0d9f401 100644 --- a/nitro.config.ts +++ b/nitro.config.ts @@ -4,4 +4,22 @@ export default defineConfig({ serverDir: "./server", baseURL: "/base", publicAssets: [{ baseURL: "/_dist", dir: "./public/_dist", maxAge: 60 * 60 * 24 * 365 }], + routeRules: { + "/tests/headers": { + headers: { + "x-nitro-test": "hello", + "x-nitro.test-2": "dotted", + "x-nitro-spaced": "hello world from nitro", + "x-nitro-long": "a".repeat(1024), + "x-nitro-special": "a!#$%&'*+-.^_`|~b", + "x-nitro-num": "42", + }, + }, + "/redirect-source": { + redirect: "/base/redirect-target", + }, + "/basic-auth-protected": { + basicAuth: { username: "admin", password: "nitrorunseverywhere" }, + }, + }, }); diff --git a/server/routes/basic-auth-protected.ts b/server/routes/basic-auth-protected.ts new file mode 100644 index 0000000..57fbbf9 --- /dev/null +++ b/server/routes/basic-auth-protected.ts @@ -0,0 +1,5 @@ +import { defineEventHandler } from "nitro/h3"; + +export default defineEventHandler((event) => { + return event.context.basicAuth?.username ?? "no-auth"; +}); diff --git a/server/routes/index.ts b/server/routes/index.ts index 1dd7e97..1029325 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -6,7 +6,16 @@ const baseURL = "/base/"; const withBase = (p: string) => baseURL + p.replace(/^\//, ""); -const tests = ["api", "form-data", "multipart-form-data", "sourcemap"]; +const tests = [ + "api", + "basic-auth", + "form-data", + "headers", + "meta", + "multipart-form-data", + "redirect", + "sourcemap", +]; const manualTests = ["env", "node-compat", "headers"]; diff --git a/server/routes/redirect-target.ts b/server/routes/redirect-target.ts new file mode 100644 index 0000000..b462e5b --- /dev/null +++ b/server/routes/redirect-target.ts @@ -0,0 +1,3 @@ +import { defineEventHandler } from "nitro/h3"; + +export default defineEventHandler(() => "REDIRECTED"); diff --git a/server/routes/tests/basic-auth.ts b/server/routes/tests/basic-auth.ts new file mode 100644 index 0000000..c5d3314 --- /dev/null +++ b/server/routes/tests/basic-auth.ts @@ -0,0 +1,18 @@ +import { defineTestHandler } from "../../utils/test"; + +// Route rule in nitro.config.ts protects /base/basic-auth-protected with basic auth. +// The handler echoes `event.context.basicAuth.username`, which is only populated +// when the basicAuth route rule runs successfully. We avoid triggering a 401 +// response here because browsers show a native auth popup on 401 + WWW-Authenticate. +export default defineTestHandler( + "basic-auth", + () => "test-page", + async ({ assert }) => { + const res = await fetch("/base/basic-auth-protected", { + headers: { Authorization: "Basic " + btoa("admin:nitrorunseverywhere") }, + }); + assert(res.status === 200, `Expected 200 with valid credentials, got: ${res.status}`); + const text = await res.text(); + assert(text === "admin", `Expected body "admin" (echoed from basicAuth context), got: ${text}`); + }, +); diff --git a/server/routes/tests/headers.ts b/server/routes/tests/headers.ts new file mode 100644 index 0000000..da27e2f --- /dev/null +++ b/server/routes/tests/headers.ts @@ -0,0 +1,38 @@ +import { defineTestHandler } from "../../utils/test"; + +// Route rule in nitro.config.ts sets multiple response headers on this path +// to exercise name/value edge cases that should work across all providers. +export default defineTestHandler( + "headers", + () => "OK", + async ({ assert, log }) => { + const res = await fetch(""); + + const cases: Array<[string, string]> = [ + // Baseline ASCII + ["x-nitro-test", "hello"], + // Dots, dashes, digits in the name + ["x-nitro.test-2", "dotted"], + // Internal spaces in value (legal per RFC 9110) + ["x-nitro-spaced", "hello world from nitro"], + // Long value (1 KB) + ["x-nitro-long", "a".repeat(1024)], + // Special-but-legal tchars in value + ["x-nitro-special", "a!#$%&'*+-.^_`|~b"], + // Numeric-like value + ["x-nitro-num", "42"], + ]; + + for (const [name, expected] of cases) { + const actual = res.headers.get(name); + log(`${name}: ${actual}`); + assert(actual === expected, `Expected ${name}=${expected}, got: ${actual}`); + } + + // Case-insensitive lookup must work regardless of how the runtime preserves case. + assert( + res.headers.get("X-NITRO-TEST") === "hello", + "Expected case-insensitive header lookup to work", + ); + }, +); diff --git a/server/routes/tests/meta.ts b/server/routes/tests/meta.ts new file mode 100644 index 0000000..5874264 --- /dev/null +++ b/server/routes/tests/meta.ts @@ -0,0 +1,16 @@ +import { version } from "nitro/meta"; +import { defineTestHandler } from "../../utils/test"; + +export default defineTestHandler( + "meta", + () => ({ version }), + async ({ assert, log }) => { + const res = await fetch("").then((r) => r.json()); + log(`Nitro version: ${res.version}`); + assert( + typeof res.version === "string" && res.version.length > 0, + `Expected non-empty version string, got: ${res.version}`, + ); + assert(/^\d+\.\d+\.\d+/.test(res.version), `Expected semver-like version, got: ${res.version}`); + }, +); diff --git a/server/routes/tests/redirect.ts b/server/routes/tests/redirect.ts new file mode 100644 index 0000000..b282474 --- /dev/null +++ b/server/routes/tests/redirect.ts @@ -0,0 +1,13 @@ +import { defineTestHandler } from "../../utils/test"; + +// Route rule in nitro.config.ts redirects /base/redirect-source to /base/redirect-target. +export default defineTestHandler( + "redirect", + () => "test-page", + async ({ assert }) => { + const res = await fetch("/base/redirect-source"); + const text = await res.text(); + assert(res.redirected === true, `Expected redirected=true, got: ${res.redirected}`); + assert(text === "REDIRECTED", `Expected body "REDIRECTED", got: ${text}`); + }, +);