Files
reverse-engineering-anything/src/jeb/JebProvider.test.ts
T
morluto c4cdb7658b fix(jeb)!: preserve engine refusal diagnostics
Parse JEB failure envelopes at the provider boundary and retain the original response, recovery facts, and selected inputs. Do not infer an unsupported target from undocumented error codes; preserve completed primary observations when metadata requests fail.

BREAKING CHANGE: JEB engine refusals now report execution_failure rather than unsupported_target. Consumers should inspect the retained upstream diagnostics for the engine's reason and recovery facts.
2026-10-27 06:22:10 +08:00

838 lines
26 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { createJebAnalysisProvider } from "../composition/jeb.js";
import { JebProvider } from "./JebProvider.js";
import type {
JebMcpConnection,
JebMcpConnectionTimeouts,
} from "./JebMcpConnection.js";
import {
AnalysisCapabilityUnavailableError,
AnalysisInputError,
AnalysisProtocolError,
AnalysisCancelledError,
AnalysisTimeoutError,
} from "../domain/analysisErrorCore.js";
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
import { parseEvidence } from "../domain/evidence.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
jebRequestSchema,
jebResultSchemas,
} from "../domain/jeb/jebAnalysis.js";
const scriptedConnection = (
responses: Readonly<Record<string, JsonValue | Error>>,
): JebMcpConnection & { readonly calls: unknown[][] } => {
const calls: unknown[][] = [];
return {
calls,
async connect() {
return Object.keys(responses);
},
async listTools() {
return Object.keys(responses);
},
async call(name, args) {
calls.push([name, args]);
const response = responses[name];
if (response === undefined)
throw new Error(`Unexpected JEB MCP call: ${name}`);
if (response instanceof Error) throw response;
return response;
},
async close() {},
};
};
const providerWith = (connection: JebMcpConnection) =>
new JebProvider({
environment: { REA_JEB_MCP_URL: "http://127.0.0.1:8425/mcp" },
connectionFactory: () => connection,
});
describe("JebProvider live availability", () => {
it("passes its validated environment timeout to the production connection", async () => {
let configured: JebMcpConnectionTimeouts | undefined;
const provider = createJebAnalysisProvider(
{ REA_JEB_MCP_TIMEOUT_MS: "900000" },
(_endpoint, timeouts) => {
configured = timeouts;
return scriptedConnection({});
},
);
expect(configured).toEqual({
requestTimeoutMs: 900_000,
discoveryTimeoutMs: 900_000,
});
await provider.close();
});
it("rejects an invalid JEB timeout before constructing its MCP connection", async () => {
let constructed = false;
const provider = createJebAnalysisProvider(
{ REA_JEB_MCP_TIMEOUT_MS: "0" },
() => {
constructed = true;
return scriptedConnection({});
},
);
expect(constructed).toBe(false);
await expect(provider.inspectAvailability()).resolves.toMatchObject({
status: "unavailable",
diagnostics: { setting: "REA_JEB_MCP_TIMEOUT_MS" },
});
await provider.close();
});
it("reports only the operations advertised by the live engine", async () => {
const connection = scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
list_units: { success: true, units: [] },
decompile_code_item: { success: true, text: "" },
});
const provider = providerWith(connection);
expect(await provider.inspectAvailability()).toMatchObject({
status: "available",
supportedOperations: [
"inspect_jeb_client",
"list_jeb_units",
"decompile_jeb_item",
],
});
expect(connection.calls).toEqual([["get_client_information", {}]]);
});
it("refreshes live operation support rather than reusing a readiness snapshot", async () => {
const connection = scriptedConnection({
get_client_information: { success: true, version: "5.48.0" },
});
let tools = ["get_client_information", "open_project"];
connection.listTools = async () => tools;
const provider = providerWith(connection);
expect(await provider.inspectAvailability()).toMatchObject({
supportedOperations: ["inspect_jeb_client", "open_jeb_project"],
});
tools = ["get_client_information"];
expect(await provider.inspectAvailability()).toMatchObject({
supportedOperations: ["inspect_jeb_client"],
});
expect(connection.calls).toHaveLength(2);
});
});
describe("JebProvider observations and failures", () => {
it("normalizes client information and caches engine identity", async () => {
const connection = scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0.202610071539",
gui_client: false,
startup_ts: 1791623439,
},
});
const provider = providerWith(connection);
const result = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (!result.ok) throw result.error;
const normalized = jebResultSchemas.inspect_jeb_client.parse(
result.value.result,
);
expect(normalized.engine.version).toBe("5.48.0.202610071539");
expect(normalized.startup_ts).toBe(1791623439);
expect(result.value.provider.version).toBe("5.48.0.202610071539");
});
it("reports unavailable when the engine endpoint is unreachable", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: new Error(
"connect ECONNREFUSED 127.0.0.1:8425",
),
list_units: new Error("connect ECONNREFUSED 127.0.0.1:8425"),
}),
);
const availability = await provider.inspectAvailability();
expect(availability.status).toBe("unavailable");
expect(availability.reason).toContain("Start a JEB client serving MCP");
expect(availability.reason).toContain("REA_JEB_MCP_URL");
const result = await provider.execute({
operation: "list_jeb_units",
input: { count: 10, index: 0 },
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toBeInstanceOf(AnalysisCapabilityUnavailableError);
expect(result.error.message).toContain("ECONNREFUSED");
});
it("maps transport failures during an operation to provider unavailability", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
list_units: new Error("connect ECONNREFUSED 127.0.0.1:8425"),
}),
);
const result = await provider.execute({
operation: "list_jeb_units",
input: { count: 10, index: 0 },
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(result.error).toBeInstanceOf(AnalysisCapabilityUnavailableError);
expect(result.error.message).toContain("ECONNREFUSED");
});
it("preserves engine refusal reasons without inferring unsupported target formats", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
decompile_code_item: {
success: false,
message:
"The decompilation failed because the item address does not reference an existing code item",
},
}),
);
const result = await provider.execute({
operation: "decompile_jeb_item",
input: { item_address: "LNope;->x()V", item_kind: "method" },
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(projectAnalysisError(result.error).message).toContain(
"does not reference an existing",
);
});
it("derives partial coverage only from a full engine page", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
list_units: {
success: true,
units: Array.from({ length: 3 }, (_, index) => ({
unit_path: `a/class${index}.dex`,
unit_type: "dex",
})),
},
}),
);
const full = await provider.execute({
operation: "list_jeb_units",
input: { count: 3, index: 0 },
});
if (!full.ok) throw full.error;
expect(
jebResultSchemas.list_jeb_units.parse(full.value.result).coverage,
).toBe("partial");
const last = await provider.execute({
operation: "list_jeb_units",
input: { count: 4, index: 0 },
});
if (!last.ok) throw last.error;
expect(
jebResultSchemas.list_jeb_units.parse(last.value.result).coverage,
).toBe("complete");
});
});
describe("JebProvider open project inventories", () => {
it.each(["input_files", "units"] as const)(
"rejects an omitted %s inventory from a successful project response",
async (omitted) => {
const project = {
success: true,
name: "empty-project",
creation_datetime: "2026-10-12T00:00:00",
input_files: [],
units: [],
};
const response = { ...project };
delete response[omitted];
const provider = providerWith(
scriptedConnection({
get_client_information: { success: true, version: "5.48.0" },
open_project: response,
}),
);
const result = await provider.execute({
operation: "open_jeb_project",
input: { path: "probe.apk" },
});
if (result.ok) throw new Error("Expected an omitted inventory failure");
expect(result.error).toBeInstanceOf(AnalysisProtocolError);
},
);
it("preserves explicitly empty project inventories", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: { success: true, version: "5.48.0" },
open_project: {
success: true,
name: "empty-project",
creation_datetime: "2026-10-12T00:00:00",
input_files: [],
units: [],
},
}),
);
const result = await provider.execute({
operation: "open_jeb_project",
input: { path: "probe.apk" },
});
if (!result.ok) throw result.error;
expect(result.value.result).toMatchObject({
input_files: [],
units: [],
});
});
});
describe("JebProvider target and endpoint selection", () => {
it("keeps the implicitly selected unit unknown when unit_path is omitted", async () => {
const provider = providerWith(
scriptedConnection({
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
decompile_code_item: { success: true, text: "int x() { return 1; }" },
}),
);
const result = await provider.execute({
operation: "decompile_jeb_item",
input: { item_address: "LFoo;->x()I", item_kind: "method" },
});
expect(result.ok).toBe(true);
if (!result.ok) return;
const normalized = result.value.result as {
readonly unit_path: string | null;
};
expect(normalized.unit_path).toBeNull();
expect(result.value.limitations.join(" ")).toContain("implicitly");
});
it("rejects unusable endpoints at construction", () => {
const rejection = (value: string): AnalysisInputError => {
try {
new JebProvider({ environment: { REA_JEB_MCP_URL: value } });
} catch (cause) {
if (cause instanceof AnalysisInputError) return cause;
throw cause;
}
throw new Error("endpoint should have been rejected");
};
expect(rejection("ftp://example.invalid/mcp").cause).toMatch(
/http and https/,
);
expect(rejection("http://user:pass@127.0.0.1:8425/mcp").cause).toMatch(
/credentials/,
);
});
});
it("refuses remote endpoints before constructing a connection", () => {
expect(
() =>
new JebProvider({
environment: { REA_JEB_MCP_URL: "http://example.com/mcp" },
}),
).toThrow(AnalysisInputError);
});
it.each(["list_jeb_units", "decompile_jeb_item"] as const)(
"rejects an omitted successful payload for %s",
async (operation) => {
const provider = providerWith(
scriptedConnection({
get_client_information: { success: true, version: "5.48.0" },
list_units: { success: true },
decompile_code_item: { success: true },
}),
);
const result = await provider.execute(
operation === "list_jeb_units"
? { operation, input: { count: 10, index: 0 } }
: {
operation,
input: { item_address: "LTest;->x()V", item_kind: "method" },
},
);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisProtocolError);
},
);
it("forwards in-flight cancellation to the MCP call", async () => {
const controller = new AbortController();
let entered!: () => void;
const started = new Promise<void>((resolve) => {
entered = resolve;
});
const provider = providerWith({
async connect() {
return ["get_client_information"];
},
async listTools() {
return ["get_client_information"];
},
async close() {},
async call(_name, _args, signal) {
entered();
await new Promise<void>((_resolve, reject) =>
signal!.addEventListener(
"abort",
() =>
reject(
new AnalysisCancelledError(_name, { cause: signal!.reason }),
),
{ once: true },
),
);
return {};
},
});
const pending = provider.execute(
{ operation: "inspect_jeb_client", input: {} },
{ signal: controller.signal },
);
await started;
controller.abort();
const result = await pending;
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.cause).toBe(controller.signal.reason);
}
});
it("refreshes cached unknown metadata only through an explicit information probe", async () => {
const responses: Record<string, JsonValue> = {
get_client_information: { success: true, gui_client: false },
list_units: { success: true, units: [] },
};
const connection = scriptedConnection(responses);
const provider = providerWith(connection);
try {
const first = await provider.execute({
operation: "list_jeb_units",
input: { count: 2, index: 0 },
});
if (!first.ok) throw first.error;
expect(first.value.provider.version).toBeNull();
responses.get_client_information = {
success: true,
version: "5.49.0",
gui_client: true,
};
const inspected = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (!inspected.ok) throw inspected.error;
const after = await provider.execute({
operation: "list_jeb_units",
input: { count: 2, index: 0 },
});
if (!after.ok) throw after.error;
expect(after.value.provider.version).toBe("5.49.0");
expect(after.value.result).toMatchObject({
engine: { version: "5.49.0", gui_client: true },
});
expect(connection.calls.map(([name]) => name)).toEqual([
"list_units",
"get_client_information",
"get_client_information",
"list_units",
]);
} finally {
await provider.close();
}
});
it("keeps concurrent client inspections bound to their own parsed metadata", async () => {
const connection = scriptedConnection({});
let replies = 0;
connection.call = async () => ({
success: true,
version: `inspection-${++replies}`,
gui_client: replies === 1,
});
const provider = providerWith(connection);
try {
const results = await Promise.all([
provider.execute({ operation: "inspect_jeb_client", input: {} }),
provider.execute({ operation: "inspect_jeb_client", input: {} }),
]);
for (const [index, result] of results.entries()) {
if (!result.ok) throw result.error;
expect(result.value.provider.version).toBe(`inspection-${index + 1}`);
expect(result.value.result).toMatchObject({
engine: {
version: result.value.provider.version,
gui_client: index === 0,
},
});
}
} finally {
await provider.close();
}
});
it("preserves provider-authored timeout guidance when projecting the requested operation", async () => {
const reason = new Error("selected deadline elapsed");
const timeout = new AnalysisTimeoutError("list_units", 123, {
cause: reason,
userMessage: "The selected MCP request budget elapsed.",
remediationAction: "Increase the selected MCP request budget.",
});
const provider = providerWith(scriptedConnection({ list_units: timeout }));
try {
const result = await provider.execute({
operation: "list_jeb_units",
input: { count: 2, index: 0 },
});
if (result.ok) throw new Error("Timed-out request passed");
expect(result.error).toMatchObject({
operation: "list_jeb_units",
timeoutMs: 123,
userMessage: timeout.userMessage,
remediationAction: timeout.remediationAction,
});
expect(result.error.cause).toBe(reason);
} finally {
await provider.close();
}
});
it("project-open refusal retains the selected engine-host artifact path", async () => {
const provider = providerWith(
scriptedConnection({
open_project: {
success: false,
message: "The input file could not be processed",
},
}),
);
const result = await provider.execute({
operation: "open_jeb_project",
input: { path: "/targets/Example.apk" },
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Refused artifact accepted");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "execution_failure",
message: "The input file could not be processed",
details: {
diagnostics: {
input: { path: "/targets/Example.apk" },
upstream_response: {
success: false,
message: "The input file could not be processed",
},
},
},
});
await provider.close();
});
it.each([
{
operation: "list_jeb_units",
input: {},
tool: "list_units",
payload: {
success: true,
units: [{ unit_path: "sample.dex", unit_type: "dex" }],
},
},
{
operation: "open_jeb_project",
input: { path: "/targets/Example.apk" },
tool: "open_project",
payload: {
success: true,
name: "Example",
input_files: [],
units: [{ unit_path: "sample.dex", unit_type: "dex" }],
},
},
{
operation: "decompile_jeb_item",
input: {
item_kind: "type",
item_address: "LExample;",
unit_path: "sample.dex",
},
tool: "decompile_code_item",
payload: { success: true, text: "class Example {}" },
},
])(
"retains completed $operation output when later client metadata is cancelled",
async ({ operation, input, tool, payload }) => {
const request = jebRequestSchema.parse({ operation, input });
const controller = new AbortController();
const reason = { source: "metadata-cancellation" };
const primary = jsonValueSchema.parse(payload);
const connection = scriptedConnection({ [tool]: primary });
const call = connection.call.bind(connection);
connection.call = async (name, args, signal) => {
if (name === "get_client_information") {
controller.abort(reason);
throw new AnalysisCancelledError(name, { cause: reason });
}
return call(name, args, signal);
};
const provider = providerWith(connection);
const result = await provider.execute(request, {
signal: controller.signal,
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Cancelled metadata lookup accepted");
expect(result.error).toMatchObject({
_tag: "AnalysisCancelledError",
operation,
cause: reason,
});
const partial = parseEvidence(result.error.partialObservation);
expect(partial.operation).toBe(operation);
expect(partial.parameters).toEqual(request.input);
expect(partial.normalized_result).toMatchObject({
status: "partial",
observation: payload,
engine_metadata: null,
});
expect(partial.raw_result).toEqual(payload);
await provider.close();
const control = providerWith(
scriptedConnection({
[tool]: primary,
get_client_information: {
success: true,
version: "5.48.0",
gui_client: false,
},
}),
);
const completed = await control.execute(request);
expect(completed.ok).toBe(true);
if (!completed.ok) throw completed.error;
expect(completed.value.result).toMatchObject({
engine: { version: "5.48.0" },
});
await control.close();
},
);
// The published JEB 5.47 catalog leaves codes open and describes recovery as
// upstream advice, not permission for REA to invoke another engine operation.
describe("JEB refusal input and response preservation", () => {
const requests = [
{
operation: "inspect_jeb_client",
input: {},
tool: "get_client_information",
},
{
operation: "open_jeb_project",
input: { path: "/targets/input.apk" },
tool: "open_project",
},
{
operation: "list_jeb_units",
input: { count: 10, index: 0 },
tool: "list_units",
},
{
operation: "decompile_jeb_item",
input: {
item_address: "LExample;->x()V",
item_kind: "method",
unit_path: "0/1",
},
tool: "decompile_code_item",
},
] as const;
it.each(requests)(
"retains unknown refusal facts for $operation",
async ({ tool, ...request }) => {
const payload = {
success: false,
message: "Engine state needs attention",
error: {
code: "future_vendor_code",
recovery_required_before_retry: false,
recovery_tool: "open_project",
vendor_detail: { phase: "analysis" },
},
vendor_trace: ["first", "second"],
};
const connection = scriptedConnection({ [tool]: payload });
const provider = providerWith(connection);
const result = await provider.execute(request);
if (result.ok) throw new Error("Engine refusal accepted");
expect(projectAnalysisError(result.error)).toMatchObject({
code: "execution_failure",
message: payload.message,
details: {
diagnostics: { input: request.input, upstream_response: payload },
},
});
expect(connection.calls).toHaveLength(1);
await provider.close();
},
);
it("omits undefined optional inputs when retaining JSON diagnostics", async () => {
const provider = providerWith(
scriptedConnection({
list_units: { success: false, message: "Refused" },
}),
);
const result = await provider.execute({
operation: "list_jeb_units",
input: {
count: 10,
index: 0,
filter: undefined,
parent_unit_path: undefined,
},
});
if (result.ok) throw new Error("Engine refusal accepted");
expect(result.error.diagnostics?.input).toEqual({ count: 10, index: 0 });
expect(result.error.diagnostics?.input).not.toHaveProperty("filter");
await provider.close();
});
});
describe("JEB refusal recovery and presentation", () => {
it.each([undefined, false, true])(
"preserves recovery flag %s without invoking recovery",
async (flag) => {
const payload = {
success: false,
error: {
code: "not_documented",
recovery_tool: "run_script",
...(flag === undefined
? {}
: { recovery_required_before_retry: flag }),
},
};
const connection = scriptedConnection({
get_client_information: payload,
});
const provider = providerWith(connection);
const result = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (result.ok) throw new Error("Engine refusal accepted");
expect(result.error.diagnostics?.upstream_response).toEqual(payload);
expect(connection.calls).toEqual([["get_client_information", {}]]);
await provider.close();
},
);
it.each([
{
payload: {
success: false,
message: "outer reason",
error: { message: "", code: "code" },
},
expected: "outer reason",
},
{
payload: {
success: false,
message: " ",
error: { message: "", code: "code" },
},
expected: "code",
},
{ payload: { success: false }, expected: "The engine refused the request" },
])(
"selects a nonempty refusal message: $expected",
async ({ payload, expected }) => {
const provider = providerWith(
scriptedConnection({ get_client_information: payload }),
);
const result = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (result.ok) throw new Error("Engine refusal accepted");
expect(projectAnalysisError(result.error).message).toBe(expected);
expect(result.error.diagnostics?.upstream_response).toEqual(payload);
await provider.close();
},
);
it("availability retains a reachable engine's refusal rather than prescribing connection setup", async () => {
const payload = {
success: false,
message: "Client initialization pending",
error: {
code: "future_code",
recovery_required_before_retry: true,
recovery_tool: "get_client_information",
},
};
const provider = providerWith(
scriptedConnection({ get_client_information: payload }),
);
expect(await provider.inspectAvailability()).toMatchObject({
status: "unavailable",
code: "runtime_unavailable",
reason: payload.message,
diagnostics: { upstream_response: payload, input: {} },
});
await provider.close();
});
it.each([
{ success: false, error: { recovery_required_before_retry: "false" } },
{ message: "missing success" },
{ success: "false" },
])("retains malformed envelopes as protocol failures", async (payload) => {
const provider = providerWith(
scriptedConnection({ get_client_information: payload }),
);
const result = await provider.execute({
operation: "inspect_jeb_client",
input: {},
});
if (result.ok) throw new Error("Malformed envelope accepted");
expect(result.error).toBeInstanceOf(AnalysisProtocolError);
expect(result.error.diagnostics?.upstream_response).toEqual(payload);
await provider.close();
});
});