mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 03:12:36 +03:00
fix(api): align combo body and legacy key access (#12070)
Alinha o body do combo e o acesso legado por chave, com testes atualizados (CLI api-generator + row parsers). Validado no worktree combinado. Obrigado!
This commit is contained in:
@@ -16,7 +16,7 @@ export function register_combos(parent) {
|
||||
});
|
||||
tag.command("post-api-combos")
|
||||
.description("Create routing combo")
|
||||
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.action(async (opts, cmd) => {
|
||||
const gOpts = cmd.optsWithGlobals();
|
||||
let url = "/api/combos";
|
||||
@@ -44,7 +44,7 @@ export function register_combos(parent) {
|
||||
tag.command("put-api-combos-id-")
|
||||
.description("Update combo")
|
||||
.requiredOption("--id <id>", "")
|
||||
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.action(async (opts, cmd) => {
|
||||
const gOpts = cmd.optsWithGlobals();
|
||||
let url = "/api/combos/{id}";
|
||||
@@ -62,7 +62,7 @@ export function register_combos(parent) {
|
||||
tag.command("patch-api-combos-id-")
|
||||
.description("Update combo")
|
||||
.requiredOption("--id <id>", "")
|
||||
.option("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.action(async (opts, cmd) => {
|
||||
const gOpts = cmd.optsWithGlobals();
|
||||
let url = "/api/combos/{id}";
|
||||
@@ -99,10 +99,17 @@ export function register_combos(parent) {
|
||||
});
|
||||
tag.command("post-api-combos-test")
|
||||
.description("Test a combo configuration")
|
||||
.requiredOption("--body <jsonOrPath>", "JSON body or @path/to/file.json")
|
||||
.action(async (opts, cmd) => {
|
||||
const gOpts = cmd.optsWithGlobals();
|
||||
let url = "/api/combos/test";
|
||||
const res = await apiFetch(url, { method: "POST", baseUrl: gOpts.baseUrl, apiKey: gOpts.apiKey });
|
||||
let body;
|
||||
if (opts.body) {
|
||||
body = opts.body.startsWith("@")
|
||||
? JSON.parse(readFileSync(opts.body.slice(1), "utf8"))
|
||||
: JSON.parse(opts.body);
|
||||
}
|
||||
const res = await apiFetch(url, { method: "POST", body, baseUrl: gOpts.baseUrl, apiKey: gOpts.apiKey });
|
||||
const data = res.ok ? await res.json() : await res.text();
|
||||
emit(data, gOpts);
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(api):** Generated API CLI commands now enforce required OpenAPI request bodies; Combo test commands forward the required `comboName` body, while API keys created by older writers after migration 149 preserve legacy allow-all Combo access without widening explicit empty allowlists — thanks @marcelokarval
|
||||
@@ -2350,9 +2350,24 @@ paths:
|
||||
post:
|
||||
tags: [Combos]
|
||||
summary: Test a combo configuration
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [comboName]
|
||||
properties:
|
||||
comboName:
|
||||
type: string
|
||||
minLength: 1
|
||||
responses:
|
||||
"200":
|
||||
description: Test result
|
||||
"400":
|
||||
description: Missing or invalid combo name
|
||||
"404":
|
||||
description: Combo not found
|
||||
|
||||
/api/settings:
|
||||
get:
|
||||
|
||||
@@ -1936,9 +1936,24 @@ paths:
|
||||
post:
|
||||
tags: [Combos]
|
||||
summary: Test a combo configuration
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [comboName]
|
||||
properties:
|
||||
comboName:
|
||||
type: string
|
||||
minLength: 1
|
||||
responses:
|
||||
"200":
|
||||
description: Test result
|
||||
"400":
|
||||
description: Missing or invalid combo name
|
||||
"404":
|
||||
description: Combo not found
|
||||
|
||||
/api/settings:
|
||||
get:
|
||||
|
||||
@@ -130,7 +130,10 @@ for (const [tag, ops] of Object.entries(byTag)) {
|
||||
lines.push(` .${flag}("--${kebab(p.name)} <${p.name}>", "${escapeStr(p.description)}")`);
|
||||
}
|
||||
if (hasBody) {
|
||||
lines.push(` .option("--body <jsonOrPath>", "JSON body or @path/to/file.json")`);
|
||||
const bodyFlag = op.requestBody.required ? "requiredOption" : "option";
|
||||
lines.push(
|
||||
` .${bodyFlag}("--body <jsonOrPath>", "JSON body or @path/to/file.json")`
|
||||
);
|
||||
}
|
||||
lines.push(` .action(async (opts, cmd) => {`);
|
||||
lines.push(` const gOpts = cmd.optsWithGlobals();`);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
|
||||
import type { AccessSchedule, RateLimitRule } from "./types";
|
||||
import { ALL_COMBOS_ACCESS_RULE } from "@/shared/constants/comboAccess";
|
||||
export { parseModelAccessMode } from "./modelAccessMode";
|
||||
export type { ModelAccessMode } from "./modelAccessMode";
|
||||
|
||||
@@ -30,6 +31,9 @@ export function parseAllowedModels(value: unknown): string[] {
|
||||
}
|
||||
|
||||
export function parseAllowedCombos(value: unknown): string[] {
|
||||
// Migration 149 may already be recorded before an older writer creates a key.
|
||||
// Preserve those legacy NULL rows as allow-all while keeping explicit [] deny-all.
|
||||
if (value === null || value === undefined) return [ALL_COMBOS_ACCESS_RULE];
|
||||
return parseStringList(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,13 @@ test("parseAllowedModels keeps only string entries, tolerates junk", () => {
|
||||
assert.deepEqual(P.parseAllowedModels(null), []);
|
||||
});
|
||||
|
||||
test("parseAllowedCombos preserves legacy NULL as allow-all without widening explicit []", () => {
|
||||
assert.deepEqual(P.parseAllowedCombos(null), ["combo/*"]);
|
||||
assert.deepEqual(P.parseAllowedCombos(undefined), ["combo/*"]);
|
||||
assert.deepEqual(P.parseAllowedCombos("[]"), []);
|
||||
assert.deepEqual(P.parseAllowedCombos('["fast-chat"]'), ["fast-chat"]);
|
||||
});
|
||||
|
||||
test("flag parsers honor the 0/1/true/false matrix", () => {
|
||||
assert.equal(P.parseNoLog(1), true);
|
||||
assert.equal(P.parseNoLog("1"), true);
|
||||
|
||||
@@ -40,6 +40,18 @@ paths:
|
||||
responses:
|
||||
"200":
|
||||
description: Updated widget
|
||||
/api/widgets/preview:
|
||||
post:
|
||||
tags: [Widgets]
|
||||
summary: Preview widget
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
"200":
|
||||
description: Previewed widget
|
||||
components:
|
||||
parameters:
|
||||
ResourceId:
|
||||
@@ -83,11 +95,16 @@ test("generator resolves a $ref path parameter into --id and substitutes {id} in
|
||||
);
|
||||
assert.doesNotMatch(generated, /url = "\/api\/widgets\/\{id\}";\s*\n\s*const res/);
|
||||
|
||||
// requestBody presence must still produce --body.
|
||||
// Required and optional request bodies must preserve their OpenAPI semantics.
|
||||
assert.match(
|
||||
generated,
|
||||
/\.option\("--body <jsonOrPath>"/,
|
||||
"generated command must declare --body for the requestBody"
|
||||
/tag\.command\("patch-api-widgets-id-?"\)[\s\S]*?\.requiredOption\("--body <jsonOrPath>"/,
|
||||
"generated command must require --body for a required requestBody"
|
||||
);
|
||||
assert.match(
|
||||
generated,
|
||||
/tag\.command\("post-api-widgets-preview"\)[\s\S]*?\.option\("--body <jsonOrPath>"/,
|
||||
"generated command must keep --body optional for an optional requestBody"
|
||||
);
|
||||
} finally {
|
||||
rmSync(workDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
||||
@@ -145,8 +162,8 @@ test("real generated bin/cli/api-commands/combos.mjs has --id and --body on the
|
||||
);
|
||||
assert.match(
|
||||
patchBlock,
|
||||
/\.option\("--body <jsonOrPath>"/,
|
||||
"PATCH combo command must accept --body"
|
||||
/\.requiredOption\("--body <jsonOrPath>"/,
|
||||
"PATCH combo command must require --body"
|
||||
);
|
||||
assert.match(
|
||||
patchBlock,
|
||||
@@ -154,3 +171,15 @@ test("real generated bin/cli/api-commands/combos.mjs has --id and --body on the
|
||||
"PATCH combo command must substitute {id} in the URL, not send it literally"
|
||||
);
|
||||
});
|
||||
|
||||
test("real generated combo-test command accepts and forwards its required request body", () => {
|
||||
const src = readFileSync(REAL_COMBOS, "utf8");
|
||||
const testBlockMatch = src.match(
|
||||
/ {2}tag\.command\("post-api-combos-test"\)[\s\S]*?(?=\n {2}tag\.command\(|\n\})/
|
||||
);
|
||||
assert.ok(testBlockMatch, "combos.mjs must have a generated combo-test command block");
|
||||
const testBlock = testBlockMatch[0];
|
||||
|
||||
assert.match(testBlock, /\.requiredOption\("--body <jsonOrPath>"/);
|
||||
assert.match(testBlock, /const res = await apiFetch\(url, \{ method: "POST", body,/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user