mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
82fd223638
Move shared function, dossier, inventory, reference and output semantics from the legacy Hopper module to native domain owners. Migrate provider, adapter, test and verifier consumers directly.
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
import { Cli } from "incur";
|
|
import pino from "pino";
|
|
import { stat } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect, it, vi } from "vitest";
|
|
|
|
import { inspectAnalysisView } from "../../../src/application/analysisView/AnalysisViewService.js";
|
|
import { registerCoreBinaryCommands } from "../../../src/cli/coreBinaryCommands.js";
|
|
import type { CliInstance } from "../../../src/cli/types.js";
|
|
import { CLI_COMMANDS } from "../../../src/cliCommandNames.js";
|
|
import { createEvidence } from "../../../src/domain/evidence.js";
|
|
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
|
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
// A complete analyze_function Evidence as runDirectAnalysis would return it:
|
|
// subject-bound (so the summary view has an artifact) with a schema-valid dossier.
|
|
const cannedCompleteEvidence = () =>
|
|
createEvidence(
|
|
{ path: "/fixtures/native.exe", format: "pe", sha256: "a".repeat(64) },
|
|
{ id: "ghidra", name: "Ghidra", version: "12.1.4" },
|
|
{
|
|
operation: "analyze_function",
|
|
parameters: { procedure: "main" },
|
|
result: functionDossierSchema.parse(ghidraFunctionDossier()),
|
|
rawResult: null,
|
|
},
|
|
);
|
|
|
|
const harness = () => {
|
|
const runDirectAnalysis = vi.fn(
|
|
async () => cannedCompleteEvidence() as unknown,
|
|
);
|
|
const cli = Cli.create("rea-fn-detail-test", { version: "1", sync: false });
|
|
registerCoreBinaryCommands(
|
|
cli as unknown as CliInstance,
|
|
pino({ enabled: false }),
|
|
runDirectAnalysis as never,
|
|
{},
|
|
);
|
|
const command = Cli.toCommands.get(cli)?.get(CLI_COMMANDS.function);
|
|
if (command === undefined)
|
|
throw new Error("function command was not registered");
|
|
const argsSchema = Reflect.get(command, "args");
|
|
const optionsSchema = Reflect.get(command, "options");
|
|
const execute = Reflect.get(command, "run");
|
|
if (typeof execute !== "function")
|
|
throw new Error("function command has no run callback");
|
|
const invoke = (options: Record<string, unknown>): Promise<unknown> =>
|
|
execute({
|
|
args: (argsSchema as { parse(v: unknown): unknown }).parse({
|
|
path: "/fixtures/native.exe",
|
|
address: "main",
|
|
}),
|
|
options: (optionsSchema as { parse(v: unknown): unknown }).parse(options),
|
|
});
|
|
return { runDirectAnalysis, invoke };
|
|
};
|
|
|
|
it("defaults to the complete dossier and runs analysis", async () => {
|
|
const h = harness();
|
|
const out = (await h.invoke({})) as { operation?: string };
|
|
expect(h.runDirectAnalysis).toHaveBeenCalledOnce();
|
|
expect(out.operation).toBe("analyze_function");
|
|
expect(
|
|
(out as { normalized_result?: unknown }).normalized_result,
|
|
).toBeDefined();
|
|
});
|
|
|
|
it("rejects summary delivery without --evidence_output before any analysis", async () => {
|
|
const h = harness();
|
|
const out = await h.invoke({ detail: "summary" });
|
|
expect(h.runDirectAnalysis).not.toHaveBeenCalled();
|
|
expect(JSON.stringify(out)).toContain("evidence_output");
|
|
});
|
|
|
|
it("summary delivery returns the compact view and writes a bundle inspect-analysis-view pages", async () => {
|
|
const h = harness();
|
|
const directory = await createTestTempDirectory("rea-fn-detail-");
|
|
const destination = join(directory, "complete.json");
|
|
const out = (await h.invoke({
|
|
detail: "summary",
|
|
evidence_output: destination,
|
|
})) as {
|
|
summary: {
|
|
normalized_result: { kind: string; parent_evidence_id: string };
|
|
};
|
|
evidence_bundle: { path: string; bytes: number };
|
|
};
|
|
expect(h.runDirectAnalysis).toHaveBeenCalledOnce();
|
|
expect(out.summary.normalized_result.kind).toBe("summary");
|
|
expect(typeof out.summary.normalized_result.parent_evidence_id).toBe(
|
|
"string",
|
|
);
|
|
expect(out.evidence_bundle.bytes).toBeGreaterThan(0);
|
|
expect((await stat(destination)).size).toBeGreaterThan(0);
|
|
// The saved bundle is the evidence-file source inspect-analysis-view reads back.
|
|
const parentId = out.summary.normalized_result.parent_evidence_id;
|
|
const page = await inspectAnalysisView({
|
|
source: { kind: "evidence-file", path: destination, evidence_id: parentId },
|
|
view: { kind: "native", facet: "pseudocode", offset: 0, limit: 16 },
|
|
});
|
|
if (!page.ok) throw page.error;
|
|
expect(page.value.parameters).toMatchObject({ parent_evidence_id: parentId });
|
|
expect(page.value.normalized_result).toMatchObject({
|
|
kind: "native",
|
|
item: {
|
|
text: functionDossierSchema
|
|
.parse(ghidraFunctionDossier())
|
|
.pseudocode.slice(0, 16),
|
|
},
|
|
});
|
|
});
|