Files
rea/tests/boundary/android/jadxProtocol.test.ts
morluto 3fa972af7c fix(android): preserve typed JVM memory failures during loading
Translate actual OutOfMemoryError at the bridge boundary before upstream stringifies it. Preserve observed heap bytes, missing reasons, coroutine context and cancellation; retain SessionHolder ownership and load options. Restrict heap-specific remediation to known heap reasons and parse named bridge failure contracts once.

Coordinate bridge protocol v4, clarify doctor configuration versus target capacity, and preserve the memory primary across cleanup failure/retry. Verify controlled immediate/late/inline continuation outcomes with the pinned JAR and real JDK, focused tests, and tiny real CLI/MCP method workflows.

Actual heap exhaustion, the reported APK, pre-handshake failures, Linux and Windows remain unverified; no stress or profiling run.

Fixes #3517
2026-10-18 00:48:12 +08:00

112 lines
3.3 KiB
TypeScript

import { expect, it } from "vitest";
import {
parseJadxEnvelope,
parseJadxJson,
normalizeJadxText,
parseJadxFailure,
} from "../../../src/android/JadxProtocol.js";
import { androidInputSchemas } from "../../../src/domain/android/androidAnalysis.js";
it("recognizes named bridge failures without reinterpreting arbitrary engine text", () => {
const failure = {
kind: "jvm-memory-failure",
memory_area: "heap",
exception_type: "java.lang.OutOfMemoryError",
message: "Java heap space",
max_heap_bytes: 67108864,
};
expect(parseJadxFailure(JSON.stringify(failure))).toEqual(failure);
for (const value of [
"load failed: cannot read /inputs/Java heap space.apk",
"{malformed Java heap space",
JSON.stringify({
...failure,
exception_type: "java.lang.IllegalArgumentException",
}),
JSON.stringify({ ...failure, max_heap_bytes: -1 }),
JSON.stringify({ ...failure, memory_area: "native" }),
JSON.stringify({ ...failure, arbitrary: true }),
])
expect(parseJadxFailure(value)).toBeUndefined();
});
it("validates provider text envelopes and rejects unsupported content rather than dropping it", () => {
expect(
parseJadxEnvelope(
{ content: [{ type: "text", text: "reported error" }], isError: true },
"inspect_android_package",
),
).toEqual({ text: "reported error", failed: true });
for (const response of [
{ content: [] },
{ content: [{ type: "image", data: "AA==", mimeType: "image/png" }] },
{
content: [
{ type: "text", text: "first" },
{ type: "text", text: "second" },
],
},
])
expect(() =>
parseJadxEnvelope(response, "inspect_android_package"),
).toThrowError("unsupported MCP content envelope");
expect(() =>
parseJadxJson("{malformed", "inspect_android_package"),
).toThrowError("malformed JSON text");
});
it("retains UTF-8 source and recognizes only the producer's terminal truncation marker", () => {
expect(normalizeJadxText("日本語")).toEqual({
text: "日本語",
status: "complete",
reported_total_bytes: null,
});
const text = "日本語\n\n... [truncated: exceeds 9 bytes, total 100 bytes]";
expect(normalizeJadxText(text)).toEqual({
text,
status: "partial",
reported_total_bytes: 100,
});
expect(normalizeJadxText(text + "\nmore actual source").status).toBe(
"complete",
);
});
it("rejects invalid APK selectors and accepts an explicit zero overload without a confirmation flag", () => {
const input = {
path: "/fixture.apk",
class_name: "fixture.Target",
method_name: "choose",
};
expect(
androidInputSchemas.inspect_android_method.safeParse({
...input,
overload_index: 0,
}).success,
).toBe(true);
expect(
androidInputSchemas.inspect_android_method.safeParse({
...input,
overload_index: -1,
}).success,
).toBe(false);
expect(
androidInputSchemas.inspect_android_method.safeParse({
...input,
overload_index: 0.5,
}).success,
).toBe(false);
expect(
androidInputSchemas.inspect_android_method.safeParse({
...input,
approved: true,
}).success,
).toBe(false);
expect(
androidInputSchemas.search_android_classes.safeParse({
path: "/fixture.apk",
query: "",
}).success,
).toBe(true);
});