Files
rea/tests/boundary/cli/appleUnavailableComponentsCli.test.ts
T
morluto c192c58815 fix(apple): retain unavailable application components
Preserve unread file and slice facts before Apple attribution. Report partial
component coverage while keeping manifest count completeness independent.
Directories and symlinks retain their existing representation.

Consolidate occurrence identity interpretation and component schemas across
Apple, Android and Harmony; remove superseded producers. Android/Harmony
completed source replies and projection IDs remain byte-identical in controls.
Apple's additive unavailable_components field changes its projection IDs.

Adapt the unavailable-component proposal and real ZIP regressions from
PR #4692 by @rodricksz4h5, adding directory, CLI, MCP and non-encrypted controls.

Validation: 77 focused checks; check:fast; docs:check (5 README checks).
18 tiny completed source cost controls; no real-provider/target execution
or heavy profiling. Published npm package and large-input costs unverified.

Fixes #4688
2026-10-17 19:46:20 +08:00

89 lines
3.4 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
import { expect } from "vitest";
import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js";
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"Apple CLI preserves unread paths and distinguishes readable coverage",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-apple-unread-cli-");
for (const encrypted of [false, true]) {
const path = join(root, `${encrypted ? "encrypted" : "readable"}.ipa`);
const unread = "Payload/Fixture.app/libentry.dylib";
const writer = new ZipWriter(new Uint8ArrayWriter());
await writer.add("Payload/Fixture.app/", undefined, { directory: true });
await writer.add(
"Payload/Fixture.app/Info.plist",
new TextReader("<plist/>"),
);
await writer.add(
unread,
new TextReader("harmless text"),
encrypted ? { password: "fixture-only" } : {},
);
await writeFile(path, await writer.close());
const inspected = await cli.run({
arguments: ["inspect-artifact", path, "--json"],
});
expect(inspected.exitCode, inspected.stderr).toBe(0);
const inspection = artifactInspectionResultSchema.parse(
parseEvidence(inspected.json).normalized_result,
);
const inventories = inspection.substeps.map(({ evidence }) => evidence);
const input = join(root, `${encrypted}.json`);
await writeFile(
input,
JSON.stringify({ inventory_evidence: inventories }),
);
const output = await cli.run({
arguments: ["project-apple-application-graph", input, "--json"],
});
expect(output.exitCode, output.stderr).toBe(0);
const evidence = parseEvidence(output.json);
const projection = appleApplicationProjectionResultSchema.parse(
evidence.normalized_result,
);
expect(projection.source_evidence_ids).toEqual(
inventories.map(({ evidence_id }) => evidence_id).sort(),
);
expect(projection.coverage).toEqual({
status: encrypted ? "partial" : "complete-within-inventory",
inventory_complete: true,
});
if (encrypted) {
const occurrence = inventories[0]?.normalized_result.occurrences.find(
({ logical_path }) => logical_path === unread,
);
expect(occurrence).toMatchObject({
artifact_id: null,
encrypted: true,
hash_status: "unavailable",
});
expect(projection.unavailable_components).toEqual([
{
path: unread,
artifact_id: null,
sha256: null,
format: occurrence?.artifact_format,
encrypted: true,
limitations: occurrence?.limitations,
},
]);
expect(projection.components.native_libraries).toEqual([]);
} else {
expect(projection.unavailable_components).toEqual([]);
expect(
projection.components.native_libraries.map(({ path }) => path),
).toEqual([unread]);
}
}
},
);