From 362c5acbfe025c190dcd91700b1c114f6b5131f2 Mon Sep 17 00:00:00 2001 From: Dizzle <112548150+maxmad64bis@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:47:05 +0200 Subject: [PATCH] feat(api): accept PATCH on /api/combos/[id] (#10869) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Obrigado — bug real de contrato: o openapi.yaml já documentava patch em /api/combos/{id}, mas a rota nunca exportou PATCH, então um cliente gerado a partir do spec publicado recebia 405 do App Router antes de qualquer handler rodar. Fix mínimo (delegação de 4 linhas para PUT, mesmo padrão já usado em /api/providers/[id] e 25 outras rotas /api/**). Validação (worktree combinado a partir de origin/release/v3.8.50, 0 conflitos): - typecheck:core limpo, complexity/cognitive-complexity dentro do baseline - tests/unit/combo-patch-verb.test.ts — 2/2 passando (falham antes com "comboRoute.PATCH is not a function") - Suíte combo completa — mesmas 5 falhas herdadas de #9985, confirmadas DRIFT --- .../features/10869-combo-patch-verb.md | 1 + src/app/api/combos/[id]/route.ts | 6 ++ tests/unit/combo-patch-verb.test.ts | 58 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 changelog.d/features/10869-combo-patch-verb.md create mode 100644 tests/unit/combo-patch-verb.test.ts diff --git a/changelog.d/features/10869-combo-patch-verb.md b/changelog.d/features/10869-combo-patch-verb.md new file mode 100644 index 0000000000..f11893d95c --- /dev/null +++ b/changelog.d/features/10869-combo-patch-verb.md @@ -0,0 +1 @@ +- feat(api): accept PATCH on /api/combos/[id], the verb the OpenAPI spec already documents (#10869) diff --git a/src/app/api/combos/[id]/route.ts b/src/app/api/combos/[id]/route.ts index 869114486f..dd35562bdf 100644 --- a/src/app/api/combos/[id]/route.ts +++ b/src/app/api/combos/[id]/route.ts @@ -265,6 +265,12 @@ export async function PUT(request, { params }) { } } +// PATCH /api/combos/[id] - partial update. PUT merges the body onto the stored +// combo, so both verbs share one handler (same shape as /api/providers/[id]). +export async function PATCH(request, ctx) { + return PUT(request, ctx); +} + // DELETE /api/combos/[id] - Delete combo export async function DELETE(request, { params }) { const authError = await requireManagementAuth(request); diff --git a/tests/unit/combo-patch-verb.test.ts b/tests/unit/combo-patch-verb.test.ts new file mode 100644 index 0000000000..c72d0e23d4 --- /dev/null +++ b/tests/unit/combo-patch-verb.test.ts @@ -0,0 +1,58 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-patch-verb-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const combosDb = await import("../../src/lib/db/combos.ts"); +const comboRoute = await import("../../src/app/api/combos/[id]/route.ts"); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +function patch(id: string, body: Record) { + return new Request(`http://localhost/api/combos/${id}`, { + method: "PATCH", + headers: { "content-type": "application/json" }, + body: JSON.stringify(body), + }); +} + +test("PATCH changes one field and leaves the rest of the combo alone", async () => { + const combo = await combosDb.createCombo({ + name: "patchable", + strategy: "priority", + models: [{ provider: "openai", model: "gpt-4o" }], + system_message: "keep me", + }); + + const response = await comboRoute.PATCH(patch(combo.id, { strategy: "round-robin" }), { + params: Promise.resolve({ id: combo.id }), + }); + assert.equal(response.status, 200); + + const stored = (await combosDb.getComboById(combo.id)) as { + strategy?: string; + system_message?: string; + models?: unknown[]; + }; + assert.equal(stored.strategy, "round-robin"); + assert.equal(stored.system_message, "keep me"); + assert.equal(stored.models?.length, 1); +}); + +test("PATCH on an unknown combo answers 404, like PUT", async () => { + const response = await comboRoute.PATCH(patch("does-not-exist", { strategy: "priority" }), { + params: Promise.resolve({ id: "does-not-exist" }), + }); + assert.equal(response.status, 404); + + const body = (await response.json()) as { error?: { code?: string } }; + assert.equal(body.error?.code, "COMBO_007"); +});