mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 18:22:42 +00:00
* fix(artifacts): classify bundle executables by their own name A directory bundle is selected for its executable, so the executable's canonical name classifies it; the bundle directory spelling is not an artifact alias. Bundle facts now reach artifact, archive and database targets so the admitted-source digest binds to the executable. Fixes #7362 * fix(artifacts): share the artifact identity across every artifact route The trailing-archive fallback also carries the bundle facts now, so a bundle whose executable is a script stub with an appended ZIP keeps its digest bound to the executable. * fix(artifacts): name the classification spelling at the resolver boundary The resolver already knows whether a bundle directory or a file was selected, so it hands the executable or the caller's spelling to the reader instead of inferring it from the Info.plist. Every admitted route now shares one identity, and the .app bundle operations accept a bundle whatever its executable admitted as. * fix(artifacts): route bundle keyed archive reads by bundle facts A bundle whose executable is a plist now carries bundleInfoPlist, so inspect_keyed_archive must read bundle-relative archives instead of the standalone route that compares the selected bundle directory with the executable. * refactor(artifacts): build the selected ESP flash identity from the shared owner The selected ESP flash route from main built path, source path and digest inline. Use the resolver's shared identity so it carries the same facts as every other admitted target.
187 lines
7.1 KiB
TypeScript
187 lines
7.1 KiB
TypeScript
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
|
|
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
|
import { expect, it, vi } from "vitest";
|
|
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
|
|
import { createDirectAnalysis } from "../../../src/composition/directAnalysis.js";
|
|
import {
|
|
artifactExtractionResultSchema,
|
|
artifactInventoryResultSchema,
|
|
} from "../../../src/domain/artifactGraph.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { elf } from "../../../src/domain/binaryTarget.fixture.js";
|
|
import { ArtifactProvider } from "../../../src/artifacts/ArtifactProvider.js";
|
|
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const fixture = async (storedName = "content-addressed-archive") => {
|
|
const root = await createTestTempDirectory("rea-artifact-selected-name-");
|
|
const canonical = join(root, storedName);
|
|
const writer = new ZipWriter(new Uint8ArrayWriter());
|
|
await writer.add(
|
|
"AndroidManifest.xml",
|
|
new TextReader(
|
|
'<manifest package="example.fixture"><application /></manifest>',
|
|
),
|
|
);
|
|
await writer.add("payload.txt", new TextReader("same packed bytes"));
|
|
await writeFile(canonical, await writer.close());
|
|
return { root, canonical };
|
|
};
|
|
|
|
it.skipIf(process.platform === "win32").each([
|
|
["selected.apk", "content", "apk"],
|
|
["selected.ipa", "content", "ipa"],
|
|
["selected.apk", "canonical.ipa", "apk"],
|
|
["selected.ipa", "canonical.apk", "ipa"],
|
|
["selected.unknown", "canonical.apk", "apk"],
|
|
["selected.asar", "content", "zip"],
|
|
] as const)(
|
|
"keeps selected %s and stored %s consistent across admission, inventory and extraction",
|
|
async (name, stored, format) => {
|
|
const { root, canonical } = await fixture(stored);
|
|
const selected = join(root, name);
|
|
await symlink(canonical, selected);
|
|
const admitted = await parseBinaryTarget(selected);
|
|
if (!admitted.ok) throw admitted.error;
|
|
expect(admitted.value.path).toBe(canonical);
|
|
expect(admitted.value.format).toBe(format);
|
|
const { runProviderAnalysis } = createDirectAnalysis({});
|
|
const observed = parseEvidence(
|
|
await runProviderAnalysis(selected, "inventory_artifact", {}).then(
|
|
projectCliAnalysisResult,
|
|
),
|
|
);
|
|
expect(observed.subject?.local_path).toBe(selected);
|
|
expect(
|
|
artifactInventoryResultSchema.parse(observed.normalized_result).manifest
|
|
.root_format,
|
|
).toBe(format);
|
|
const provider = new ArtifactProvider({});
|
|
const client = provider.createClient(admitted.value);
|
|
try {
|
|
const execution = await client.execute("extract_artifact", {
|
|
output_root: join(root, "output"),
|
|
integrity_policy: "fail",
|
|
});
|
|
if (!execution.ok) throw execution.error;
|
|
expect(execution.value.subject?.path).toBe(selected);
|
|
const result = artifactExtractionResultSchema.parse(
|
|
execution.value.result,
|
|
);
|
|
expect(result.manifest.root_format).toBe(format);
|
|
expect(
|
|
await readFile(join(result.output_root, "payload.txt"), "utf8"),
|
|
).toBe("same packed bytes");
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"reopens the same packed file under distinct package selections and reuses unchanged selection",
|
|
async () => {
|
|
const { root, canonical } = await fixture();
|
|
const apk = join(root, "selected.apk");
|
|
const ipa = join(root, "selected.ipa");
|
|
await symlink(canonical, apk);
|
|
await symlink(canonical, ipa);
|
|
const provider = new ArtifactProvider({});
|
|
const created = vi.spyOn(provider, "createClient");
|
|
const session = createTestBinarySession(provider);
|
|
try {
|
|
for (const [path, format, calls] of [
|
|
[apk, "apk", 1],
|
|
[apk, "apk", 1],
|
|
[ipa, "ipa", 2],
|
|
] as const) {
|
|
const opened = await session.open(path);
|
|
if (!opened.ok) throw opened.error;
|
|
expect(created).toHaveBeenCalledTimes(calls);
|
|
const execution = await session.execute("inventory_artifact", {});
|
|
if (!execution.ok) throw execution.error;
|
|
expect(execution.value.subject?.path).toBe(path);
|
|
expect(
|
|
artifactInventoryResultSchema.parse(execution.value.result).manifest
|
|
.root_format,
|
|
).toBe(format);
|
|
}
|
|
} finally {
|
|
await session.close();
|
|
created.mockRestore();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"does not reinterpret real executable bytes from an artifact alias suffix",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-executable-name-");
|
|
const canonical = join(root, "program");
|
|
const selected = join(root, "selected.asar");
|
|
await writeFile(canonical, elf(2, 1, 62));
|
|
await symlink(canonical, selected);
|
|
const admitted = await parseBinaryTarget(selected);
|
|
if (!admitted.ok) throw admitted.error;
|
|
expect(admitted.value.format).toBe("elf");
|
|
expect(admitted.value.kind).toBe("executable");
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"classifies a bundle's executable by its own name and keeps the bundle facts",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-bundle-name-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await mkdir(bundle);
|
|
const infoPlist = join(bundle, "Info.plist");
|
|
await writeFile(
|
|
infoPlist,
|
|
"<plist><dict><key>CFBundleExecutable</key><string>main.js</string></dict></plist>",
|
|
);
|
|
const executable = join(bundle, "main.js");
|
|
await writeFile(executable, "export const value = 1;\n");
|
|
const admitted = await parseBinaryTarget(bundle);
|
|
if (!admitted.ok) throw admitted.error;
|
|
expect(admitted.value).toMatchObject({
|
|
path: executable,
|
|
kind: "artifact",
|
|
format: "javascript",
|
|
bundleInfoPlist: infoPlist,
|
|
artifactSelectedPath: bundle,
|
|
});
|
|
},
|
|
);
|
|
|
|
it.skipIf(process.platform === "win32")(
|
|
"keeps the bundle facts when the executable is a script stub with a trailing archive",
|
|
async () => {
|
|
const root = await createTestTempDirectory("rea-artifact-bundle-zip-");
|
|
const bundle = join(root, "Fixture.app");
|
|
await mkdir(bundle);
|
|
const infoPlist = join(bundle, "Info.plist");
|
|
await writeFile(
|
|
infoPlist,
|
|
"<plist><dict><key>CFBundleExecutable</key><string>main</string></dict></plist>",
|
|
);
|
|
const writer = new ZipWriter(new Uint8ArrayWriter());
|
|
await writer.add("payload.txt", new TextReader("appended archive"));
|
|
const executable = join(bundle, "main");
|
|
await writeFile(
|
|
executable,
|
|
Buffer.concat([Buffer.from("#!/bin/sh\nexit 0\n"), await writer.close()]),
|
|
);
|
|
const admitted = await parseBinaryTarget(bundle);
|
|
if (!admitted.ok) throw admitted.error;
|
|
expect(admitted.value).toMatchObject({
|
|
path: executable,
|
|
kind: "archive",
|
|
format: "zip",
|
|
bundleInfoPlist: infoPlist,
|
|
artifactSelectedPath: bundle,
|
|
});
|
|
},
|
|
);
|