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
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:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user