diff --git a/packages/social/x/src/index.test.ts b/packages/social/x/src/index.test.ts index 5c1c3b41..814cb953 100644 --- a/packages/social/x/src/index.test.ts +++ b/packages/social/x/src/index.test.ts @@ -83,6 +83,28 @@ describe('social-x API posting', () => { }); }); + it('normalizes custom API base URLs before composing tweet paths', async () => { + const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({ + ok: true, + status: 201, + json: async () => ({ data: { id: '1445880548472328194', text: 'Custom base' } }), + } as Response); + + const ctx = { + ...fakeConnectContext({ X_BEARER_TOKEN: 'x-token' }), + dryRun: false, + }; + + await adapter.post(ctx as any, { + body: 'Custom base', + }, { + mode: 'api', + apiBaseUrl: 'https://api.x.example/', + }); + + expect(fetchMock.mock.calls[0]?.[0]).toBe('https://api.x.example/2/tweets'); + }); + it('rejects media attachments without pre-uploaded media ids', async () => { const ctx = { ...fakeConnectContext({ X_BEARER_TOKEN: 'x-token' }), diff --git a/packages/social/x/src/index.ts b/packages/social/x/src/index.ts index 35b5f574..097cf9d4 100644 --- a/packages/social/x/src/index.ts +++ b/packages/social/x/src/index.ts @@ -56,7 +56,8 @@ export default defineSocial({ throw new Error('X media posts require pre-uploaded media IDs in config.mediaIds'); } - const res = await fetch(`${config.apiBaseUrl ?? 'https://api.x.com'}/2/tweets`, { + const apiBaseUrl = (config.apiBaseUrl ?? 'https://api.x.com').replace(/\/+$/, ''); + const res = await fetch(`${apiBaseUrl}/2/tweets`, { method: 'POST', headers: { authorization: `Bearer ${token}`,