Files
rea/src/application/NativeApiInspection.test.ts
T
morluto 4fbc501121 feat(native): add inspection primitives, dispatch traces and approved UI observation (#500)
* fix(hopper): normalize string-backed addresses and update verification

* feat(native): add inspection primitives, dispatch traces and approved UI observation

* test: prioritize real artifact E2E and captured golden regressions

* fix(process): queue observed output before reactive rendering actions

* test: prune redundant cases and strengthen boundary assertions

* fix(cli): route native value tracing through shared workflows

* test: replace synthetic success with real workflows and captured data

* fix(catalog): separate portable metadata from native host availability
2026-10-04 16:02:36 +08:00

105 lines
3.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { functionDossierSchema } from "../domain/hopperValues.js";
import { ghidraFunctionDossier } from "../domain/hopperValues.fixture.js";
import { nativeApiBoundarySchema } from "../domain/nativeApiBoundary.js";
import { projectNativeApiInspection } from "./NativeApiInspection.js";
describe("native API inspection", () => {
it("preserves structured type and jump-table boundary evidence", () => {
const dossier = functionDossierSchema.parse(ghidraFunctionDossier());
expect(projectNativeApiInspection(dossier)).toMatchObject({
procedure: { address: "0x401000", name: "fixture_main" },
boundary: {
available: true,
return_type: { data_type: "int", confidence: "medium" },
jump_tables: [
{
dispatch_address: "0x401010",
data_sources: [{ address: "0x403000" }],
mappings: [
{
target_address: "0x401020",
},
],
},
],
},
unsupported_branches: [],
residual_unknowns: [],
});
});
it("preserves unsupported provider branches as residual unknowns", () => {
const value = ghidraFunctionDossier();
if (typeof value !== "object" || value === null || Array.isArray(value))
throw new TypeError("Ghidra dossier fixture is invalid");
const dossier = functionDossierSchema.parse(
Object.fromEntries(
Object.entries(value).filter(([key]) => key !== "native_api"),
),
);
expect(projectNativeApiInspection(dossier)).toMatchObject({
boundary: { available: false },
unsupported_branches: [
"structured-boundary-types",
"jump-table-data-mapping",
],
residual_unknowns: [
expect.stringContaining("boundary types"),
expect.stringContaining("jump table"),
],
});
});
it("reports missing jump-table targets as an explicit unknown", () => {
const value = ghidraFunctionDossier();
const dossier = functionDossierSchema.parse(value);
const boundary = dossier.native_api;
if (boundary?.available !== true)
throw new TypeError("Ghidra native API fixture is unavailable");
const table = boundary.jump_tables[0];
if (table === undefined)
throw new TypeError("Ghidra jump-table fixture is unavailable");
const incomplete = functionDossierSchema.parse({
...dossier,
native_api: {
...boundary,
jump_tables: [{ ...table, mappings: [] }],
},
});
expect(projectNativeApiInspection(incomplete).residual_unknowns).toContain(
"Which case values and targets belong to the dispatch at 0x401010?",
);
});
it("rejects boundary types placed in the wrong ABI role", () => {
const boundary = functionDossierSchema.parse(
ghidraFunctionDossier(),
).native_api;
if (boundary === null || boundary.available !== true)
throw new TypeError("Ghidra native API fixture is unavailable");
expect(
nativeApiBoundarySchema.safeParse({
...boundary,
return_type: { ...boundary.return_type, role: "parameter" },
}).success,
).toBe(false);
expect(
nativeApiBoundarySchema.safeParse({
...boundary,
parameters: [
{
...boundary.return_type,
role: "return",
},
],
}).success,
).toBe(false);
});
});