mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
160 lines
5.9 KiB
TypeScript
160 lines
5.9 KiB
TypeScript
import { lstat, mkdir, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { createPackage } from "@electron/asar";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { scanArtifactInventory } from "../../../src/artifacts/inventory/ArtifactInventory.js";
|
|
import { readJavaScriptArtifactFiles } from "../../../src/artifacts/javascript/JavaScriptArtifactFiles.js";
|
|
import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js";
|
|
import { DirectoryArtifactReader } from "../../../src/artifacts/DirectoryArtifactReader.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const fixture = async () => {
|
|
const root = await createTestTempDirectory("rea-ingestion-content-identity-");
|
|
const source = join(root, "source");
|
|
const bundle = join(root, "bundle");
|
|
await Promise.all([mkdir(source), mkdir(bundle)]);
|
|
await writeFile(join(source, "main.js"), "module.exports = 42;\n");
|
|
await writeFile(join(source, "asset.txt"), "original asset");
|
|
const archive = join(bundle, "app.asar");
|
|
await createPackage(source, archive);
|
|
return { source, bundle, archive };
|
|
};
|
|
|
|
describe("JavaScript artifact ingestion content identities", () => {
|
|
it.each([
|
|
["changed-asset", "directory"],
|
|
["added-asset", "directory"],
|
|
["changed-asset", "asar"],
|
|
["added-asset", "asar"],
|
|
])(
|
|
"rejects a rebuilt container with %s for a %s input and unchanged source",
|
|
async (change, format) => {
|
|
const { source, bundle, archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(
|
|
format === "asar" ? archive : bundle,
|
|
);
|
|
await writeFile(
|
|
join(source, change === "changed-asset" ? "asset.txt" : "new.txt"),
|
|
"rebuilt asset",
|
|
);
|
|
await createPackage(source, archive);
|
|
const reader =
|
|
format === "asar"
|
|
? new AsarArtifactReader(archive)
|
|
: new DirectoryArtifactReader(bundle);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ reason: "integrity" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("rejects changed opaque native-addon bytes after inventory", async () => {
|
|
const root = await createTestTempDirectory("rea-ingestion-native-bytes-");
|
|
const addon = join(root, "addon.node");
|
|
await writeFile(addon, Buffer.from([0xcf, 0xfa, 0xed, 0xfe, 1]));
|
|
const snapshot = await scanArtifactInventory(root);
|
|
await writeFile(addon, Buffer.from([0xcf, 0xfa, 0xed, 0xfe, 2]));
|
|
const reader = new DirectoryArtifactReader(root);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ reason: "integrity" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves an unchanged direct ASAR raw identity and source", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
try {
|
|
const files = await readJavaScriptArtifactFiles(reader, snapshot);
|
|
expect(files.containers).toEqual([]);
|
|
expect(files.files.find(({ path }) => path === "main.js")).toMatchObject({
|
|
container_sha256: snapshot.manifest.root_sha256,
|
|
text: { included: true, value: "module.exports = 42;\n" },
|
|
});
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("rejects a same-size ASAR replacement against the inventoried root digest", async () => {
|
|
const { source, archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
const original = await lstat(archive);
|
|
await writeFile(join(source, "main.js"), "module.exports = 43;\n");
|
|
await createPackage(source, archive);
|
|
expect((await lstat(archive)).size).toBe(original.size);
|
|
|
|
const reader = new AsarArtifactReader(archive);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ reason: "integrity" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves typed cancellation when the direct ASAR filesystem stream aborts", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
const controller = new AbortController();
|
|
try {
|
|
const reading = readJavaScriptArtifactFiles(
|
|
reader,
|
|
snapshot,
|
|
controller.signal,
|
|
);
|
|
controller.abort();
|
|
await expect(reading).rejects.toMatchObject({ reason: "cancelled" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("retains native filesystem errors for outer IO classification", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
await rm(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ code: "ENOENT" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves unchanged container and opaque addon identities", async () => {
|
|
const { bundle } = await fixture();
|
|
await writeFile(join(bundle, "addon.node"), Buffer.from([1, 2, 3]));
|
|
const snapshot = await scanArtifactInventory(bundle);
|
|
const reader = new DirectoryArtifactReader(bundle);
|
|
try {
|
|
const files = await readJavaScriptArtifactFiles(reader, snapshot);
|
|
expect(files.containers).toHaveLength(1);
|
|
expect(
|
|
files.files.find(({ path }) => path === "addon.node"),
|
|
).toMatchObject({
|
|
text: { included: false, reason: "not-applicable" },
|
|
});
|
|
expect(
|
|
files.files.find(({ path }) => path === "app.asar/main.js")?.text,
|
|
).toEqual({ included: true, value: "module.exports = 42;\n" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
});
|