mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
Normalize path facts once at the owning filesystem boundary, preserve caller spelling and unavailable inventory members, and retain typed read failures. Record exclusion entry kinds so only proven directories can establish descendant symlink targets; omitted legacy kinds stay unknown. Reviewed fixes include #1969, #2642, #2759, and #2994. Validation: 26 reference graph source tests passed; prior artifact checks passed 275 tests with one Windows-only skip. Updated compiled import workflows are included in final validation after regeneration.
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { execFile } from "node:child_process";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
import { build } from "plist";
|
|
import { expect, it } from "vitest";
|
|
|
|
import { analyzeInterfaceBuilderBundle } from "../../../src/artifacts/apple/InterfaceBuilderAnalysis.js";
|
|
import { inspectBundleKeyedArchive } from "../../../src/artifacts/apple/KeyedArchiveReader.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const archive = build({
|
|
$archiver: "NSKeyedArchiver",
|
|
$objects: ["$null", "Café"],
|
|
$top: { root: { CF$UID: 1 } },
|
|
});
|
|
|
|
it("preflights repeated unterminated quoted declarations without suffix rescans", async () => {
|
|
const preflightUrl = new URL(
|
|
"../../../dist/artifacts/apple/InterfaceBuilderDecodeBudget.js",
|
|
import.meta.url,
|
|
).href;
|
|
const { stdout } = await promisify(execFile)(
|
|
process.execPath,
|
|
[
|
|
"--input-type=module",
|
|
"-e",
|
|
`
|
|
import { preflightPropertyListDecode } from ${JSON.stringify(preflightUrl)};
|
|
const xml = '<!DOCTYPE "x>x" '.repeat(70000);
|
|
const observed = preflightPropertyListDecode(Buffer.from(xml), 16 * 1024 * 1024);
|
|
process.stdout.write(JSON.stringify({ observed, expected: xml.length * 4 }));
|
|
`,
|
|
],
|
|
{ timeout: 5000 },
|
|
);
|
|
const result = JSON.parse(stdout) as {
|
|
observed: { estimatedBytes: number };
|
|
expected: number;
|
|
};
|
|
expect(result.observed.estimatedBytes).toBe(result.expected);
|
|
}, 10000);
|
|
|
|
const fixture = async (declaration: string) => {
|
|
const bundlePath = await createTestTempDirectory("rea-xml-declaration-");
|
|
await writeFile(
|
|
join(bundlePath, "Panel.nib"),
|
|
Buffer.from(archive.replace("UTF-8", declaration), "utf8"),
|
|
);
|
|
return { bundlePath, targetSha256: "a".repeat(64) };
|
|
};
|
|
|
|
it.each(["UTF-8", "utf-8"])(
|
|
"retains UTF-8 characters with the supported declaration %s",
|
|
async (declaration) => {
|
|
const input = await fixture(declaration);
|
|
const archiveResult = await inspectBundleKeyedArchive({
|
|
...input,
|
|
parameters: { path: "Panel.nib" },
|
|
});
|
|
expect(archiveResult.objects).toContainEqual(
|
|
expect.objectContaining({ value: "Café" }),
|
|
);
|
|
const interfaceBuilderResult = await analyzeInterfaceBuilderBundle(input);
|
|
expect(interfaceBuilderResult.documents).toHaveLength(1);
|
|
expect(interfaceBuilderResult.documents[0]?.relative_path).toBe(
|
|
"Panel.nib",
|
|
);
|
|
},
|
|
);
|
|
|
|
it.each(["REA-UNSUPPORTED", "ISO-8859-1"])(
|
|
"reports unsupported keyed archive encoding %s instead of substituting UTF-8 text",
|
|
async (declaration) => {
|
|
const input = await fixture(declaration);
|
|
await expect(
|
|
inspectBundleKeyedArchive({
|
|
...input,
|
|
parameters: { path: "Panel.nib" },
|
|
}),
|
|
).rejects.toMatchObject({
|
|
reason: "format",
|
|
message: expect.stringContaining(declaration),
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each(["REA-UNSUPPORTED", "ISO-8859-1"])(
|
|
"reports unsupported Interface Builder encoding %s without graph substitution",
|
|
async (declaration) => {
|
|
const result = await analyzeInterfaceBuilderBundle(
|
|
await fixture(declaration),
|
|
);
|
|
expect(result.documents).toEqual([]);
|
|
expect(result.graph.nodes).toEqual([]);
|
|
expect(result.graph.coverage).toContainEqual(
|
|
expect.objectContaining({
|
|
facet: "archive_decode",
|
|
status: "partial",
|
|
reason: "one_or_more_archives_invalid",
|
|
}),
|
|
);
|
|
expect(result.graph.truncated).toBe(true);
|
|
},
|
|
);
|