* test: prefer structured assertions over recovery prose Lock regex, IDA, Hopper/Kuna, MCP budget, capture-heap, and pcap failures on error codes and reported limits instead of full recovery sentences. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: keep decoder heap negatives off the Windows reserve "512 MiB" also appears in the Bun Windows reserve sentence. The negative check stays on "512 MiB (source setting)", which is the other adapter's heap. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: keep the one-retry cap and deadline recovery tokens The lost-connection guidance must say once only, so an unlimited "whenever it is safe" rewrite fails. Deadline recovery still has to name set, restart, the floor, and the maximum constant. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: keep heap, MCP, pcap, and preload recovery tokens Codex still wanted the set/restart/floor/maximum heap guidance, MCP default/floor/maximum bounds, Wireshark install guidance, and the dropped-preload attribution limit as separate tokens. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: keep shared-context and transport recovery tokens Shared-context capture must still exclude requests without a known root or descendant frame. Shutdown must end the REA process before starting again, and a failed HTTP listen must offer another port or stopping the process that holds it. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: keep handoff, input-check, and budget format tokens Skill and server guidance must still tell the user to restart the MCP client. Process failures must still say to check the input files. Invalid MCP phase budgets must still say positive decimal integer and ms. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: bind budget floors and active deadline sources The phase-budget minimum is the start of "from N to M ms", so a digit inside the maximum no longer satisfies it. Capability text must contain the active "N ms (source ...)" pair, not the default number and a heap source mentioned apart. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: bind heap MiB sources and pcap host tokens A configured 256 MiB request must appear as "256 MiB (source setting)", so a byte-sized 256 no longer passes. Unsupported pcap hosts must still name Linux, util-linux, Windows, and owned Job Objects as separate tokens. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: bind the selected decoder heap to MiB and source The two-adapter limitation must contain "256 MiB (source setting)". The exhaustion message binds that capacity as "256 MiB V8 old-generation heap (source setting)", so a byte-sized 256 no longer passes. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: bind doctor client seconds and prlimit absolute guidance Doctor recovery must name startup_timeout_sec of the policy's seconds, so a hardcoded 60 no longer passes while the policy stays 30. A relative prlimit must say the value must be absolute and tell the user to check REA_TSHARK_PRLIMIT_COMMAND. Co-authored-by: morluto <morluto@users.noreply.github.com> * test: assert integrity codes for rehashed evidence and coverage Independently rehashed process Evidence and a tampered digest project evidence_integrity_mismatch. An unsupported comparison operation stays invalid_request. Unsupported inventory coverage status is an integrity failure whose reason carries the coverage.status schema issue. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { expect } from "vitest";
|
|
|
|
import {
|
|
ANDROID_APPLICATION_GRAPH_EXAMPLE,
|
|
APPLE_APPLICATION_GRAPH_EXAMPLE,
|
|
HARMONY_APPLICATION_GRAPH_EXAMPLE,
|
|
MACOS_APPLICATION_GRAPH_EXAMPLE,
|
|
} from "../../../src/contracts/mobileApplicationGraphExamples.js";
|
|
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest.for([
|
|
{
|
|
name: "Android",
|
|
command: "project-android-application-graph",
|
|
operation: "project_android_application_graph",
|
|
example: ANDROID_APPLICATION_GRAPH_EXAMPLE,
|
|
},
|
|
{
|
|
name: "IPA",
|
|
command: "project-apple-application-graph",
|
|
operation: "project_apple_application_graph",
|
|
example: APPLE_APPLICATION_GRAPH_EXAMPLE,
|
|
},
|
|
{
|
|
name: "macOS",
|
|
command: "project-apple-application-graph",
|
|
operation: "project_apple_application_graph",
|
|
example: MACOS_APPLICATION_GRAPH_EXAMPLE,
|
|
},
|
|
{
|
|
name: "Harmony",
|
|
command: "project-harmony-application-graph",
|
|
operation: "project_harmony_application_graph",
|
|
example: HARMONY_APPLICATION_GRAPH_EXAMPLE,
|
|
},
|
|
] as const)(
|
|
"CLI executes the canonical $name graph example without target files",
|
|
async ({ command, operation, example }, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-mobile-graph-example-");
|
|
const input = JSON.stringify(example);
|
|
const path = join(root, "example.json");
|
|
await writeFile(path, input);
|
|
for (const argument of [input, path]) {
|
|
const result = await cli.run({
|
|
arguments: [command, argument, "--json"],
|
|
cwd: root,
|
|
});
|
|
expect(result.exitCode, result.stdout).toBe(0);
|
|
const evidence = parseEvidence(result.json);
|
|
expect(evidence.operation).toBe(operation);
|
|
expect(evidence.normalized_result).toMatchObject({
|
|
coverage: { status: "partial", inventory_complete: true },
|
|
});
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"CLI still rejects an unsupported inventory coverage status",
|
|
async ({ cli }) => {
|
|
const original = ANDROID_APPLICATION_GRAPH_EXAMPLE.inventory_evidence[0]!;
|
|
const inventory = artifactInventoryResultSchema.parse(
|
|
original.normalized_result,
|
|
);
|
|
const evidence = createEvidence(
|
|
{ path: "Fixture.apk", sha256: "a".repeat(64), format: "apk" },
|
|
original.provider,
|
|
{
|
|
operation: "inventory_artifact",
|
|
parameters: {},
|
|
result: {
|
|
...inventory,
|
|
coverage: { scope: "root", status: "invalid" },
|
|
},
|
|
confidence: "observed",
|
|
authority: "shipped-artifact",
|
|
},
|
|
);
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"project-android-application-graph",
|
|
JSON.stringify({ inventory_evidence: [evidence] }),
|
|
"--json",
|
|
],
|
|
});
|
|
expect(result.exitCode).toBe(1);
|
|
const projected = result.json;
|
|
expect(projected).toMatchObject({
|
|
code: "evidence_integrity_mismatch",
|
|
category: "integrity_mismatch",
|
|
});
|
|
const reason = inventoryIntegrityReason(projected);
|
|
expect(JSON.parse(reason)).toMatchObject([
|
|
{
|
|
code: "invalid_value",
|
|
values: ["complete", "unknown"],
|
|
path: ["coverage", "status"],
|
|
},
|
|
]);
|
|
expect(projected).toMatchObject({
|
|
message: reason,
|
|
details: { reason },
|
|
});
|
|
},
|
|
);
|
|
|
|
const inventoryIntegrityReason = (projected: unknown): string => {
|
|
if (
|
|
projected === null ||
|
|
typeof projected !== "object" ||
|
|
!("details" in projected) ||
|
|
projected.details === null ||
|
|
typeof projected.details !== "object" ||
|
|
!("reason" in projected.details) ||
|
|
typeof projected.details.reason !== "string"
|
|
)
|
|
throw new Error("Inventory rejection omitted an integrity reason");
|
|
return projected.details.reason;
|
|
};
|