Files
rea/tests/boundary/android/jadxProtocol.test.ts
T
morluto 76b56957e9 refactor(android): own search summaries at the producer boundary
Reuse validated class pages for inline pagination audits while retaining complete provider replies. Move retention into the shared Android workflow and remove the MCP-only service wrapper and application text reparser. Preserve unknown JSON metadata, late cancellation and retention failures.
2026-10-23 21:12:42 +08:00

157 lines
4.6 KiB
TypeScript

import { expect, it } from "vitest";
import {
parseJadxEnvelope,
parseJadxClassPage,
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({
content: [{ type: "text", text: "reported error" }],
isError: 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);
});
it("preserves JSON page and envelope annotations while rejecting unsupported inventory scope", () => {
const page = {
scope: "top_level",
total_classes: 1,
offset: 0,
next_offset: 1,
limit: 4096,
items: ["fixture.Target"],
audit: { source: "雪", missing: null },
};
Object.defineProperty(page, "__proto__", {
value: { observed: "page" },
enumerable: true,
});
expect(parseJadxClassPage(page)).toEqual(page);
expect(Object.hasOwn(parseJadxClassPage(page), "__proto__")).toBe(true);
expect(() => parseJadxClassPage({ ...page, scope: "all_dex" })).toThrow();
const response = {
content: [
{
type: "text",
text: JSON.stringify(page),
annotations: { audience: ["assistant"] },
},
],
_meta: { page: 1 },
};
Object.defineProperty(response, "__proto__", {
value: { observed: "envelope" },
enumerable: true,
});
Object.defineProperty(response.content[0], "__proto__", {
value: { observed: "content" },
enumerable: true,
});
const parsed = parseJadxEnvelope(response, "search_android_classes");
expect(parsed).toEqual(response);
expect(Object.hasOwn(parsed, "__proto__")).toBe(true);
expect(Object.hasOwn(parsed.content[0], "__proto__")).toBe(true);
});