chore: import upstream snapshot with attribution
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:30:11 +08:00
commit 70bf21e064
5213 changed files with 731895 additions and 0 deletions
@@ -0,0 +1,63 @@
import { mkdtempSync } from "node:fs";
import { writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it } from "vitest";
import { createAssetsIgnoreFunction, getContentType } from "../helpers";
describe("assets", () => {
const tmpDir = mkdtempSync(join(tmpdir(), "wrangler-tests"));
describe(".assetsignore", () => {
it("should ignore metafiles by default", async ({ expect }) => {
const { assetsIgnoreFunction } = await createAssetsIgnoreFunction(tmpDir);
expect(assetsIgnoreFunction(".assetsignore")).toBeTruthy();
expect(assetsIgnoreFunction("_redirects")).toBeTruthy();
expect(assetsIgnoreFunction("_headers")).toBeTruthy();
// don't ignore metafiles in child directories
expect(assetsIgnoreFunction(join("child", ".assetsignore"))).toBeFalsy();
expect(assetsIgnoreFunction(join("child", "_redirects"))).toBeFalsy();
expect(assetsIgnoreFunction(join("child", "_headers"))).toBeFalsy();
});
it("should allow users to force opt-in metafiles", async ({ expect }) => {
await writeFile(
join(tmpDir, "./.assetsignore"),
"!.assetsignore\n!_redirects\n!_headers"
);
const { assetsIgnoreFunction } = await createAssetsIgnoreFunction(tmpDir);
expect(assetsIgnoreFunction(".assetsignore")).toBeFalsy();
expect(assetsIgnoreFunction("_redirects")).toBeFalsy();
expect(assetsIgnoreFunction("_headers")).toBeFalsy();
});
it("should allow users to ignore files", async ({ expect }) => {
await writeFile(
join(tmpDir, "./.assetsignore"),
"logo.png\nchild/**/*.svg\n!child/nope.svg\n/*.js"
);
const { assetsIgnoreFunction } = await createAssetsIgnoreFunction(tmpDir);
expect(assetsIgnoreFunction("abc")).toBeFalsy();
expect(assetsIgnoreFunction("logo.png")).toBeTruthy();
expect(assetsIgnoreFunction(join("child", "logo.png"))).toBeTruthy();
expect(assetsIgnoreFunction("foo.js")).toBeTruthy();
expect(assetsIgnoreFunction(join("child", "foo.js"))).toBeFalsy();
expect(assetsIgnoreFunction(join("child", "yup.svg"))).toBeTruthy();
expect(
assetsIgnoreFunction(join("child", "a", "b", "c", "yup.svg"))
).toBeTruthy();
expect(assetsIgnoreFunction(join("child", "nope.svg"))).toBeFalsy();
});
});
});
describe("getContentType", () => {
it("should return 'text/javascript", ({ expect }) => {
const contentType = getContentType("/_astro/sponsors.CIiPz7eJ.js");
expect(contentType).toBe("text/javascript; charset=utf-8");
});
});
@@ -0,0 +1,304 @@
import { test } from "vitest";
import { parseHeaders } from "../configuration/parseHeaders";
test("parseHeaders should reject malformed initial lines", ({ expect }) => {
const input = `
# A single token before a path
c
# A header before a path
Access-Control-Allow-Origin: *
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [],
invalid: [
{
line: "c",
lineNumber: 3,
message: "Expected a path beginning with at least one forward-slash",
},
{
line: "Access-Control-Allow-Origin: *",
lineNumber: 5,
message:
"Path should come before header (access-control-allow-origin: *)",
},
],
});
});
test("parseHeaders should reject invalid headers", ({ expect }) => {
const input = `
# Valid header sails through
/a
Name: Value
#
/b
I'm invalid!
!x-content-type
But: I'm okay!
! Content-Type: application/json
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] },
{ path: "/b", headers: { but: "I'm okay!" }, unsetHeaders: [] },
],
invalid: [
{
line: `I'm invalid!`,
lineNumber: 7,
message: "Expected a colon-separated header pair (e.g. name: value)",
},
{
line: "!x-content-type",
lineNumber: 8,
message: "Expected a colon-separated header pair (e.g. name: value)",
},
{
line: `! Content-Type: application/json`,
lineNumber: 10,
message: "Header name cannot include spaces",
},
],
});
});
test("parseHeaders should reject lines longer than 2000 chars", ({
expect,
}) => {
const huge_line = `${Array(1001).fill("a").join("")}: ${Array(1001)
.fill("b")
.join("")}`;
const input = `
# Valid entry
/a
Name: Value
# Jumbo comment line OK, ignored as normal
${Array(1001).fill("#").join("")}
# Huge path names rejected
/b
Name: Value
${huge_line}
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] },
{ path: "/b", headers: { name: "Value" }, unsetHeaders: [] },
],
invalid: [
{
message: `Ignoring line 10 as it exceeds the maximum allowed length of 2000.`,
},
],
});
});
test("parseHeaders should reject any rules after the first 100", ({
expect,
}) => {
const input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(150)
.fill(undefined)
.map((_, i) => `/a/${i}\nx-index: ${i}`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
expect(parseHeaders(input)).toEqual({
rules: Array(101)
.fill(undefined)
.map((_, i) => ({
path: "/a/" + i,
headers: { "x-index": `${i}` },
unsetHeaders: [],
})),
invalid: [
{
message: `Maximum number of rules supported is 100. Skipping remaining 100 lines of file.`,
},
],
});
});
test("parseHeaders should reject paths with multiple wildcards", ({
expect,
}) => {
const input = `
# Multiple wildcards in an absolute URL
https://*.pages.dev/*
x-custom: value
# Multiple wildcards in a relative path
/blog/*/posts/*
x-custom: value
# Single wildcard is fine
https://*.pages.dev/
x-custom: value
/blog/*
x-custom: value
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{
path: "https://*.pages.dev/",
headers: { "x-custom": "value" },
unsetHeaders: [],
},
{
path: "/blog/*",
headers: { "x-custom": "value" },
unsetHeaders: [],
},
],
invalid: [
{
line: "https://*.pages.dev/*",
lineNumber: 3,
message:
"Only one wildcard is allowed per rule. Use a named placeholder (e.g. :project) instead. Skipping https://*.pages.dev/*.",
},
{
line: "/blog/*/posts/*",
lineNumber: 6,
message:
"Only one wildcard is allowed per rule. Use a named placeholder (e.g. :project) instead. Skipping /blog/*/posts/*.",
},
],
});
});
test("parseHeaders should reject paths combining wildcard with :splat placeholder", ({
expect,
}) => {
const input = `
# Wildcard + :splat in an absolute URL
https://*.pages.dev/:splat
x-custom: value
# Wildcard + :splat in a relative path
/blog/*/:splat
x-custom: value
# Just :splat without wildcard is fine
/blog/:splat
x-custom: value
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{
path: "/blog/:splat",
headers: { "x-custom": "value" },
unsetHeaders: [],
},
],
invalid: [
{
line: "https://*.pages.dev/:splat",
lineNumber: 3,
message:
"Cannot combine a wildcard * with a :splat placeholder because wildcards are converted to :splat at runtime. Skipping https://*.pages.dev/:splat.",
},
{
line: "/blog/*/:splat",
lineNumber: 6,
message:
"Cannot combine a wildcard * with a :splat placeholder because wildcards are converted to :splat at runtime. Skipping /blog/*/:splat.",
},
],
});
});
test("parseHeaders should reject malformed URLs", ({ expect }) => {
const input = `
# Spaces should be URI encoded
/some page with spaces
valid: yup
# OK with URL escaped encoding
/some%20page
valid: yup
# Unescaped URLs are handled OK by Deno, so escape & pass them through
/://so;\`me
valid: yup
/nons:/&@%+~{}ense
valid: yup
# Absolute URLs with a non-https protocol should be rejected
https://yeah.com
valid: yup
http://nah.com/blog
invalid: things
# Absolute URLs with a port should be rejected
https://nah.com:8080
invalid: also
https://nah.com:8080/blog
invalid: 2
//yeah.com/blog
valid: things
/yeah
valid: yup
/yeah.com
valid: yup
# Anything standalone is interpreted as a invalid header pair
nah.com
:
test:
`;
const result = parseHeaders(input);
expect(result).toEqual({
invalid: [
{
line: "http://nah.com/blog",
lineNumber: 16,
message:
'URLs should either be relative (e.g. begin with a forward-slash), or use HTTPS (e.g. begin with "https://").',
},
{
line: "https://nah.com:8080",
lineNumber: 19,
message:
"Specifying ports is not supported. Skipping absolute URL https://nah.com:8080.",
},
{
line: "https://nah.com:8080/blog",
lineNumber: 21,
message:
"Specifying ports is not supported. Skipping absolute URL https://nah.com:8080/blog.",
},
{
line: "nah.com",
lineNumber: 30,
message: "Expected a colon-separated header pair (e.g. name: value)",
},
{ line: ":", lineNumber: 31, message: "No header name specified" },
{ line: "test:", lineNumber: 32, message: "No header value specified" },
],
rules: [
{
path: "/some%20page%20with%20spaces",
headers: { valid: "yup" },
unsetHeaders: [],
},
{ path: "/some%20page", headers: { valid: "yup" }, unsetHeaders: [] },
{ path: "/://so;%60me", headers: { valid: "yup" }, unsetHeaders: [] },
{
path: "/nons:/&@%+~%7B%7Dense",
headers: { valid: "yup" },
unsetHeaders: [],
},
{
path: "https://yeah.com/",
headers: { valid: "yup" },
unsetHeaders: [],
},
{
path: "//yeah.com/blog",
headers: { valid: "things" },
unsetHeaders: [],
},
{ path: "/yeah", headers: { valid: "yup" }, unsetHeaders: [] },
{ path: "/yeah.com", headers: { valid: "yup" }, unsetHeaders: [] },
],
});
});
@@ -0,0 +1,178 @@
import { test } from "vitest";
import { parseHeaders } from "../configuration/parseHeaders";
test("parseHeaders should handle a single rule", ({ expect }) => {
const input = `/a
Name: Value`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] }],
invalid: [],
});
});
test("parseHeaders should handle headers with exclamation marks", ({
expect,
}) => {
const input = `/a
!Name: Value`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [{ path: "/a", headers: { "!name": "Value" }, unsetHeaders: [] }],
invalid: [],
});
});
test("parseHeaders should ignore blank lines", ({ expect }) => {
const input = `
/a
Name: Value
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] }],
invalid: [],
});
});
test("parseHeaders should trim whitespace", ({ expect }) => {
const input = `
/a
Name : Value
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] }],
invalid: [],
});
});
test("parseHeaders should ignore comments", ({ expect }) => {
const input = `
# This is a comment
/a
# And one here too.
Name: Value
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] }],
invalid: [],
});
});
test("parseHeaders should combine headers together", ({ expect }) => {
const input = `
/a
Set-Cookie: test=cookie; expires=never
Set-Cookie: another=cookie; magic!
/b
A: ABBA
B: BABA
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{
path: "/a",
headers: {
"set-cookie": "test=cookie; expires=never, another=cookie; magic!",
},
unsetHeaders: [],
},
{
path: "/b",
headers: { a: "ABBA", b: "BABA" },
unsetHeaders: [],
},
],
invalid: [],
});
});
test("parseHeaders should support setting hosts", ({ expect }) => {
const input = `
https://example.com
a: a
https://example.com/
b: b
https://example.com/blog
c: c
/blog
d:d
https://:subdomain.example.*/path
e:e
`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{ path: "https://example.com/", headers: { a: "a" }, unsetHeaders: [] },
{ path: "https://example.com/", headers: { b: "b" }, unsetHeaders: [] },
{
path: "https://example.com/blog",
headers: { c: "c" },
unsetHeaders: [],
},
{ path: "/blog", headers: { d: "d" }, unsetHeaders: [] },
{
path: "https://:subdomain.example.*/path",
headers: { e: "e" },
unsetHeaders: [],
},
],
invalid: [],
});
});
test("parseHeaders should add unset headers", ({ expect }) => {
const input = `/a
Name: Value
! Place`;
const result = parseHeaders(input);
expect(result).toEqual({
rules: [
{ path: "/a", headers: { name: "Value" }, unsetHeaders: ["Place"] },
],
invalid: [],
});
});
test("parseHeaders should support custom limits", ({ expect }) => {
const aaa = Array(1001).fill("a").join("");
const bbb = Array(1001).fill("b").join("");
const huge_line = `${aaa}: ${bbb}`;
let input = `
# Valid entry
/a
Name: Value
# Jumbo comment line OK, ignored as normal
${Array(1001).fill("#").join("")}
# Huge path names rejected
/b
Name: Value
${huge_line}
`;
let result = parseHeaders(input, { maxLineLength: 3000 });
expect(result).toEqual({
rules: [
{ path: "/a", headers: { name: "Value" }, unsetHeaders: [] },
{ path: "/b", headers: { name: "Value", [aaa]: bbb }, unsetHeaders: [] },
],
invalid: [],
});
input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(150)
.fill(undefined)
.map((_, i) => `/a/${i}\nx-index: ${i}`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
result = parseHeaders(input, { maxRules: 200 });
expect(result.rules.length).toBe(150);
expect(result.invalid.length).toBe(0);
});
@@ -0,0 +1,370 @@
import { test } from "vitest";
import { parseRedirects } from "../configuration/parseRedirects";
// Snapshot values
const maxDynamicRedirectRules = 100;
const maxLineLength = 2000;
const maxStaticRedirectRules = 2000;
test("parseRedirects should reject malformed lines", ({ expect }) => {
const input = `
# Single token
/c
# Four tokens
/d /e 302 !important
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [
{
line: `/c`,
lineNumber: 3,
message: "Expected exactly 2 or 3 whitespace-separated tokens. Got 1.",
},
{
line: `/d /e 302 !important`,
lineNumber: 5,
message: "Expected exactly 2 or 3 whitespace-separated tokens. Got 4.",
},
],
});
});
test("parseRedirects should reject invalid status codes", ({ expect }) => {
const input = `
# Valid token sails through
/a /b 301
# 418 NOT OK
/c /d 418
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 3 }],
invalid: [
{
line: `/c /d 418`,
lineNumber: 5,
message:
"Valid status codes are 200, 301, 302 (default), 303, 307, or 308. Got 418.",
},
],
});
});
test(`parseRedirects should reject duplicate 'from' paths`, ({ expect }) => {
const input = `
# Valid entry
/a /b
# Nonsensical but permitted (for now)
/b /a
# Duplicate 'from'
/a /c
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 302, to: "/b", lineNumber: 3 },
{ from: "/b", status: 302, to: "/a", lineNumber: 5 },
],
invalid: [
{
line: `/a /c`,
lineNumber: 7,
message: `Ignoring duplicate rule for path /a.`,
},
],
});
});
test(`parseRedirects should reject lines longer than ${maxLineLength} chars`, ({
expect,
}) => {
const huge_line = `/${Array(maxLineLength).fill("a").join("")} /${Array(
maxLineLength
)
.fill("b")
.join("")} 301`;
const input = `
# Valid entry
/a /b
# Jumbo comment line OK, ignored as normal
${Array(maxLineLength + 1)
.fill("#")
.join("")}
# Huge path names rejected
${huge_line}
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b", lineNumber: 3 }],
invalid: [
{
message: `Ignoring line 7 as it exceeds the maximum allowed length of ${maxLineLength}.`,
},
],
});
});
test("parseRedirects should reject any dynamic rules after the first 100", ({
expect,
}) => {
const input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(150)
.fill(undefined)
.map((_, i) => `/a/${i}/* /b/${i}/:splat`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
expect(parseRedirects(input)).toEqual({
rules: Array(100)
.fill(undefined)
.map((_, i) => ({
from: `/a/${i}/*`,
to: `/b/${i}/:splat`,
status: 302,
lineNumber: i + 3,
})),
invalid: [
{
message: `Maximum number of dynamic rules supported is 100. Skipping remaining 52 lines of file.`,
},
],
});
});
test(`parseRedirects should reject any static rules after the first ${maxStaticRedirectRules}`, ({
expect,
}) => {
const input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(maxStaticRedirectRules + 50)
.fill(undefined)
.map((_, i) => `/a/${i} /b/${i}`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
expect(parseRedirects(input)).toEqual({
rules: Array(maxStaticRedirectRules)
.fill(undefined)
.map((_, i) => ({
from: `/a/${i}`,
to: `/b/${i}`,
status: 302,
lineNumber: i + 3,
})),
invalid: Array(50)
.fill(undefined)
.map(() => ({
message: `Maximum number of static rules supported is ${maxStaticRedirectRules}. Skipping line.`,
})),
});
});
test("parseRedirects should reject a combination of lots of static and dynamic rules", ({
expect,
}) => {
const input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(maxStaticRedirectRules + 50)
.fill(undefined)
.map((_, i) => `/a/${i} /b/${i}`)
.join("\n")}
${Array(maxDynamicRedirectRules + 50)
.fill(undefined)
.map((_, i) => `/a/${i}/* /b/${i}/:splat`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
expect(parseRedirects(input)).toEqual({
rules: [
...Array(maxStaticRedirectRules)
.fill(undefined)
.map((_, i) => ({
from: `/a/${i}`,
to: `/b/${i}`,
status: 302,
lineNumber: i + 3,
})),
...Array(maxDynamicRedirectRules)
.fill(undefined)
.map((_, i) => ({
from: `/a/${i}/*`,
to: `/b/${i}/:splat`,
status: 302,
lineNumber: i + maxStaticRedirectRules + 53,
})),
],
invalid: [
...Array(50)
.fill(undefined)
.map(() => ({
message: `Maximum number of static rules supported is ${maxStaticRedirectRules}. Skipping line.`,
})),
{
message: `Maximum number of dynamic rules supported is ${maxDynamicRedirectRules}. Skipping remaining 52 lines of file.`,
},
],
});
});
test("parseRedirects should reject malformed URLs", ({ expect }) => {
const input = `
# Spaces rejected on token length
/some page /somewhere else
# OK with URL escaped encoding
/some%20page /somewhere%20else
# Unescaped URLs are handled OK by Deno, so escape & pass them through
/://so;\`me /nons:/&@%+~{}ense
# Absolute URLs aren't OK for 'from', but are fine for 'to'
https://yeah.com https://nah.com
/nah https://yeah.com
# This is actually parsed as /yeah.com, which we might want to detect but is ok for now
yeah.com https://nah.com
`;
const result = parseRedirects(input);
expect(result).toEqual({
invalid: [
{
line: `/some page /somewhere else`,
lineNumber: 3,
message: "Expected exactly 2 or 3 whitespace-separated tokens. Got 4.",
},
{
line: `https://yeah.com https://nah.com`,
lineNumber: 9,
message:
"Only relative URLs are allowed. Skipping absolute URL https://yeah.com.",
},
],
rules: [
{
from: "/some%20page",
status: 302,
to: "/somewhere%20else",
lineNumber: 5,
},
{
from: "/://so;%60me",
status: 302,
to: "/nons:/&@%+~%7B%7Dense",
lineNumber: 7,
},
{ from: "/nah", status: 302, to: "https://yeah.com/", lineNumber: 10 },
{
from: "/yeah.com",
status: 302,
to: "https://nah.com/",
lineNumber: 12,
},
],
});
});
test("parseRedirects should reject non-relative URLs for proxying (200) redirects", ({
expect,
}) => {
const input = `
/a https://example.com/b 200
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [
{
line: `/a https://example.com/b 200`,
lineNumber: 2,
message:
"Proxy (200) redirects can only point to relative paths. Got https://example.com/b",
},
],
});
});
test("parseRedirects should reject wildcard patterns to index", ({
expect,
}) => {
const input = `
/* /index.html 200
/* /index 200
/ /index.html
/ /index
`;
const invalidRedirectError =
"Infinite loop detected in this rule and has been ignored. This will cause a redirect to strip `.html` or `/index` and end up triggering this rule again. Please fix or remove this rule to silence this warning.";
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [
{
line: "/* /index.html 200",
lineNumber: 2,
message: invalidRedirectError,
},
{
line: "/* /index 200",
lineNumber: 3,
message: invalidRedirectError,
},
{
line: "/ /index.html",
lineNumber: 4,
message: invalidRedirectError,
},
{
line: "/ /index",
lineNumber: 5,
message: invalidRedirectError,
},
],
});
});
test("parseRedirects should allow root patterns to index when HTML handling disabled", ({
expect,
}) => {
// This test documents the fix for https://github.com/cloudflare/workers-sdk/issues/11824
// Exact path matches like "/ /index.html" are valid when html_handling is "none"
// because there's no automatic index.html serving, so no infinite loop occurs.
// However, wildcard patterns like "/* /index.html" should still be blocked.
const input = `
/* /index.html 200
/* /index 200
/ /index.html
/ /index
`;
const invalidRedirectError =
"Infinite loop detected in this rule and has been ignored. This will cause a redirect to strip `.html` or `/index` and end up triggering this rule again. Please fix or remove this rule to silence this warning.";
const result = parseRedirects(input, { htmlHandling: "none" });
expect(result).toEqual({
rules: [
{
from: "/",
status: 302,
to: "/index.html",
lineNumber: 4,
},
],
invalid: [
{
line: "/* /index.html 200",
lineNumber: 2,
message: invalidRedirectError,
},
{
line: "/* /index 200",
lineNumber: 3,
message: invalidRedirectError,
},
{
line: "/ /index",
lineNumber: 5,
message: "Ignoring duplicate rule for path /.",
},
],
});
});
@@ -0,0 +1,440 @@
import { test } from "vitest";
import { parseRedirects } from "../configuration/parseRedirects";
test("parseRedirects should handle a single rule", ({ expect }) => {
const input = `/a /b 301`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should ignore blank lines", ({ expect }) => {
const input = `
/a /b 301
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 2 }],
invalid: [],
});
});
test("parseRedirects should trim whitespace", ({ expect }) => {
const input = `
/a /b 301
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 2 }],
invalid: [],
});
});
test("parseRedirects should ignore comments", ({ expect }) => {
const input = `
# This is a comment
/a /b 301
# And one here too.
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 3 }],
invalid: [],
});
});
test("parseRedirects should handle a single comment-only line", ({
expect,
}) => {
const input = `# This is just a comment`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [],
});
});
test("parseRedirects should handle an indented comment-only line", ({
expect,
}) => {
const input = ` # indented comment`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [],
});
});
test("parseRedirects should handle multiple consecutive comment lines", ({
expect,
}) => {
const input = `
# First comment
# Second comment
# Indented comment
/a /b 301
# Comment after rule
# Another comment
/c /d
# Final comment
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 301, to: "/b", lineNumber: 5 },
{ from: "/c", status: 302, to: "/d", lineNumber: 8 },
],
invalid: [],
});
});
test("parseRedirects should handle a file with only comments", ({ expect }) => {
const input = `
# This file has no redirects
# Just comments
# Some indented
# And more comments
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [],
invalid: [],
});
});
test("parseRedirects should default to 302", ({ expect }) => {
const input = `
/a /b 302
/c /d
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 302, to: "/b", lineNumber: 2 },
{ from: "/c", status: 302, to: "/d", lineNumber: 3 },
],
invalid: [],
});
});
test("parseRedirects should preserve querystrings on to", ({ expect }) => {
const input = `
/a /b?query=string 302
/c?this=rejected /d 301
/ext https://some.domain:1234/route?q=string#anchor
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 302, to: "/b?query=string", lineNumber: 2 },
{ from: "/c", status: 301, to: "/d", lineNumber: 3 },
{
from: "/ext",
status: 302,
to: "https://some.domain:1234/route?q=string#anchor",
lineNumber: 4,
},
],
invalid: [],
});
});
test("parseRedirects should preserve fragments", ({ expect }) => {
const input = `
/a /b#blah 302
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b#blah", lineNumber: 2 }],
invalid: [],
});
});
test("parseRedirects should preserve fragments which contain a hash sign", ({
expect,
}) => {
const input = `
/a /b##blah-1 302
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b##blah-1", lineNumber: 2 }],
invalid: [],
});
});
test("parseRedirects should preserve fragments which contain a hash sign and are full URLs", ({
expect,
}) => {
const input = `
/a https://example.com/b##blah-1 302
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{
from: "/a",
status: 302,
to: "https://example.com/b##blah-1",
lineNumber: 2,
},
],
invalid: [],
});
});
test("parseRedirects should accept 200 (proxying) redirects", ({ expect }) => {
const input = `
/a /b 200
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{
from: "/a",
status: 200,
to: "/b",
lineNumber: 2,
},
],
invalid: [],
});
});
test("parseRedirects should accept absolute URLs that end with index.html", ({
expect,
}) => {
const input = `
/foo https://bar.com/index.html 302
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{
from: "/foo",
status: 302,
to: "https://bar.com/index.html",
lineNumber: 2,
},
],
invalid: [],
});
});
test("parseRedirects should accept going to absolute URLs with ports", ({
expect,
}) => {
const input = `
/foo https://bar.com:123/index.html 302
/cat https://cat.com:12345 302
/dog https://dog.com:12345
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{
from: "/foo",
status: 302,
to: "https://bar.com:123/index.html",
lineNumber: 2,
},
{
from: "/cat",
status: 302,
to: "https://cat.com:12345/",
lineNumber: 3,
},
{
from: "/dog",
status: 302,
to: "https://dog.com:12345/",
lineNumber: 4,
},
],
invalid: [],
});
});
test("parseRedirects should accept relative URLs that don't point to .html files", ({
expect,
}) => {
const input = `
/* /foo 200
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{
from: "/*",
status: 200,
to: "/foo",
lineNumber: 2,
},
],
invalid: [],
});
});
test("parseRedirects should support inline comments", ({ expect }) => {
const input = `/a /b 301 # redirect a to b`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support inline comments without status code", ({
expect,
}) => {
const input = `/a /b # redirect with default status`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support inline comments after URL fragments", ({
expect,
}) => {
const input = `/a /b#section 301 # redirect to section`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b#section", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support inline comments without space after hash", ({
expect,
}) => {
const input = `/a /b 301 #no space comment`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 301, to: "/b", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support multiple rules with inline comments", ({
expect,
}) => {
const input = `
/a /b 301 # first rule
/c /d # second rule with default status
# full line comment
/e /f 307 # third rule
`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 301, to: "/b", lineNumber: 2 },
{ from: "/c", status: 302, to: "/d", lineNumber: 3 },
{ from: "/e", status: 307, to: "/f", lineNumber: 5 },
],
invalid: [],
});
});
test("parseRedirects should support inline comments with absolute URLs containing fragments", ({
expect,
}) => {
const input = `/a https://x.com/b#c # comment`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [
{ from: "/a", status: 302, to: "https://x.com/b#c", lineNumber: 1 },
],
invalid: [],
});
});
test("parseRedirects should support empty inline comments (just hash)", ({
expect,
}) => {
const input = `/a /b #`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support inline comments with multiple hashes in URL fragment", ({
expect,
}) => {
const input = `/a /b##anchor # comment`;
const result = parseRedirects(input);
expect(result).toEqual({
rules: [{ from: "/a", status: 302, to: "/b##anchor", lineNumber: 1 }],
invalid: [],
});
});
test("parseRedirects should support custom limits", ({ expect }) => {
const aaa = Array(1001).fill("a").join("");
const bbb = Array(1001).fill("b").join("");
const huge_line = `/${aaa} /${bbb} 301`;
let input = `
# Valid entry
/a /b
# Jumbo comment line OK, ignored as normal
${Array(1001).fill("#").join("")}
# Huge path names rejected
${huge_line}
`;
let result = parseRedirects(input, { maxLineLength: 3000 });
expect(result).toEqual({
rules: [
{ from: "/a", status: 302, to: "/b", lineNumber: 3 },
{ from: `/${aaa}`, status: 301, to: `/${bbb}`, lineNumber: 7 },
],
invalid: [],
});
input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(150)
.fill(undefined)
.map((_, i) => `/a/${i}/* /b/${i}/:splat`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
result = parseRedirects(input, { maxDynamicRules: 200 });
expect(result.rules.length).toBe(150);
expect(result.invalid.length).toBe(0);
input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(2050)
.fill(undefined)
.map((_, i) => `/a/${i} /b/${i}`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
result = parseRedirects(input, { maxStaticRules: 3000 });
expect(result.rules.length).toBe(2050);
expect(result.invalid.length).toBe(0);
input = `
# COMMENTS DON'T COUNT TOWARDS TOTAL VALID RULES
${Array(2050)
.fill(undefined)
.map((_, i) => `/a/${i} /b/${i}`)
.join("\n")}
${Array(150)
.fill(undefined)
.map((_, i) => `/a/${i}/* /b/${i}/:splat`)
.join("\n")}
# BUT DO GET COUNTED AS TOTAL LINES SKIPPED
`;
result = parseRedirects(input, {
maxDynamicRules: 200,
maxStaticRules: 3000,
});
expect(result.rules.length).toBe(2200);
expect(result.invalid.length).toBe(0);
});
@@ -0,0 +1,97 @@
import { describe, it } from "vitest";
import { parseStaticRouting } from "../configuration/parseStaticRouting";
describe("parseStaticRouting", () => {
it("throws when given empty rules", ({ expect }) => {
expect(() => parseStaticRouting([])).toThrowErrorMatchingInlineSnapshot(
`[Error: No \`run_worker_first\` rules were provided; must provide at least 1 rule.]`
);
});
it("throws when given only negative rules", ({ expect }) => {
expect(() =>
parseStaticRouting(["!/assets"])
).toThrowErrorMatchingInlineSnapshot(
`[Error: Only negative \`run_worker_first\` rules were provided; must provide at least 1 non-negative rule]`
);
});
it("throws when too many rules are provided", ({ expect }) => {
const rules = Array.from({ length: 120 }, (_, i) => `/rule/${i}`);
expect(() => parseStaticRouting(rules)).toThrowErrorMatchingInlineSnapshot(
`[Error: Too many \`run_worker_first\` rules were provided; 120 rules provided exceeds max of 100.]`
);
const userWorkerRules = Array.from({ length: 60 }, (_, i) => `/rule/${i}`);
const assetRules = Array.from({ length: 60 }, (_, i) => `!/rule/${60 + i}`);
expect(() =>
parseStaticRouting([...userWorkerRules, ...assetRules])
).toThrowErrorMatchingInlineSnapshot(
`[Error: Too many \`run_worker_first\` rules were provided; 120 rules provided exceeds max of 100.]`
);
});
it("throws when a rule is too long", ({ expect }) => {
const rule = `/api/${"a".repeat(130)}`;
expect(() => parseStaticRouting([rule])).toThrowErrorMatchingInlineSnapshot(
`
[Error: Invalid routes in \`run_worker_first\`:
'/api/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': all rules must be less than 100 characters in length]
`
);
});
it("throws when rule doesn't begin with /", ({ expect }) => {
expect(() => parseStaticRouting(["api/*", "!asset"]))
.toThrowErrorMatchingInlineSnapshot(`
[Error: Invalid routes in \`run_worker_first\`:
'api/*': rules must start with '/' or '!/'
'!asset': negative rules must start with '!/']
`);
});
it("throws when given redundant rules", ({ expect }) => {
expect(() =>
parseStaticRouting([
"/api/*",
"/oauth/callback",
"/api/some/route",
"!/api/assets/*",
])
).toThrowErrorMatchingInlineSnapshot(
`
[Error: Invalid routes in \`run_worker_first\`:
'/api/some/route': rule '/api/*' makes it redundant]
`
);
});
it("throws when given duplicate routes", ({ expect }) => {
expect(() =>
parseStaticRouting([
"/api/some/route",
"/oauth/callback",
"/api/some/route",
"!/api/assets/*",
])
).toThrowErrorMatchingInlineSnapshot(
`
[Error: Invalid routes in \`run_worker_first\`:
'/api/some/route': rule is a duplicate; rules must be unique]
`
);
});
it("correctly parses valid rules", ({ expect }) => {
const parsed = parseStaticRouting([
"/api/*",
"/oauth/callback",
"!/api/assets/*",
]);
const expected = {
user_worker: ["/api/*", "/oauth/callback"],
asset_worker: ["/api/assets/*"],
};
expect(parsed).toEqual(expected);
});
});