diff --git a/lib/confluence-client.js b/lib/confluence-client.js index 2d106f6..b5f51e8 100644 --- a/lib/confluence-client.js +++ b/lib/confluence-client.js @@ -2078,7 +2078,16 @@ class ConfluenceClient { return pathOrUrl; } - return this.buildUrl(pathOrUrl); + // Prepend context path (e.g. /wiki on Atlassian Cloud) when the API returns + // a path relative to the Confluence context root. Without this, URLs such as + // attachment download links (_links.download → "/download/attachments/...") + // get built as https:///download/... instead of + // https:///wiki/download/..., which returns 404 on Cloud. + const prefix = this.webUrlPrefix || ''; + const needsPrefix = prefix && !pathOrUrl.startsWith(`${prefix}/`); + const withPrefix = needsPrefix ? `${prefix}${pathOrUrl}` : pathOrUrl; + + return this.buildUrl(withPrefix); } parseNextStart(nextLink) { diff --git a/tests/confluence-client.test.js b/tests/confluence-client.test.js index 237bc39..74fe502 100644 --- a/tests/confluence-client.test.js +++ b/tests/confluence-client.test.js @@ -105,6 +105,39 @@ describe('ConfluenceClient', () => { }); expect(httpClient.toAbsoluteUrl('https://cdn.example.com/file.pdf')).toBe('https://cdn.example.com/file.pdf'); }); + + test('toAbsoluteUrl prepends /wiki context path on Atlassian Cloud', () => { + const cloudClient = new ConfluenceClient({ + domain: 'test.atlassian.net', + token: 'token', + apiPath: '/wiki/rest/api' + }); + // _links.download from the API is relative to the Confluence context root, + // so on Cloud (apiPath starts with /wiki/) we must prepend /wiki. Otherwise + // the resulting URL hits https:///download/... and returns 404. + expect(cloudClient.toAbsoluteUrl('/download/attachments/123/file.png?version=1&modificationDate=1700000000000&cacheVersion=1&api=v2')) + .toBe('https://test.atlassian.net/wiki/download/attachments/123/file.png?version=1&modificationDate=1700000000000&cacheVersion=1&api=v2'); + }); + + test('toAbsoluteUrl does not double-prepend /wiki when path already starts with it', () => { + const cloudClient = new ConfluenceClient({ + domain: 'test.atlassian.net', + token: 'token', + apiPath: '/wiki/rest/api' + }); + expect(cloudClient.toAbsoluteUrl('/wiki/download/attachments/123/file.png')) + .toBe('https://test.atlassian.net/wiki/download/attachments/123/file.png'); + }); + + test('toAbsoluteUrl leaves Server/DC paths untouched (no webUrlPrefix)', () => { + const serverClient = new ConfluenceClient({ + domain: 'confluence.example.com', + token: 'token', + apiPath: '/rest/api' + }); + expect(serverClient.toAbsoluteUrl('/download/attachments/123/file.png')) + .toBe('https://confluence.example.com/download/attachments/123/file.png'); + }); }); describe('api path handling', () => {