Skip to content

Commit e2a0bce

Browse files
committed
test(snapshot): compare the written header by string equality, never as a pattern built from the schema URL
The flow and library tests built a RegExp from SNAPSHOT_SCHEMA_URL (dots escaped by hand), which CodeQL reads as a hostname pattern that can match more hosts than intended. The header is now split into lines and the schema pin compared whole; a look-alike host with one dot replaced is pinned as rejected. The unwritable-path assertion drops its path-derived RegExp the same way.
1 parent 6a6e829 commit e2a0bce

2 files changed

Lines changed: 53 additions & 21 deletions

File tree

‎test/flows/library.test.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,19 @@ describe("snapshotRepository and snapshotRepositories", () => {
172172
{ key: "labels", status: "snapshot", detail: [] },
173173
{ key: "actions_secrets", status: "snapshot", detail: [SECRET_NOTE] },
174174
],
175-
yaml: expect.stringMatching(
176-
new RegExp(
177-
`^# yaml-language-server: \\$schema=${SNAPSHOT_SCHEMA_URL.replaceAll(".", "\\.")}\n# Snapshot of o/r taken \\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z\n# actions_secrets: ${SECRET_NOTE.replaceAll("[", "\\[").replaceAll("]", "\\]")}\nlabels:\n`,
178-
),
179-
),
180-
log: [{ level: "notice", line: `actions_secrets: ${SECRET_NOTE}` }],
175+
yaml: expect.any(String),
176+
log: [{ level: "notice", line: SECRET_NOTE }],
181177
});
182178
if (report.yaml === undefined) {
183179
throw new Error("a snapshot result carries its file");
184180
}
181+
// The header, line by line and by equality: the pin is a URL, never a pattern.
182+
const [pin, dated, note, first] = report.yaml.split("\n");
183+
expect(pin).toBe(`# yaml-language-server: $schema=${SNAPSHOT_SCHEMA_URL}`);
184+
expect(dated).toMatch(
185+
/^# Snapshot of o\/r taken \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/,
186+
);
187+
expect([note, first]).toEqual([`# ${SECRET_NOTE}`, "labels:"]);
185188
expect(stringifyYaml(parseYamlDoc(report.yaml))).toBe(stringifyYaml(report.settings));
186189
});
187190

‎test/flows/snapshot.test.ts‎

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,22 @@ const labelsRoute = (slug: string, labels: Array<typeof BUG>) => ({
4949
[`GET /repos/${slug}/labels?per_page=100&page=1`]: { data: labels },
5050
});
5151

52-
/** The header a written snapshot starts with: the schema pin, then the dated repository line. */
53-
const HEADER = (slug: string) =>
54-
new RegExp(
55-
`^# yaml-language-server: \\$schema=${SNAPSHOT_SCHEMA_URL.replaceAll(".", "\\.")}\\n# Snapshot of ${slug} taken \\d{4}-\\d{2}-\\d{2}T[\\d:.]+Z\\n`,
56-
);
52+
/** An ISO-8601 UTC instant, the form the header dates the snapshot in. */
53+
const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
54+
55+
/**
56+
* Whether `written` opens with the snapshot header for `slug`: the schema pin
57+
* compared as a whole string (never as a pattern, so a look-alike host cannot
58+
* pass), then the dated repository line.
59+
*/
60+
export function hasSnapshotHeader(written: string, slug: string): boolean {
61+
const [pin, dated] = written.split("\n");
62+
if (pin !== `# yaml-language-server: $schema=${SNAPSHOT_SCHEMA_URL}`) {
63+
return false;
64+
}
65+
const prefix = `# Snapshot of ${slug} taken `;
66+
return dated?.startsWith(prefix) === true && ISO_INSTANT.test(dated.slice(prefix.length));
67+
}
5768

5869
let dir: string;
5970
beforeEach(() => {
@@ -101,14 +112,32 @@ const dirCfg = (overrides: Partial<DirConfig> = {}): DirConfig =>
101112
...overrides,
102113
}) as DirConfig;
103114

