Files
rea/tests/acceptance/applications/androidRuntimeComponentHints.test.ts
T
morluto bd4f5e9c19 fix(parsers): preserve format evidence and caller input identity
Retain bounded FAT and offset-tagged PE observations through archive hashing,
classification, and content nodes. Preserve PyInstaller member paths according
to the observed carrier dialect and share APK v1 signing-name semantics.

Read SQLite's implicit sqlite_schema table without inventing a stored schema
declaration. Distinguish unsupported ELF numbering and decoded-name admission
from malformed data and fixed scan allowances. Preserve Sigrok basenames and
classify matching explicit input-format refusals at their owning boundary.

Validation: 554 distinct tests passed, 3 skipped across serial focused runs;
build, typecheck/lint, docs, formatting, and discovery (2141 files) passed.
Sigrok command-boundary simulators do not establish real-tool decoding.
Small repeated public PyInstaller CLI runs had equivalent inventory facts;
warm median elapsed 0.5431s before / 0.5426s after, with no speed claim.

Fixes #6733
Fixes #6741
Fixes #6722
Fixes #6732
Fixes #6736
Fixes #6734
Fixes #6764
Fixes #6766
Refs #6739
2026-10-23 22:54:28 +08:00

140 lines
5.2 KiB
TypeScript

import { createHash } from "node:crypto";
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 { androidApplicationProjectionResultSchema } from "../../../src/domain/android/androidApplication.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"Android runtime hints follow classified bytecode through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-android-runtime-hints-");
const environment = { HOME: root, USERPROFILE: root };
const { call } = await connectLocalToolsMcp(environment);
for (const prefix of ["", "named"]) {
const writer = new ZipWriter(new Uint8ArrayWriter());
await writer.add("AndroidManifest.xml", new TextReader("fixture"));
for (const extension of ["dex", "class"])
await writer.add(
`assets/${prefix}.${extension}`,
new TextReader("ordinary text"),
);
const path = join(root, `${prefix || "dot-names"}.apk`);
await writeFile(path, await writer.close());
const inspected = await cli.run({
arguments: ["inspect-artifact", path, "--json"],
environment,
});
expect(inspected.exitCode, inspected.stderr).toBe(0);
const source = parseEvidence(inspected.json);
const input = { inventory_evidence: [source] };
const projected = await cli.run({
arguments: [
"project-android-application-graph",
JSON.stringify(input),
"--json",
],
environment,
});
expect(projected.exitCode, projected.stderr).toBe(0);
const evidence = parseEvidence(projected.json);
const graph = androidApplicationProjectionResultSchema.parse(
evidence.normalized_result,
);
expect(graph.components.dex.map(({ path }) => path)).toEqual(
prefix ? ["assets/named.dex"] : [],
);
expect(graph.components.jvm_classes.map(({ path }) => path)).toEqual(
prefix ? ["assets/named.class"] : [],
);
expect(graph.runtime_families).toEqual(
prefix ? ["dalvik-art", "java-kotlin"] : [],
);
const mcp = await call("project_android_application_graph", input);
expect(mcp.isError).not.toBe(true);
expect(parseEvidence(mcp.structuredContent)).toEqual(evidence);
}
},
);
cliTest(
"projects nested APK v1 filename candidates as inventory metadata through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory(
"rea-android-signing-candidates-",
);
const environment = { HOME: root, USERPROFILE: root };
const { call } = await connectLocalToolsMcp(environment);
const entries = [
"META-INF/CERT.SF",
"META-INF/CERT.EC",
"META-INF/keys/NESTED.SF",
"META-INF/keys/NESTED.EC",
"META-INF/keys/readme.txt",
"assets/not-signing.SF",
];
const writer = new ZipWriter(new Uint8ArrayWriter());
await writer.add("AndroidManifest.xml", new TextReader("fixture"));
for (const entry of entries)
await writer.add(entry, new TextReader(`inventory:${entry}`));
const path = join(root, "signing-candidates.apk");
await writeFile(path, await writer.close());
const inspected = await cli.run({
arguments: ["inspect-artifact", path, "--json"],
environment,
});
expect(inspected.exitCode, inspected.stderr).toBe(0);
const input = { inventory_evidence: [parseEvidence(inspected.json)] };
const projected = await cli.run({
arguments: [
"project-android-application-graph",
JSON.stringify(input),
"--json",
],
environment,
});
expect(projected.exitCode, projected.stderr).toBe(0);
const cliEvidence = parseEvidence(projected.json);
const cliGraph = androidApplicationProjectionResultSchema.parse(
cliEvidence.normalized_result,
);
const mcp = await call("project_android_application_graph", input);
expect(mcp.isError).not.toBe(true);
const mcpEvidence = parseEvidence(mcp.structuredContent);
const mcpGraph = androidApplicationProjectionResultSchema.parse(
mcpEvidence.normalized_result,
);
const expectedPaths = [
"META-INF/CERT.EC",
"META-INF/CERT.SF",
"META-INF/keys/NESTED.EC",
"META-INF/keys/NESTED.SF",
];
expect(cliGraph.components.signing.map(({ path }) => path).sort()).toEqual(
expectedPaths,
);
expect(mcpGraph.components.signing).toEqual(cliGraph.components.signing);
for (const component of cliGraph.components.signing)
expect(component.sha256).toBe(
createHash("sha256")
.update(`inventory:${component.path}`)
.digest("hex"),
);
expect(JSON.stringify(cliGraph.components.signing)).not.toContain(
"readme.txt",
);
expect(JSON.stringify(cliGraph.components.signing)).not.toContain(
"not-signing.SF",
);
expect(mcpEvidence).toEqual(cliEvidence);
},
);