From 69f2a95371f947798c6aa12cd12711298526ff17 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Sun, 6 Sep 2026 19:49:13 +0200 Subject: [PATCH] fix(openapi): guard suffix matching and add ambient context test --- src/services/openapi.ts | 5 ++++- tests/openapi.test.ts | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/services/openapi.ts b/src/services/openapi.ts index 4b746a9..649fb43 100644 --- a/src/services/openapi.ts +++ b/src/services/openapi.ts @@ -98,9 +98,12 @@ export async function getOpenApiSpec( // 2. Specific path lookup if (options?.path) { const target = normalizePath(options.path); + const suffix = target.replace(/^\/api\/v3/, ""); const matchedKey = allPaths.find((p) => p.toLowerCase() === target) || - allPaths.find((p) => p.toLowerCase().endsWith(target.replace(/^\/api\/v3/, ""))); + (suffix.length > 0 && suffix !== "/" + ? allPaths.find((p) => p.toLowerCase().endsWith(suffix)) + : undefined); if (!matchedKey || !doc.paths[matchedKey]) { const sample = allPaths.slice(0, 10).join(", "); diff --git a/tests/openapi.test.ts b/tests/openapi.test.ts index b2c2856..ad230af 100644 --- a/tests/openapi.test.ts +++ b/tests/openapi.test.ts @@ -6,6 +6,7 @@ import { } from "../src/services/openapi.ts"; import { OpenProjectClient } from "../src/client/api-client.ts"; import { OpenProjectNotFoundError } from "../src/client/errors.ts"; +import { runWithContext } from "../src/context.ts"; describe("OpenApi Service", () => { const mockSpec = { @@ -144,10 +145,17 @@ describe("OpenApi Service", () => { ); }); - test("full mode returns the complete raw specification", async () => { - const result = (await getOpenApiSpec({ full: true }, mockClient)) as typeof mockSpec; - expect(result.openapi).toBe("3.0.3"); - expect(result.paths).toBeDefined(); - expect(result.components).toBeDefined(); + test("resolves ambient client from RequestContext when client is omitted", async () => { + const res = await runWithContext({ client: mockClient, isReadOnly: true }, async () => { + return getOpenApiSpec({}); + }); + expect((res as OpenApiSummary).title).toBe("OpenProject API V3 (Test)"); + }); + + test("path mode does not falsely match root /api/v3 via empty suffix", async () => { + // If /api/v3 itself is not a path key, requesting it must throw OpenProjectNotFoundError + expect(getOpenApiSpec({ path: "/api/v3" }, mockClient)).rejects.toThrow( + OpenProjectNotFoundError + ); }); });