115+
describe("hasSnapshotHeader", () => {
116+
const header = (url: string, slug = "o/r", instant = "2026-09-12T01:00:06.503Z") =>
117+
`# yaml-language-server: $schema=${url}\n# Snapshot of ${slug} taken ${instant}\nlabels: []\n`;
118+
119+
test("accepts the exact pin and a dated line; rejects a look-alike host, another slug, and a non-instant", () => {
120+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL), "o/r")).toBe(true);
121+
// The URL's dots are literal: a host with one dot swapped for another character is not the pin.
122+
expect(
123+
hasSnapshotHeader(
124+
header(SNAPSHOT_SCHEMA_URL.replace("githubusercontent.com", "githubusercontentXcom")),
125+
"o/r",
126+
),
127+
).toBe(false);
128+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL, "o/other"), "o/r")).toBe(false);
129+
expect(hasSnapshotHeader(header(SNAPSHOT_SCHEMA_URL, "o/r", "yesterday"), "o/r")).toBe(false);
130+
});
131+
});
132+
104133
describe("runSnapshot, file form", () => {
105134
test("writes the document under its header and reports through the outputs, the log, and the summary", async () => {
106135
const api = new MockApi(labelsRoute("o/r", [BUG, DOCS]));
107136
const cfg = fileCfg();
108137
const collected = collectingIo();
109138
expect(await run(api, cfg, collected.io)).toBe(0);
110139
const written = readFileSync(cfg.snapshotFile, "utf8");
111-
expect(written).toMatch(HEADER("o/r"));
140+
expect(hasSnapshotHeader(written, "o/r")).toBe(true);
112141
expect(parseYaml(written)).toEqual(doc(BUG, DOCS));
113142
expect(api.mutations()).toEqual([]);
114143
expect(collected.outputs).toEqual({ "skipped-sections": "", result: "snapshot" });
@@ -191,14 +220,14 @@ describe("runSnapshot, file form", () => {
191220
const collected = collectingIo();
192221
expect(await run(api, cfg, collected.io)).toBe(1);
193222
expect(collected.outputs.result).toBe("failed");
194-
expect(collected.lines[0]).toEqual({
195-
level: "error",
196-
line: expect.stringMatching(
197-
new RegExp(
198-
`^cannot write the snapshot to ${cfg.snapshotFile.replaceAll(".", "\\.")}: .*(EEXIST|ENOTDIR).*\\. Check that the "snapshot-file" input names a writable path$`,
199-
),
200-
),
201-
});
223+
const [first] = collected.lines;
224+
expect(first?.level).toBe("error");
225+
// The path is compared as text, never as a pattern; the OS error code sits between.
226+
expect(first?.line.startsWith(`cannot write the snapshot to ${cfg.snapshotFile}: `)).toBe(true);
227+
expect(first?.line).toMatch(/E(EXIST|NOTDIR)/);
228+
expect(
229+
first?.line.endsWith('. Check that the "snapshot-file" input names a writable path'),
230+
).toBe(true);
202231
});
203232
});
204233

@@ -265,7 +294,7 @@ describe("runSnapshot, dir form", () => {
265294
expect(await run(api, cfg, collected.io)).toBe(0);
266295
const fileA = join(cfg.snapshotDir, "o", "a.yml");
267296
const fileB = join(cfg.snapshotDir, "o", "b.yml");
268-
expect(readFileSync(fileA, "utf8")).toMatch(HEADER("o/a"));
297+
expect(hasSnapshotHeader(readFileSync(fileA, "utf8"), "o/a")).toBe(true);
269298
expect(parseYaml(readFileSync(fileA, "utf8"))).toEqual(doc(BUG));
270299
expect(parseYaml(readFileSync(fileB, "utf8"))).toEqual(doc(DOCS));
271300
expect(api.mutations()).toEqual([]);

0 commit comments

Comments
 (0)