mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 06:46:10 +00:00
* fix(cli): reject non-decimal byte-entropy window options * fix(cli): admit artifact numeric options as decimals Keyed-archive offset and limit, asset-catalog paging, and interface-builder budgets use the same decimal token check as byte-entropy windows. Defaulted fields stay numbers. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
304 lines
8.3 KiB
TypeScript
304 lines
8.3 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import {
|
|
cliTest,
|
|
runFileCli,
|
|
type CliResult,
|
|
} from "../../support/cli/cliFixture.js";
|
|
|
|
const SESSION_ID = "27d3e3f1-f1e5-49ae-91ec-95af1f343a5a";
|
|
const NON_INTEGER_TEXT = [
|
|
"",
|
|
" ",
|
|
"\t",
|
|
"1\n",
|
|
"0x1",
|
|
"0b1",
|
|
"1e0",
|
|
"+1",
|
|
"1.0",
|
|
"9007199254740992",
|
|
];
|
|
const NON_INTEGER_OPTION_TEXT = [...NON_INTEGER_TEXT, "-0"];
|
|
|
|
/** A readable 256-byte file, so only option parsing can refuse the request. */
|
|
const entropySample = async (): Promise<string> => {
|
|
const path = join(
|
|
await createTestTempDirectory("rea-entropy-options-"),
|
|
"sample.bin",
|
|
);
|
|
await writeFile(
|
|
path,
|
|
Uint8Array.from({ length: 256 }, (_, index) => index),
|
|
);
|
|
return path;
|
|
};
|
|
|
|
const expectCliValidationError = (result: CliResult, field: string): void => {
|
|
expect(result.exitCode).toBe(1);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
code: "VALIDATION_ERROR",
|
|
fieldErrors: expect.arrayContaining([
|
|
expect.objectContaining({ path: expect.stringContaining(field) }),
|
|
]),
|
|
});
|
|
};
|
|
|
|
describe("CLI integer arguments", () => {
|
|
// Run independent processes before retaining the shared validation worker.
|
|
cliTest.for(NON_INTEGER_OPTION_TEXT)(
|
|
"rejects %j as an Android method overload index",
|
|
async (value, { cli }) => {
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"inspect-android-method",
|
|
"/nonexistent/missing.apk",
|
|
"com.example.Main",
|
|
"run",
|
|
`--overload-index=${value}`,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, "overloadIndex");
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"keeps an explicit decimal zero as an Android method overload index",
|
|
async ({ cli }) => {
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"inspect-android-method",
|
|
"/nonexistent/missing.apk",
|
|
"com.example.Main",
|
|
"run",
|
|
"--overload-index=0",
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
code: "target_unavailable",
|
|
});
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"keeps an explicit decimal zero as an Android reference overload index",
|
|
async ({ cli }) => {
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"trace-android-references",
|
|
"/nonexistent/missing.apk",
|
|
"com.example.Main",
|
|
"--overload-index=0",
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
code: "target_unavailable",
|
|
});
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"keeps the one-based source line lower bound at the CLI boundary",
|
|
async ({ cli }) => {
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"trace-web-source-location",
|
|
"/nonexistent/manifest.json",
|
|
"0",
|
|
"/nonexistent/app.js.map",
|
|
"https://example.test/app.js.map",
|
|
"0",
|
|
"0",
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, "line");
|
|
},
|
|
);
|
|
|
|
cliTest.for(NON_INTEGER_OPTION_TEXT)(
|
|
"rejects %j as an Android reference overload index",
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
"trace-android-references",
|
|
"/nonexistent/missing.apk",
|
|
"com.example.Main",
|
|
`--overload-index=${value}`,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, "overloadIndex");
|
|
},
|
|
);
|
|
|
|
cliTest.for(NON_INTEGER_TEXT)(
|
|
"rejects %j as a Cutter expected generation",
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: ["cutter-command", SESSION_ID, value, "pdi", "--json"],
|
|
});
|
|
|
|
expectCliValidationError(result, "expectedGeneration");
|
|
},
|
|
);
|
|
|
|
cliTest.for(NON_INTEGER_TEXT)(
|
|
"rejects %j as a web module script index",
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
"trace-web-module-imports",
|
|
"/nonexistent/manifest.json",
|
|
value,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, "scriptIndex");
|
|
},
|
|
);
|
|
|
|
cliTest.for(NON_INTEGER_OPTION_TEXT)(
|
|
"rejects %j in a repeated web capture record selector",
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
"inspect-web-network-capture",
|
|
"/nonexistent/capture.qlog",
|
|
"qlog",
|
|
"--record",
|
|
"0",
|
|
"--record",
|
|
value,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, "record");
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("byte-entropy window options", () => {
|
|
// The options share one parser, so one takes every malformed spelling.
|
|
// Numeric coercion reads "0x10" as a valid length and "0x100" as a valid
|
|
// block size; "384" is decimal but not a power of two.
|
|
for (const [flag, field, values] of [
|
|
["--offset", "offset", NON_INTEGER_OPTION_TEXT],
|
|
["--length", "length", ["0x10"]],
|
|
["--block-size", "blockSize", ["0x100", "384"]],
|
|
] as const) {
|
|
cliTest.for(values)(
|
|
`rejects %j as a byte-entropy ${flag} value`,
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
"inspect-byte-entropy",
|
|
await entropySample(),
|
|
`${flag}=${value}`,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, field);
|
|
},
|
|
);
|
|
}
|
|
|
|
cliTest(
|
|
"keeps decimal byte-entropy windows and the omitted-length default",
|
|
async ({ cli }) => {
|
|
const path = await entropySample();
|
|
const window = async (...flags: string[]): Promise<unknown> => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: ["inspect-byte-entropy", path, ...flags, "--json"],
|
|
});
|
|
expect(result.exitCode, result.stdout).toBe(0);
|
|
const { normalized_result: report } = JSON.parse(result.stdout) as {
|
|
normalized_result: { block_size: number; window: unknown };
|
|
};
|
|
return { block_size: report.block_size, window: report.window };
|
|
};
|
|
|
|
expect(await window()).toEqual({
|
|
block_size: 4096,
|
|
window: { offset: 0, length: 256 },
|
|
});
|
|
expect(
|
|
await window("--offset=016", "--length=16", "--block-size=256"),
|
|
).toEqual({ block_size: 256, window: { offset: 16, length: 16 } });
|
|
expect(await window("--offset=0", "--length=0")).toEqual({
|
|
block_size: 4096,
|
|
window: { offset: 0, length: 0 },
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("artifact decimal options", () => {
|
|
// Same admission as byte-entropy windows. One option takes every malformed
|
|
// spelling; siblings in artifactCommands.ts take one coercion each.
|
|
for (const [command, flag, field, values] of [
|
|
["inspect-keyed-archive", "--offset", "offset", NON_INTEGER_OPTION_TEXT],
|
|
["inspect-keyed-archive", "--limit", "limit", ["", "0x10", "1e2"]],
|
|
["inspect-asset-catalog", "--offset", "offset", ["", "0x10"]],
|
|
["inspect-asset-catalog", "--limit", "limit", ["1e2"]],
|
|
["decode-interface-builder", "--max-documents", "maxDocuments", ["0x10"]],
|
|
["decode-interface-builder", "--max-objects", "maxObjects", [""]],
|
|
[
|
|
"decode-interface-builder",
|
|
"--max-connections",
|
|
"maxConnections",
|
|
["1e2"],
|
|
],
|
|
] as const) {
|
|
cliTest.for(values)(
|
|
`rejects %j as ${command} ${flag}`,
|
|
async (value, { cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
command,
|
|
"/nonexistent/missing-artifact",
|
|
`${flag}=${value}`,
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expectCliValidationError(result, field);
|
|
},
|
|
);
|
|
}
|
|
|
|
cliTest("keeps decimal keyed-archive offset and limit", async ({ cli }) => {
|
|
const result = await runFileCli(cli, {
|
|
arguments: [
|
|
"inspect-keyed-archive",
|
|
"/nonexistent/missing-archive",
|
|
"--offset=016",
|
|
"--limit=1",
|
|
"--json",
|
|
],
|
|
});
|
|
|
|
expect(result.exitCode).toBe(1);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
code: "target_unavailable",
|
|
});
|
|
});
|
|
});
|