From beb80a07bafc0847600eb635e0323068a386c811 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 12 Oct 2026 05:24:33 +0800 Subject: [PATCH] test(cli): consolidate schema coverage and assert input refusals Remove 30 redundant truthy-JSON schema checks. Validate advertised groups and property parity across every primary command, retain required-field checks, and assert structured malformed-input errors. Exercise JEB positional validation rather than an unknown flag. Validation: 13 public CLI adapter tests, combined checks, and temporary-directory hygiene. Host configuration refusal coverage from main is preserved. --- tests/boundary/cli/adbCli.test.ts | 37 ++----------------- tests/boundary/cli/apktoolCli.test.ts | 21 +++++------ .../cli/cliInputSchemaRequired.test.ts | 25 +++++++++++-- tests/boundary/cli/jebCli.test.ts | 32 ++++++---------- 4 files changed, 47 insertions(+), 68 deletions(-) diff --git a/tests/boundary/cli/adbCli.test.ts b/tests/boundary/cli/adbCli.test.ts index f36166780..88d20af9b 100644 --- a/tests/boundary/cli/adbCli.test.ts +++ b/tests/boundary/cli/adbCli.test.ts @@ -5,38 +5,6 @@ import { serveCli as serve, } from "./cliServeHarness.js"; -it.each([ - ["inspect-adb-client"], - ["list-adb-devices"], - ["inspect-adb-device"], - ["list-adb-packages"], - ["pull-adb-package"], - ["read-adb-logcat"], - ["inspect-adb-package"], - ["pull-adb-file"], - ["push-adb-file"], - ["capture-adb-screen"], - ["list-adb-processes"], - ["list-adb-directory"], - ["list-adb-features"], - ["list-adb-services"], - ["inspect-adb-display"], - ["inspect-adb-window"], - ["read-adb-setting"], - ["collect-adb-bugreport"], - ["resolve-adb-packages"], - ["install-adb-package"], - ["uninstall-adb-package"], - ["start-adb-app"], - ["start-adb-activity"], - ["stop-adb-app"], -])("%s advertises a schema through the public CLI", async (command) => { - const result = await serve([command, "--schema", "--json"]); - expect(result.exitCode, command).toBe(0); - const schema = JSON.parse(result.stdout) as { args?: unknown }; - expect(schema).toBeTruthy(); -}); - it.each([ [ ["inspect-adb-client", "--json"], @@ -63,7 +31,10 @@ it.each([ it("validates device serials before contacting any device", async () => { const result = await serve(["inspect-adb-device", "bad serial", "--json"]); - expect(result.stdout).toContain("invalid_input"); + expect(JSON.parse(result.stdout)).toMatchObject({ + code: "invalid_request", + category: "invalid_input", + }); }); it("requires the output directory option on pull", async () => { diff --git a/tests/boundary/cli/apktoolCli.test.ts b/tests/boundary/cli/apktoolCli.test.ts index f52855b78..fcbb29a96 100644 --- a/tests/boundary/cli/apktoolCli.test.ts +++ b/tests/boundary/cli/apktoolCli.test.ts @@ -5,16 +5,6 @@ import { serveCli as serve, } from "./cliServeHarness.js"; -it.each(["inspect-apktool-client", "decode-android-resources"])( - "%s advertises a schema through the public CLI", - async (command) => { - const result = await serve([command, "--schema", "--json"]); - expect(result.exitCode, command).toBe(0); - const schema = JSON.parse(result.stdout) as { args?: unknown }; - expect(schema).toBeTruthy(); - }, -); - it.each([ [ ["inspect-apktool-client", "--json"], @@ -47,5 +37,14 @@ it("validates the locale option before launching apktool", async () => { "not a locale", "--json", ]); - expect(result.stdout).toContain("invalid"); + expect(result.exitCode).toBe(1); + expect(JSON.parse(result.stdout)).toMatchObject({ + code: "VALIDATION_ERROR", + fieldErrors: [ + { + code: "invalid_format", + path: "locale", + }, + ], + }); }); diff --git a/tests/boundary/cli/cliInputSchemaRequired.test.ts b/tests/boundary/cli/cliInputSchemaRequired.test.ts index a74a3e963..023b7dc4a 100644 --- a/tests/boundary/cli/cliInputSchemaRequired.test.ts +++ b/tests/boundary/cli/cliInputSchemaRequired.test.ts @@ -13,7 +13,7 @@ const registeredCommands: WeakMap = Cli.toCommands; const advertisedGroupSchema = z .object({ - properties: z.record(z.string(), z.unknown()).optional(), + properties: z.record(z.string(), z.unknown()), required: z.array(z.string()).optional(), }) .loose(); @@ -70,8 +70,27 @@ describe("CLI advertised input schemas", () => { for (const command of createCliInventory(cli).primary) { const advertised = await advertisedSchema(cli, command); for (const group of INPUT_GROUPS) { - const required = (advertised[group]?.required ?? []).toSorted(); - const rejected = omissionRejected(parserSchema(cli, command, group)); + const parser = parserSchema(cli, command, group); + const advertisedGroup = advertised[group]; + if (parser === undefined) { + if (advertisedGroup !== undefined) + mismatches.push(`${command} ${group}: advertised without parser`); + continue; + } + if (advertisedGroup === undefined) { + mismatches.push(`${command} ${group}: parser group not advertised`); + continue; + } + const parserProperties = Object.keys(parser.shape).toSorted(); + const advertisedProperties = Object.keys( + advertisedGroup.properties, + ).toSorted(); + if (advertisedProperties.join() !== parserProperties.join()) + mismatches.push( + `${command} ${group}: advertised properties [${advertisedProperties.join()}], parser properties [${parserProperties.join()}]`, + ); + const required = (advertisedGroup.required ?? []).toSorted(); + const rejected = omissionRejected(parser); if (required.join() !== rejected.join()) mismatches.push( `${command} ${group}: advertised [${required.join()}], parser rejects [${rejected.join()}]`, diff --git a/tests/boundary/cli/jebCli.test.ts b/tests/boundary/cli/jebCli.test.ts index f5bb036ab..c188b0e31 100644 --- a/tests/boundary/cli/jebCli.test.ts +++ b/tests/boundary/cli/jebCli.test.ts @@ -11,18 +11,6 @@ const serve = async ( ): Promise<{ readonly stdout: string; readonly exitCode: number }> => serveCli(argv, jebEnvironment); -it.each([ - ["inspect-jeb-client"], - ["open-jeb-project"], - ["list-jeb-units"], - ["decompile-jeb-item"], -])("%s advertises a schema through the public CLI", async (command) => { - const result = await serve([command, "--schema", "--json"]); - expect(result.exitCode, command).toBe(0); - const schema = JSON.parse(result.stdout) as { args?: unknown }; - expect(schema).toBeTruthy(); -}); - it("reports an unreachable engine endpoint with recovery guidance", async () => { const result = await serve(["inspect-jeb-client", "--json"]); expect(result.stdout).toContain("capability_unavailable"); @@ -30,13 +18,15 @@ it("reports an unreachable engine endpoint with recovery guidance", async () => }); it("validates decompile item input before contacting the engine", async () => { - const result = await serve([ - "decompile-jeb-item", - "not-an-address", - "--item-kind", - "method", - "--json", - ]); - expect(result.stdout).toBeTruthy(); - expect(result.stdout).not.toContain("capability_unavailable"); + const result = await serve(["decompile-jeb-item", "", "method", "--json"]); + expect(result.exitCode).toBe(1); + expect(JSON.parse(result.stdout)).toMatchObject({ + code: "VALIDATION_ERROR", + fieldErrors: [ + { + code: "too_small", + path: "item-address", + }, + ], + }); });