Skip to content

Commit 155915e

Browse files
committed
test: dedupe routing and text suites
1 parent 30be04c commit 155915e

10 files changed

Lines changed: 1760 additions & 1345 deletions

src/auto-reply/chunk.test.ts

Lines changed: 418 additions & 253 deletions
Large diffs are not rendered by default.

src/routing/account-id.test.ts

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,59 @@ import {
66
} from "./account-id.js";
77

88
describe("account id normalization", () => {
9-
it("defaults missing values to default account", () => {
10-
expect(normalizeAccountId(undefined)).toBe(DEFAULT_ACCOUNT_ID);
11-
expect(normalizeAccountId(null)).toBe(DEFAULT_ACCOUNT_ID);
12-
expect(normalizeAccountId(" ")).toBe(DEFAULT_ACCOUNT_ID);
13-
});
14-
15-
it("normalizes valid ids to lowercase", () => {
16-
expect(normalizeAccountId(" Business_1 ")).toBe("business_1");
17-
});
9+
const reservedAccountIdCases = [
10+
{ name: "rejects __proto__ pollution keys", input: "__proto__" },
11+
{ name: "rejects constructor pollution keys", input: "constructor" },
12+
{ name: "rejects prototype pollution keys", input: "prototype" },
13+
] as const;
1814

19-
it("sanitizes invalid characters into canonical ids", () => {
20-
expect(normalizeAccountId(" Prod/US East ")).toBe("prod-us-east");
21-
});
15+
function expectNormalizedAccountIdCase(params: {
16+
input: string | null | undefined;
17+
expected: string | undefined;
18+
optional?: boolean;
19+
}) {
20+
const normalize = params.optional ? normalizeOptionalAccountId : normalizeAccountId;
21+
expect(normalize(params.input)).toBe(params.expected);
22+
}
2223

23-
it("rejects prototype-pollution key vectors", () => {
24-
expect(normalizeAccountId("__proto__")).toBe(DEFAULT_ACCOUNT_ID);
25-
expect(normalizeAccountId("constructor")).toBe(DEFAULT_ACCOUNT_ID);
26-
expect(normalizeAccountId("prototype")).toBe(DEFAULT_ACCOUNT_ID);
27-
expect(normalizeOptionalAccountId("__proto__")).toBeUndefined();
28-
expect(normalizeOptionalAccountId("constructor")).toBeUndefined();
29-
expect(normalizeOptionalAccountId("prototype")).toBeUndefined();
24+
it.each([
25+
{
26+
name: "defaults undefined to default account",
27+
input: undefined,
28+
expected: DEFAULT_ACCOUNT_ID,
29+
},
30+
{ name: "defaults null to default account", input: null, expected: DEFAULT_ACCOUNT_ID },
31+
{
32+
name: "defaults blank strings to default account",
33+
input: " ",
34+
expected: DEFAULT_ACCOUNT_ID,
35+
},
36+
{ name: "normalizes valid ids to lowercase", input: " Business_1 ", expected: "business_1" },
37+
{
38+
name: "sanitizes invalid characters into canonical ids",
39+
input: " Prod/US East ",
40+
expected: "prod-us-east",
41+
},
42+
...reservedAccountIdCases.map(({ name, input }) => ({
43+
name,
44+
input,
45+
expected: DEFAULT_ACCOUNT_ID,
46+
})),
47+
] as const)("$name", ({ input, expected }) => {
48+
expectNormalizedAccountIdCase({ input, expected });
3049
});
3150

32-
it("preserves optional semantics without forcing default", () => {
33-
expect(normalizeOptionalAccountId(undefined)).toBeUndefined();
34-
expect(normalizeOptionalAccountId(" ")).toBeUndefined();
35-
expect(normalizeOptionalAccountId(" !!! ")).toBeUndefined();
36-
expect(normalizeOptionalAccountId(" Business ")).toBe("business");
51+
it.each([
52+
{ name: "keeps undefined optional values unset", input: undefined, expected: undefined },
53+
{ name: "keeps blank optional values unset", input: " ", expected: undefined },
54+
{ name: "keeps invalid optional values unset", input: " !!! ", expected: undefined },
55+
...reservedAccountIdCases.map(({ name, input }) => ({
56+
name: name.replace(" pollution keys", " optional values"),
57+
input,
58+
expected: undefined,
59+
})),
60+
{ name: "normalizes valid optional values", input: " Business ", expected: "business" },
61+
] as const)("$name", ({ input, expected }) => {
62+
expectNormalizedAccountIdCase({ input, expected, optional: true });
3763
});
3864
});

src/routing/account-lookup.test.ts

Lines changed: 83 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,98 @@
11
import { describe, expect, it } from "vitest";
22
import { resolveAccountEntry, resolveNormalizedAccountEntry } from "./account-lookup.js";
33

4+
function createAccountsWithPrototypePollution() {
5+
const inherited = { default: { id: "polluted" } };
6+
return Object.create(inherited) as Record<string, { id: string }>;
7+
}
8+
9+
function expectResolvedAccountLookupCase(
10+
actual: { id: string } | undefined,
11+
expected: { id: string } | undefined,
12+
) {
13+
expect(actual).toEqual(expected);
14+
}
15+
16+
function expectPrototypePollutionIgnoredCase(
17+
resolve: (accounts: Record<string, { id: string }>) => { id: string } | undefined,
18+
) {
19+
const pollutedAccounts = createAccountsWithPrototypePollution();
20+
expect(resolve(pollutedAccounts)).toBeUndefined();
21+
}
22+
23+
function expectAccountLookupCase(params: {
24+
accounts?: Record<string, { id: string }>;
25+
resolve: (accounts: Record<string, { id: string }>) => { id: string } | undefined;
26+
expected: { id: string } | undefined;
27+
}) {
28+
expectResolvedAccountLookupCase(params.resolve(params.accounts ?? {}), params.expected);
29+
}
30+
431
describe("resolveAccountEntry", () => {
5-
it("resolves direct and case-insensitive account keys", () => {
6-
const accounts = {
7-
default: { id: "default" },
8-
Business: { id: "business" },
9-
};
10-
expect(resolveAccountEntry(accounts, "default")).toEqual({ id: "default" });
11-
expect(resolveAccountEntry(accounts, "business")).toEqual({ id: "business" });
32+
const accounts = {
33+
default: { id: "default" },
34+
Business: { id: "business" },
35+
};
36+
37+
it.each([
38+
{
39+
name: "resolves the default account key",
40+
resolve: (localAccounts: Record<string, { id: string }>) =>
41+
resolveAccountEntry(localAccounts, "default"),
42+
expected: { id: "default" },
43+
},
44+
{
45+
name: "resolves a normalized business account key",
46+
resolve: (localAccounts: Record<string, { id: string }>) =>
47+
resolveAccountEntry(localAccounts, "business"),
48+
expected: { id: "business" },
49+
},
50+
] as const)("$name", ({ resolve, expected }) => {
51+
expectAccountLookupCase({ accounts, resolve, expected });
1252
});
1353

1454
it("ignores prototype-chain values", () => {
15-
const inherited = { default: { id: "polluted" } };
16-
const accounts = Object.create(inherited) as Record<string, { id: string }>;
17-
expect(resolveAccountEntry(accounts, "default")).toBeUndefined();
55+
expectPrototypePollutionIgnoredCase((localAccounts) =>
56+
resolveAccountEntry(localAccounts, "default"),
57+
);
1858
});
1959
});
2060

2161
describe("resolveNormalizedAccountEntry", () => {
22-
it("resolves normalized account keys with a custom normalizer", () => {
23-
const accounts = {
24-
"Ops Team": { id: "ops" },
25-
};
26-
27-
expect(
28-
resolveNormalizedAccountEntry(accounts, "ops-team", (accountId) =>
29-
accountId.trim().toLowerCase().replaceAll(" ", "-"),
30-
),
31-
).toEqual({ id: "ops" });
32-
});
62+
const normalizeAccountId = (accountId: string) =>
63+
accountId.trim().toLowerCase().replaceAll(" ", "-");
3364

34-
it("ignores prototype-chain values", () => {
35-
const inherited = { default: { id: "polluted" } };
36-
const accounts = Object.create(inherited) as Record<string, { id: string }>;
65+
it.each([
66+
{
67+
name: "resolves normalized account keys with a custom normalizer",
68+
accounts: {
69+
"Ops Team": { id: "ops" },
70+
},
71+
resolve: (accounts: Record<string, { id: string }>) =>
72+
resolveNormalizedAccountEntry(accounts, "ops-team", normalizeAccountId),
73+
expected: {
74+
id: "ops",
75+
},
76+
},
77+
{
78+
name: "ignores prototype-chain values",
79+
resolve: () => undefined,
80+
expected: undefined,
81+
assert: () =>
82+
expectPrototypePollutionIgnoredCase((accounts) =>
83+
resolveNormalizedAccountEntry(accounts, "default", (accountId) => accountId),
84+
),
85+
},
86+
] as const)("$name", ({ accounts, resolve, expected, assert }) => {
87+
if (assert) {
88+
assert();
89+
return;
90+
}
3791

38-
expect(
39-
resolveNormalizedAccountEntry(accounts, "default", (accountId) => accountId),
40-
).toBeUndefined();
92+
expectAccountLookupCase({
93+
accounts,
94+
resolve,
95+
expected,
96+
});
4197
});
4298
});

0 commit comments

Comments
 (0)