mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 08:17:02 +00:00
fix(cli): validate outline-depth before numeric coercion (#7505)
Fixes morluto/rea#7425 Signed-off-by: pqminh27 <minh8a1hb@gmail.com>
This commit is contained in:
3 files changed
+87
-19
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
||||||
import { z } from "incur";
|
import { z } from "incur";
|
||||||
|
import { cliIntegerArgument } from "./options.js";
|
||||||
|
|
||||||
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
import type { DirectAnalysis } from "../composition/directAnalysis.js";
|
||||||
import type { ReferenceSourcePolicy } from "../domain/referenceSourcePolicy.js";
|
import type { ReferenceSourcePolicy } from "../domain/referenceSourcePolicy.js";
|
||||||
@@ -288,13 +289,7 @@ const registerReferenceSourceCommand = (
|
|||||||
.describe(
|
.describe(
|
||||||
"Complete committed graph or compact historical-source summary",
|
"Complete committed graph or compact historical-source summary",
|
||||||
),
|
),
|
||||||
outlineDepth: z
|
outlineDepth: cliIntegerArgument(0)
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.safe()
|
|
||||||
// After .safe(): its safe-range check overwrites an earlier minimum,
|
|
||||||
// which advertised negative depths the parser rejects.
|
|
||||||
.nonnegative()
|
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Summary outline path depth (default: 2; 0 omits entries); requires --detail summary",
|
"Summary outline path depth (default: 2; 0 omits entries); requires --detail summary",
|
||||||
|
|||||||
@@ -394,25 +394,44 @@ describe("Apktool locale selection input schema", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("reference-source outline-depth input schema", () => {
|
describe("reference-source outline-depth input schema", () => {
|
||||||
it("aligns advertised bounds with the parser's accepted values", async () => {
|
it("parses decimal CLI tokens into the advertised safe integer schema", async () => {
|
||||||
const cli = createCli({});
|
const cli = createCli({});
|
||||||
const advertised = await advertisedSchema(cli, "import-reference-source");
|
const advertised = await advertisedSchema(cli, "import-reference-source");
|
||||||
const outlineDepth = advertised.options?.properties?.outlineDepth;
|
const outlineDepth = advertised.options?.properties?.outlineDepth;
|
||||||
expect(outlineDepth).toMatchObject({ type: "integer", minimum: 0 });
|
expect(outlineDepth).toMatchObject({ type: "integer", minimum: 0 });
|
||||||
|
|
||||||
const parser = parserSchema(cli, "import-reference-source", "options");
|
const parser = parserSchema(cli, "import-reference-source", "options");
|
||||||
expect(parser?.safeParse({ outlineDepth: -1 }).success).toBe(false);
|
expect(parser?.safeParse({}).success).toBe(true);
|
||||||
expect(parser?.safeParse({ outlineDepth: 0 }).success).toBe(true);
|
const parsedValues = [
|
||||||
expect(parser?.safeParse({ outlineDepth: 2 }).success).toBe(true);
|
["0", 0],
|
||||||
|
["2", 2],
|
||||||
|
["02", 2],
|
||||||
|
[String(Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER],
|
||||||
|
] as const;
|
||||||
|
for (const [token, expected] of parsedValues) {
|
||||||
|
const parsed = parser?.safeParse({ outlineDepth: token });
|
||||||
|
expect(parsed?.success).toBe(true);
|
||||||
|
if (parsed === undefined || !parsed.success)
|
||||||
|
throw new Error(`Expected outline-depth token ${token} to parse`);
|
||||||
|
expect(parsed.data).toMatchObject({ outlineDepth: expected });
|
||||||
|
}
|
||||||
|
for (const token of ["", "-1", "9007199254740992"]) {
|
||||||
|
expect(parser?.safeParse({ outlineDepth: token }).success).toBe(false);
|
||||||
|
}
|
||||||
|
|
||||||
const ajv = new Ajv2020({ strict: false });
|
const ajv = new Ajv2020({ strict: false });
|
||||||
const validate = ajv.compile({
|
const validate = ajv.compile({
|
||||||
...advertised.options,
|
...advertised.options,
|
||||||
$schema: "https://json-schema.org/draft/2020-12/schema",
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
||||||
});
|
});
|
||||||
|
for (const [token] of parsedValues) {
|
||||||
|
const parsed = parser?.safeParse({ outlineDepth: token });
|
||||||
|
if (parsed === undefined || !parsed.success)
|
||||||
|
throw new Error(`Expected outline-depth token ${token} to parse`);
|
||||||
|
expect(validate(parsed.data)).toBe(true);
|
||||||
|
}
|
||||||
expect(validate({ outlineDepth: -1 })).toBe(false);
|
expect(validate({ outlineDepth: -1 })).toBe(false);
|
||||||
expect(validate({ outlineDepth: 0 })).toBe(true);
|
expect(validate({ outlineDepth: 9007199254740992 })).toBe(false);
|
||||||
expect(validate({ outlineDepth: 2 })).toBe(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -58,11 +58,22 @@ describe.skipIf(process.platform === "win32")(
|
|||||||
manifest_id: `hsm_${createHash("sha256").update(semanticJson).digest("hex")}`,
|
manifest_id: `hsm_${createHash("sha256").update(semanticJson).digest("hex")}`,
|
||||||
};
|
};
|
||||||
expect((await run("--detail", "full")).stdout).toBe(complete.stdout);
|
expect((await run("--detail", "full")).stdout).toBe(complete.stdout);
|
||||||
for (const depth of [undefined, 0, 1, 10]) {
|
for (const { depth, token } of [
|
||||||
|
{ depth: undefined, token: undefined },
|
||||||
|
{ depth: 0, token: "0" },
|
||||||
|
{ depth: 1, token: "1" },
|
||||||
|
{ depth: 2, token: "2" },
|
||||||
|
{ depth: 2, token: "02" },
|
||||||
|
{ depth: 10, token: "10" },
|
||||||
|
{
|
||||||
|
depth: Number.MAX_SAFE_INTEGER,
|
||||||
|
token: String(Number.MAX_SAFE_INTEGER),
|
||||||
|
},
|
||||||
|
]) {
|
||||||
const result = await run(
|
const result = await run(
|
||||||
"--detail",
|
"--detail",
|
||||||
"summary",
|
"summary",
|
||||||
...(depth === undefined ? [] : ["--outline-depth", String(depth)]),
|
...(token === undefined ? [] : ["--outline-depth", token]),
|
||||||
);
|
);
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.json).toHaveProperty("manifest", expectedManifest);
|
expect(result.json).toHaveProperty("manifest", expectedManifest);
|
||||||
@@ -155,12 +166,55 @@ describe("compiled historical-source summary input boundaries", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (const [label, options] of [
|
||||||
|
["--outline-depth=", ["--detail", "summary", "--outline-depth="]],
|
||||||
|
[
|
||||||
|
"an empty outline-depth argument",
|
||||||
|
["--detail", "summary", "--outline-depth", ""],
|
||||||
|
],
|
||||||
|
...[
|
||||||
|
" ",
|
||||||
|
"\t",
|
||||||
|
"1\n",
|
||||||
|
"0x1",
|
||||||
|
"0b1",
|
||||||
|
"1e0",
|
||||||
|
"+1",
|
||||||
|
"1.0",
|
||||||
|
"-0",
|
||||||
|
"-1",
|
||||||
|
"1.5",
|
||||||
|
"NaN",
|
||||||
|
"9007199254740992",
|
||||||
|
].map((value) => [
|
||||||
|
JSON.stringify(value),
|
||||||
|
["--detail", "summary", "--outline-depth", value],
|
||||||
|
]),
|
||||||
|
] as const) {
|
||||||
|
cliTest(`rejects ${label} as an outline depth`, async ({ cli }) => {
|
||||||
|
const root = await createTestTempDirectory(
|
||||||
|
"rea-reference-summary-input-",
|
||||||
|
);
|
||||||
|
const result = await cli.run({
|
||||||
|
arguments: ["import-reference-source", root, ...options, "--json"],
|
||||||
|
cwd: root,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.json).toMatchObject({
|
||||||
|
code: "VALIDATION_ERROR",
|
||||||
|
fieldErrors: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
path: expect.stringContaining("outlineDepth"),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
expect(result.stdout).not.toContain("HistoricalSourceGraph");
|
||||||
|
expect(result.stdout).not.toContain("HistoricalSourceSummary");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const options of [
|
for (const options of [
|
||||||
["--detail", "summry"],
|
["--detail", "summry"],
|
||||||
["--detail", "summary", "--outline-depth", "-1"],
|
|
||||||
["--detail", "summary", "--outline-depth", "1.5"],
|
|
||||||
["--detail", "summary", "--outline-depth", "NaN"],
|
|
||||||
["--detail", "summary", "--outline-depth", "9007199254740992"],
|
|
||||||
["--outline-depth", "1"],
|
["--outline-depth", "1"],
|
||||||
["--detail", "full", "--outline-depth", "1"],
|
["--detail", "full", "--outline-depth", "1"],
|
||||||
]) {
|
]) {
|
||||||
|
|||||||
Reference in new issue
Block a user