fix(cli): preserve blank JSON paths for input validation (#1068)
* fix(cli): preserve blank JSON paths for input validation * test(cli): avoid unchecked path field assertions --------- Co-authored-by: N0zoM1z0 <1705004398@qq.com>
This commit is contained in:
parent
6a7650e93c
commit
3dcb732da3
|
|
@ -37,9 +37,9 @@ export const parseCliJsonInput = async (
|
|||
/**
|
||||
* Resolve named string fields of CLI-supplied JSON against the operator
|
||||
* working directory. Each entry is a key path from the document root; only
|
||||
* fields holding strings are resolved, everything else passes through
|
||||
* untouched. Shared MCP contracts reject relative paths, so CLI workflows
|
||||
* resolve operator-relative values before validation.
|
||||
* fields holding nonblank strings are resolved, everything else passes through
|
||||
* untouched so shared schemas can reject blank paths. Shared MCP contracts reject
|
||||
* relative paths, so CLI workflows resolve operator-relative values before validation.
|
||||
*/
|
||||
export const resolveCliJsonPaths = (
|
||||
value: unknown,
|
||||
|
|
@ -61,7 +61,12 @@ const resolveJsonPath = (
|
|||
([key, entry]: readonly [string, unknown]): readonly [string, unknown] => {
|
||||
if (key !== head) return [key, entry];
|
||||
if (tail.length === 0)
|
||||
return [key, typeof entry === "string" ? resolve(entry) : entry];
|
||||
return [
|
||||
key,
|
||||
typeof entry === "string" && entry.trim().length > 0
|
||||
? resolve(entry)
|
||||
: entry,
|
||||
];
|
||||
return [key, resolveJsonPath(entry, tail)];
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
import { resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveCliJsonPaths } from "./cliJsonInput.js";
|
||||
import { electronActiveObservationInputSchema } from "./domain/javascript/electronActiveObservation.js";
|
||||
|
||||
const paths = [
|
||||
["executable_path"],
|
||||
["application_path"],
|
||||
["application_root"],
|
||||
] as const;
|
||||
|
||||
describe("CLI JSON path normalization", () => {
|
||||
it.each([
|
||||
["executable_path", ""],
|
||||
["executable_path", " \t "],
|
||||
["application_path", ""],
|
||||
["application_path", " \t "],
|
||||
["application_root", ""],
|
||||
["application_root", " \t "],
|
||||
])(
|
||||
"keeps blank %s (%j) invalid at the shared input boundary",
|
||||
(field, value) => {
|
||||
const input = {
|
||||
executable_path: "/synthetic/electron",
|
||||
application_path: "/synthetic/app",
|
||||
application_root: "/synthetic",
|
||||
[field]: value,
|
||||
};
|
||||
const parsed = electronActiveObservationInputSchema.safeParse(
|
||||
resolveCliJsonPaths(input, paths),
|
||||
);
|
||||
expect(parsed.success).toBe(false);
|
||||
if (parsed.success) throw new Error("Expected blank path rejection");
|
||||
expect(parsed.error.issues).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ path: [field] })]),
|
||||
);
|
||||
expect(input).toHaveProperty(field, value);
|
||||
},
|
||||
);
|
||||
|
||||
it("resolves selected relative paths without trimming names or changing unrelated fields", () => {
|
||||
const input = {
|
||||
browser: { executable_path: " browser ", label: "relative-label" },
|
||||
application_path: "app",
|
||||
};
|
||||
expect(
|
||||
resolveCliJsonPaths(input, [["browser", "executable_path"]]),
|
||||
).toEqual({
|
||||
browser: {
|
||||
executable_path: resolve(" browser "),
|
||||
label: "relative-label",
|
||||
},
|
||||
application_path: "app",
|
||||
});
|
||||
expect(input.browser.executable_path).toBe(" browser ");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { expect } from "vitest";
|
||||
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for(["", " \t "])(
|
||||
"rejects blank Electron application_root (%j) before filesystem or provider work",
|
||||
async (applicationRoot, { cli }) => {
|
||||
const response = await cli.run({
|
||||
arguments: [
|
||||
"capture-electron-scenario",
|
||||
JSON.stringify({
|
||||
executable_path: "/synthetic/missing-electron",
|
||||
application_path: "/synthetic/missing-app",
|
||||
application_root: applicationRoot,
|
||||
}),
|
||||
"--json",
|
||||
],
|
||||
environment: { REA_LOG_LEVEL: "silent" },
|
||||
timeoutMs: 15000,
|
||||
});
|
||||
expect(response.exitCode).toBe(1);
|
||||
expect(response.json).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
operation: "capture_electron_scenario",
|
||||
issues: expect.arrayContaining([
|
||||
{ path: ["application_root"], reason: "out_of_range", minimum: 1 },
|
||||
]),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
Loading…
Reference in New Issue