test(artifacts): respect destination filesystem case sensitivity
This commit is contained in:
@@ -100,7 +100,6 @@ describe("entry-order validation and reader controls", () => {
|
||||
it.each([
|
||||
["pkg/data.txt", "pkg/data.txt/"],
|
||||
["pkg/data.txt/", "pkg/data.txt"],
|
||||
["pkg/data.txt", "PKG/other.txt"],
|
||||
["pkg/data.txt", "pkg"],
|
||||
["../escape.txt"],
|
||||
])("keeps invalid/colliding paths rejected for %j", async (...entries) => {
|
||||
@@ -112,6 +111,25 @@ describe("entry-order validation and reader controls", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps case-distinct directory names separate in either entry order", async () => {
|
||||
const root = await createTestTempDirectory("rea-entry-order-case-");
|
||||
const archive = join(root, "case-distinct.zip");
|
||||
const entries = ["pkg/", "pkg/data.txt", "PKG/", "PKG/other.txt"];
|
||||
const parents = {
|
||||
".": null,
|
||||
pkg: ".",
|
||||
"pkg/data.txt": "pkg",
|
||||
PKG: ".",
|
||||
"PKG/other.txt": "PKG",
|
||||
};
|
||||
for (const ordered of [entries, entries.toReversed()]) {
|
||||
await writeOrderedZip(archive, ordered);
|
||||
expect(artifactParentPaths(await inventoryArtifact(archive))).toEqual(
|
||||
parents,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves ordinary directory and ASAR structure", async () => {
|
||||
const root = await createTestTempDirectory("rea-entry-order-readers-");
|
||||
const directory = join(root, "source");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { lstat, readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
||||
@@ -20,6 +20,7 @@ import { artifactInventoryResultSchema } from "../../../../src/domain/artifactGr
|
||||
import type { BinaryTarget } from "../../../../src/domain/binaryTargetTypes.js";
|
||||
import { parseBinaryTarget } from "../../../../src/application/BinaryTargetResolver.js";
|
||||
import { parseEvidence } from "../../../../src/domain/evidence.js";
|
||||
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
|
||||
|
||||
const { runProviderAnalysis } = createDirectAnalysis({});
|
||||
|
||||
@@ -136,38 +137,51 @@ describe("artifact archive safety", () => {
|
||||
destinationCaseCollisionMessage("res/2f.xml", ["2f.xml", "2F.xml"]),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("extracts logical names that differ only in case", async () => {
|
||||
const root = await createTestTempDirectory("rea-case-distinct-zip-");
|
||||
const path = join(root, "fixture.zip");
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
await writer.add("Main.js", new TextReader("upper"));
|
||||
await writer.add("main.js", new TextReader("lower"));
|
||||
await writeFile(path, await writer.close());
|
||||
const parsed = await parseBinaryTarget(path);
|
||||
expect(parsed.ok).toBe(true);
|
||||
if (!parsed.ok) return;
|
||||
const result = await inventory(parsed.value);
|
||||
expect(result.limitations).toContain(
|
||||
ENGLISH_UNICODE_CASE_INVENTORY_LIMITATION,
|
||||
it("extracts case-distinct names only when the destination can preserve them", async () => {
|
||||
const root = await createTestTempDirectory("rea-case-distinct-zip-");
|
||||
await writeFile(join(root, "case-probe"), "lower");
|
||||
await writeFile(join(root, "CASE-PROBE"), "upper");
|
||||
const caseSensitive =
|
||||
(await readFile(join(root, "case-probe"), "utf8")) === "lower";
|
||||
const path = join(root, "fixture.zip");
|
||||
const writer = new ZipWriter(new Uint8ArrayWriter());
|
||||
await writer.add("Main.js", new TextReader("upper"));
|
||||
await writer.add("main.js", new TextReader("lower"));
|
||||
await writeFile(path, await writer.close());
|
||||
const parsed = await parseBinaryTarget(path);
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
const result = await inventory(parsed.value);
|
||||
expect(result.limitations).toContain(
|
||||
ENGLISH_UNICODE_CASE_INVENTORY_LIMITATION,
|
||||
);
|
||||
expect(
|
||||
result.occurrences
|
||||
.filter(({ logical_path }) => logical_path !== ".")
|
||||
.map(({ logical_path }) => logical_path)
|
||||
.sort(),
|
||||
).toEqual(["Main.js", "main.js"]);
|
||||
const output = join(root, "output");
|
||||
const extracted = await new ArtifactProvider(process.env)
|
||||
.createClient(parsed.value)
|
||||
.execute(
|
||||
"extract_artifact",
|
||||
artifactExtractionExecutionSchema.parse({ output_root: output }),
|
||||
);
|
||||
expect(
|
||||
result.occurrences
|
||||
.filter(({ logical_path }) => logical_path !== ".")
|
||||
.map(({ logical_path }) => logical_path)
|
||||
.sort(),
|
||||
).toEqual(["Main.js", "main.js"]);
|
||||
const output = join(root, "output");
|
||||
const extracted = await new ArtifactProvider(process.env)
|
||||
.createClient(parsed.value)
|
||||
.execute(
|
||||
"extract_artifact",
|
||||
artifactExtractionExecutionSchema.parse({ output_root: output }),
|
||||
);
|
||||
expect(extracted.ok).toBe(true);
|
||||
expect(await readFile(join(output, "Main.js"), "utf8")).toBe("upper");
|
||||
expect(await readFile(join(output, "main.js"), "utf8")).toBe("lower");
|
||||
});
|
||||
if (!caseSensitive) {
|
||||
if (extracted.ok) throw new Error("expected a destination case collision");
|
||||
expect(projectAnalysisError(extracted.error)).toMatchObject({
|
||||
message:
|
||||
"Destination filesystem cannot store both main.js and Main.js; inventory retains both logical names.",
|
||||
details: { operation: "extract_artifact", reason: "path" },
|
||||
});
|
||||
await expect(lstat(output)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
return;
|
||||
}
|
||||
if (!extracted.ok) throw extracted.error;
|
||||
expect(await readFile(join(output, "Main.js"), "utf8")).toBe("upper");
|
||||
expect(await readFile(join(output, "main.js"), "utf8")).toBe("lower");
|
||||
});
|
||||
const inventory = async (targetValue: BinaryTarget) => {
|
||||
const result = await new ArtifactProvider(process.env)
|
||||
|
||||
Reference in New Issue
Block a user