1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3import { describe, it } from "vitest";
4import { MAX_ROUTES_RULES } from "../configuration/constants.ts";
5import { parseStaticRouting } from "../configuration/parseStaticRouting.ts";
6
7describe("parseStaticRouting", () => {
8 it("throws when given empty rules", ({ expect }) => {
9 expect(() => parseStaticRouting([])).toThrowErrorMatchingInlineSnapshot(
10 `[Error: No \`run_worker_first\` rules were provided; must provide at least 1 rule.]`,
11 );
12 });
13
14 it("throws when given only negative rules", ({ expect }) => {
15 expect(() =>
16 parseStaticRouting(["!/assets"]),
17 ).toThrowErrorMatchingInlineSnapshot(
18 `[Error: Only negative \`run_worker_first\` rules were provided; must provide at least 1 non-negative rule]`,
19 );
20 });
21
22 it("throws when too many rules are provided", ({ expect }) => {
23 const rules = Array.from({ length: 120 }, (_, i) => `/rule/${i}`);
24 expect(() => parseStaticRouting(rules)).toThrowErrorMatchingInlineSnapshot(
25 `[Error: Too many \`run_worker_first\` rules were provided; 120 rules provided exceeds max of 100.]`,
26 );
27
28 const userWorkerRules = Array.from({ length: 60 }, (_, i) => `/rule/${i}`);
29 const assetRules = Array.from({ length: 60 }, (_, i) => `!/rule/${60 + i}`);
30 expect(() =>
31 parseStaticRouting([...userWorkerRules, ...assetRules]),
32 ).toThrowErrorMatchingInlineSnapshot(
33 `[Error: Too many \`run_worker_first\` rules were provided; 120 rules provided exceeds max of 100.]`,
34 );
35
36 const rulesWithRemovableDuplicates = [
37 ...Array.from({ length: 99 }, (_, i) => `/rule/${i}`),
38 ...Array.from({ length: 5 }, () => "/rule/0"),
39 ];
40 expect(() => parseStaticRouting(rulesWithRemovableDuplicates))
41 .toThrowErrorMatchingInlineSnapshot(`
42 [Error: Too many \`run_worker_first\` rules were provided; 104 rules provided (99 distinct, 5 duplicate entries) exceeds max of 100. Note: duplicate entries count towards the route limit. Ensure that no duplicate rules are present in your \`run_worker_first\` configuration.
43
44 The duplicated rules found are:
45 - "/rule/0"]
46 `);
47
48 const rulesWithRemainingExcess = [
49 ...Array.from({ length: 100 }, (_, i) => `/rule/${i}`),
50 "/api/*",
51 "!/assets/*",
52 "!/assets/*",
53 "/api/*",
54 ];
55 expect(() => parseStaticRouting(rulesWithRemainingExcess))
56 .toThrowErrorMatchingInlineSnapshot(`
57 [Error: Too many \`run_worker_first\` rules were provided; 104 rules provided (102 distinct, 2 duplicate entries) exceeds max of 100. Note: duplicate entries count towards the route limit. Ensure that no duplicate rules are present in your \`run_worker_first\` configuration.
58
59 The duplicated rules found are:
60 - "/api/*"
61 - "!/assets/*"]
62 `);
63
64 const duplicatedRules = Array.from(
65 { length: MAX_ROUTES_RULES + 1 },
66 (_, i) => `/duplicated/${i}`,
67 );
68 expect(() => parseStaticRouting([...duplicatedRules, ...duplicatedRules]))
69 .toThrowErrorMatchingInlineSnapshot(`
70 [Error: Too many \`run_worker_first\` rules were provided; 202 rules provided (101 distinct, 101 duplicate entries) exceeds max of 100. Note: duplicate entries count towards the route limit. Ensure that no duplicate rules are present in your \`run_worker_first\` configuration.
71
72 The duplicated rules found are:
73 - "/duplicated/0"
74 - "/duplicated/1"
75 - "/duplicated/2"
76 - "/duplicated/3"
77 - "/duplicated/4"
78 ...and 96 more duplicated rules.]
79 `);
80 });
81
82 it("throws when a rule is too long", ({ expect }) => {
83 const rule = `/api/${"a".repeat(130)}`;
84 expect(() => parseStaticRouting([rule])).toThrowErrorMatchingInlineSnapshot(
85 `
86 [Error: Invalid routes in \`run_worker_first\`:
87 '/api/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': all rules must be less than 100 characters in length]
88 `,
89 );
90 });
91
92 it("throws when rule doesn't begin with /", ({ expect }) => {
93 expect(() => parseStaticRouting(["api/*", "!asset"]))
94 .toThrowErrorMatchingInlineSnapshot(`
95 [Error: Invalid routes in \`run_worker_first\`:
96 'api/*': rules must start with '/' or '!/'
97 '!asset': negative rules must start with '!/']
98 `);
99 });
100
101 it("throws when given redundant rules", ({ expect }) => {
102 expect(() =>
103 parseStaticRouting([
104 "/api/*",
105 "/oauth/callback",
106 "/api/some/route",
107 "!/api/assets/*",
108 ]),
109 ).toThrowErrorMatchingInlineSnapshot(
110 `
111 [Error: Invalid routes in \`run_worker_first\`:
112 '/api/some/route': rule '/api/*' makes it redundant]
113 `,
114 );
115 });
116
117 it("throws when given duplicate routes", ({ expect }) => {
118 expect(() =>
119 parseStaticRouting([
120 "/api/some/route",
121 "/oauth/callback",
122 "/api/some/route",
123 "!/api/assets/*",
124 ]),
125 ).toThrowErrorMatchingInlineSnapshot(
126 `
127 [Error: Invalid routes in \`run_worker_first\`:
128 '/api/some/route': rule is a duplicate; rules must be unique]
129 `,
130 );
131 });
132
133 it("correctly parses valid rules", ({ expect }) => {
134 const parsed = parseStaticRouting([
135 "/api/*",
136 "/oauth/callback",
137 "!/api/assets/*",
138 ]);
139 const expected = {
140 user_worker: ["/api/*", "/oauth/callback"],
141 asset_worker: ["/api/assets/*"],
142 };
143 expect(parsed).toEqual(expected);
144 });
145});