From 868c38b258c580ce7e42b55e7b14d7a0b31bfd69 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:53:29 +0000 Subject: [PATCH] fix: preserve base URL path segments in buildUrl() for Managed Dynatrace and similar hosts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Strip the leading '/' from path before new URL(path, base) so that an absolute-path reference does not resolve against the origin and silently discard path components already present in base (e.g. /e/ for Dynatrace Managed clusters). Fixes #283 — recurrence of BUG-4 root cause (same WHATWG URL spec behavior that dropped /artifactory from Artifactory base URLs in PR #94). Co-authored-by: Sunny Kolattukudy --- src/lib/http.test.ts | 20 ++++++++++++++++++++ src/lib/http.ts | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/lib/http.test.ts b/src/lib/http.test.ts index 28596b4..9745fe9 100644 --- a/src/lib/http.test.ts +++ b/src/lib/http.test.ts @@ -681,6 +681,26 @@ describe('HttpClient — Dynatrace authentication', () => { await client.dynatracePlatform('/platform/storage/query/v1/query:execute'); expect(authorization).toBe('Bearer platform-token'); }); + + it('preserves /e/ path segment for Dynatrace Managed base URLs', async () => { + const capturedUrls: string[] = []; + vi.stubGlobal('fetch', async (url: string) => { + capturedUrls.push(url); + return new Response('{"entities":[],"totalCount":0}', { status: 200 }); + }); + const client = new HttpClient(baseConfig({ + dynatrace: { + baseUrl: 'https://apm.pncint.net/e/ccd497ef-cb0f-4294-9044-1b6faead0768', + apiToken: 'tok', + platformUrl: undefined, + platformToken: undefined + } + })); + await client.dynatrace('/api/v2/entities'); + expect(capturedUrls[0]).toBe( + 'https://apm.pncint.net/e/ccd497ef-cb0f-4294-9044-1b6faead0768/api/v2/entities' + ); + }); }); describe('HttpClient — --debug mode', () => { diff --git a/src/lib/http.ts b/src/lib/http.ts index c7a8ebd..962c15c 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -41,7 +41,14 @@ function redactHeaders(headers: RequestInit['headers']): Record } function buildUrl(base: string, path: string, params?: Record): string { - const url = new URL(path, base.endsWith('/') ? base : base + '/'); + const normalizedBase = base.endsWith('/') ? base : base + '/'; + // Strip a leading '/' so that new URL(path, base) resolves relative to base's full + // path rather than the origin. Per the WHATWG URL spec, an absolute-path reference + // (starting with '/') resolves against the origin and discards any path already + // present in base — which silently drops path segments like '/e/' + // from Dynatrace Managed or Artifactory-with-context-root base URLs. + const normalizedPath = path.startsWith('/') ? path.slice(1) : path; + const url = new URL(normalizedPath, normalizedBase); if (params) { for (const [key, val] of Object.entries(params)) { if (val !== undefined) {