Files
reverse-engineering-anything/tests/boundary/cli/cliDomainFieldSchema.test.ts
T
morlutoandCursor Agent 5c2917266d fix(inputs): validate command admission and preserve valid MCP requests (#9901)
* fix(cli): validate named inputs before target admission

* fix(mcp): preserve valid inputs when advertised schema references widen

* test(inputs): reach session behavior with admitted inputs

One-shot admission now rejects incomplete requests before a session exists. Lifecycle tests supply the required fields, and unknown arguments are rejected before provider issues are retained.

* fix(cli): preserve Electron fuse path spelling

Anchor relative fuse paths without stripping dot segments, and let an empty path reach the operation so it still reports invalid_request.

* fix(cli): reject outline-depth unless detail is summary

The advertised schema already required detail summary whenever outline-depth
was present. The parser still applied the full default and accepted that
combination, so its output failed the same schema. Parsing now rejects it
before the command runs.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-11-11 00:43:30 -05:00

280 lines
7.3 KiB
TypeScript

import { Ajv2020 } from "ajv/dist/2020.js";
import type { AnySchema } from "ajv";
import { Cli, z } from "incur";
import { expect, it } from "vitest";
import { createCli } from "../../../src/cli.js";
type CliInstance = ReturnType<typeof createCli>;
type CommandMap = NonNullable<ReturnType<typeof Cli.toCommands.get>>;
const registeredCommands: WeakMap<object, CommandMap> = Cli.toCommands;
const serve = async (cli: CliInstance, argv: readonly string[]) => {
let stdout = "";
let exitCode = 0;
await cli.serve([...argv], {
env: {},
exit: (code) => {
exitCode = code;
},
stdout: (text) => {
stdout += text;
},
});
expect(exitCode, argv.join(" ")).toBe(0);
return z
.object({
args: z
.object({ properties: z.record(z.string(), z.unknown()) })
.passthrough()
.optional(),
options: z
.object({ properties: z.record(z.string(), z.unknown()) })
.passthrough()
.optional(),
})
.parse(JSON.parse(stdout));
};
const absoluteMapping =
process.platform === "win32" ? "C:\\mapping.txt" : "/tmp/mapping.txt";
it("advertises the domain constraints for android, adb settings, and browser selectors", async () => {
const cli = createCli({});
const ajv = new Ajv2020({ strict: false, validateFormats: false });
const validate = async (
command: string,
group: "args" | "options",
value: Record<string, unknown>,
) => {
const advertised = await serve(cli, [command, "--schema", "--json"]);
const schema = advertised[group];
expect(schema, command).toBeDefined();
return ajv.compile(schema as AnySchema)(value);
};
expect(
await validate("inspect-android-class", "args", {
path: "",
className: "com.example.Main",
}),
).toBe(false);
expect(
await validate("inspect-android-class", "args", {
path: "app.apk",
className: "",
}),
).toBe(false);
expect(
await validate("inspect-android-class", "args", {
path: "app.apk",
className: "com.example.Main",
}),
).toBe(true);
expect(
await validate("inspect-android-class", "options", {
mappingPath: "mapping.txt",
}),
).toBe(false);
expect(
await validate("inspect-android-class", "options", {
mappingPath: absoluteMapping,
}),
).toBe(true);
expect(
await validate("inspect-android-method", "args", {
path: "app.apk",
className: "com.example.Main",
methodName: "",
}),
).toBe(false);
const setting = {
serial: "emulator-5554",
namespace: "system",
};
expect(
await validate("read-adb-setting", "args", {
...setting,
key: "key with space",
}),
).toBe(false);
expect(
await validate("read-adb-setting", "args", {
...setting,
key: "k".repeat(257),
}),
).toBe(false);
expect(
await validate("read-adb-setting", "args", {
...setting,
key: "airplane_mode_on",
}),
).toBe(true);
const listener = {
targetId: "page",
selector: "button",
};
expect(
await validate("inspect-web-event-listeners", "args", {
...listener,
endpoint: "",
}),
).toBe(false);
expect(
await validate("inspect-web-event-listeners", "args", {
endpoint: "http://127.0.0.1:9222",
targetId: "",
selector: "button",
}),
).toBe(false);
expect(
await validate("inspect-web-event-listeners", "args", {
endpoint: "http://127.0.0.1:9222",
...listener,
selector: "",
}),
).toBe(false);
expect(
await validate("inspect-web-event-listeners", "args", {
endpoint: "http://127.0.0.1:9222",
...listener,
}),
).toBe(true);
expect(
await validate("capture-web-screenshot", "args", {
endpoint: "",
targetId: "page",
}),
).toBe(false);
expect(
await validate("capture-web-screenshot", "args", {
endpoint: "http://127.0.0.1:9222",
targetId: "page",
}),
).toBe(true);
const parser = registeredCommands.get(cli)?.get("inspect-android-class");
expect(parser && "args" in parser && parser.args instanceof z.ZodObject).toBe(
true,
);
if (parser && "args" in parser && parser.args instanceof z.ZodObject)
expect(
parser.args.safeParse({ path: "app.apk", className: "" }).success,
).toBe(false);
});
it("advertises selection dependencies after omittable-default projection", async () => {
const cli = createCli({});
const ajv = new Ajv2020({ strict: false, validateFormats: false });
for (const [command, invalid, valid] of [
[
"inspect-packet-fields",
{ all: true, limit: 1 },
{ all: false, limit: 1 },
],
[
"inspect-signal-decoders",
{ all: true, limit: 1 },
{ all: false, limit: 1 },
],
[
"inspect-packet-capture",
{ fields: ["ip.src"], all_matches: true, match_limit: 1 },
{ fields: ["ip.src"], all_matches: false, match_limit: 1 },
],
[
"decode-signal-recording",
{ decoders: "uart", all_lines: true, line_limit: 1 },
{ decoders: "uart", all_lines: false, line_limit: 1 },
],
[
"inspect-il2cpp-metadata",
{ genericInstances: true },
{ genericInstances: false },
],
[
"inspect-il2cpp-metadata",
{ architecture: "arm64" },
{ binary: "lib.dylib", architecture: "arm64" },
],
[
"inspect-il2cpp-metadata",
{ ghidraSeeds: "seeds.txt" },
{ binary: "lib.so", ghidraSeeds: "seeds.txt" },
],
[
"inspect-il2cpp-metadata",
{ seedBase: "0x1000" },
{ binary: "lib.so", ghidraSeeds: "seeds.txt", seedBase: "0x1000" },
],
[
"import-reference-source",
{ outlineDepth: 2, detail: "full" },
{ outlineDepth: 2, detail: "summary" },
],
] as const) {
const schema = (await serve(cli, [command, "--schema", "--json"])).options;
expect(schema, command).toBeDefined();
const validate = ajv.compile(schema as AnySchema);
expect(
validate(invalid),
`${command} invalid ${JSON.stringify(invalid)}`,
).toBe(false);
expect(validate(valid), `${command} valid ${JSON.stringify(valid)}`).toBe(
true,
);
}
});
it("rejects invalid selected paths before inspecting a target", async () => {
const cli = createCli({});
for (const [args, field, code] of [
[
["inspect-apple-ips", "missing.ips", "--join-path="],
"joinPath",
"too_small",
],
[
["match-native-functions", "missing.json", "--evidence_output="],
"evidence_output",
"too_small",
],
[
["match-native-functions", "missing.json", "--evidence_output=bad\0path"],
"evidence_output",
"invalid_format",
],
[
[
"match-native-functions",
"missing.json",
"--evidence_output=bad\ud800path",
],
"evidence_output",
"invalid_format",
],
[
["match-native-functions", "missing.json", "--source_bundle="],
"source_bundle.0",
"too_small",
],
[["import-reference-source", ""], "root", "too_small"],
] as const) {
let stdout = "";
await cli.serve([...args, "--json"], {
env: {},
exit: () => {},
stdout: (text) => {
stdout += text;
},
});
expect(JSON.parse(stdout), args.join(" ")).toMatchObject({
code: "VALIDATION_ERROR",
fieldErrors: [{ path: field, code }],
});
}
});