test: expand archive path helper coverage

This commit is contained in:
Peter Steinberger 2026-03-13 18:29:56 +00:00
parent bc9a9cf972
commit f0a266cb86
1 changed files with 85 additions and 14 deletions

View File

@ -1,18 +1,71 @@
import path from "node:path"; import path from "node:path";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import {
isWindowsDrivePath,
normalizeArchiveEntryPath,
resolveArchiveOutputPath, resolveArchiveOutputPath,
stripArchivePath, stripArchivePath,
validateArchiveEntryPath, validateArchiveEntryPath,
} from "./archive-path.js"; } from "./archive-path.js";
describe("archive path helpers", () => { describe("archive path helpers", () => {
it("uses custom escape labels in traversal errors", () => { it.each([
{ value: "C:\\temp\\file.txt", expected: true },
{ value: "D:/temp/file.txt", expected: true },
{ value: "tmp/file.txt", expected: false },
{ value: "/tmp/file.txt", expected: false },
])("detects Windows drive paths for %j", ({ value, expected }) => {
expect(isWindowsDrivePath(value)).toBe(expected);
});
it.each([
{ raw: "dir\\file.txt", expected: "dir/file.txt" },
{ raw: "dir/file.txt", expected: "dir/file.txt" },
])("normalizes archive separators for %j", ({ raw, expected }) => {
expect(normalizeArchiveEntryPath(raw)).toBe(expected);
});
it.each(["", ".", "./"])("accepts empty-like entry paths: %j", (entryPath) => {
expect(() => validateArchiveEntryPath(entryPath)).not.toThrow();
});
it.each([
{
name: "uses custom escape labels in traversal errors",
entryPath: "../escape.txt",
message: "archive entry escapes targetDir: ../escape.txt",
},
{
name: "rejects Windows drive paths",
entryPath: "C:\\temp\\file.txt",
message: "archive entry uses a drive path: C:\\temp\\file.txt",
},
{
name: "rejects absolute paths after normalization",
entryPath: "/tmp/file.txt",
message: "archive entry is absolute: /tmp/file.txt",
},
{
name: "rejects double-slash absolute paths after normalization",
entryPath: "\\\\server\\share.txt",
message: "archive entry is absolute: \\\\server\\share.txt",
},
])("$name", ({ entryPath, message }) => {
expect(() => expect(() =>
validateArchiveEntryPath("../escape.txt", { validateArchiveEntryPath(entryPath, {
escapeLabel: "targetDir", escapeLabel: "targetDir",
}), }),
).toThrow("archive entry escapes targetDir: ../escape.txt"); ).toThrow(message);
});
it.each([
{ entryPath: "a/../escape.txt", stripComponents: 1, expected: "../escape.txt" },
{ entryPath: "a//b/file.txt", stripComponents: 1, expected: "b/file.txt" },
{ entryPath: "./", stripComponents: 0, expected: null },
{ entryPath: "a", stripComponents: 3, expected: null },
{ entryPath: "dir\\sub\\file.txt", stripComponents: 1, expected: "sub/file.txt" },
])("strips archive paths for %j", ({ entryPath, stripComponents, expected }) => {
expect(stripArchivePath(entryPath, stripComponents)).toBe(expected);
}); });
it("preserves strip-induced traversal for follow-up validation", () => { it("preserves strip-induced traversal for follow-up validation", () => {
@ -25,22 +78,40 @@ describe("archive path helpers", () => {
).toThrow("archive entry escapes targetDir: ../escape.txt"); ).toThrow("archive entry escapes targetDir: ../escape.txt");
}); });
it("keeps resolved output paths inside the root", () => { it.each([
const rootDir = path.join(path.sep, "tmp", "archive-root"); {
const safe = resolveArchiveOutputPath({ name: "keeps resolved output paths inside the root",
rootDir,
relPath: "sub/file.txt", relPath: "sub/file.txt",
originalPath: "sub/file.txt", originalPath: "sub/file.txt",
}); expected: path.resolve(path.join(path.sep, "tmp", "archive-root"), "sub/file.txt"),
expect(safe).toBe(path.resolve(rootDir, "sub/file.txt")); },
{
name: "rejects output paths that escape the root",
relPath: "../escape.txt",
originalPath: "../escape.txt",
escapeLabel: "targetDir",
message: "archive entry escapes targetDir: ../escape.txt",
},
])("$name", ({ relPath, originalPath, escapeLabel, expected, message }) => {
const rootDir = path.join(path.sep, "tmp", "archive-root");
if (message) {
expect(() =>
resolveArchiveOutputPath({
rootDir,
relPath,
originalPath,
escapeLabel,
}),
).toThrow(message);
return;
}
expect(() => expect(
resolveArchiveOutputPath({ resolveArchiveOutputPath({
rootDir, rootDir,
relPath: "../escape.txt", relPath,
originalPath: "../escape.txt", originalPath,
escapeLabel: "targetDir",
}), }),
).toThrow("archive entry escapes targetDir: ../escape.txt"); ).toBe(expected);
}); });
}); });