Skip to content

Commit c2ed2da

Browse files
committed
fix: set baseUrl to empty string on axios call, but pass it again in getUrl while including axios defaults
1 parent f9f5514 commit c2ed2da

File tree

4 files changed

+197
-4
lines changed

4 files changed

+197
-4
lines changed

packages/openapi-ts/src/plugins/@hey-api/client-axios/__tests__/client.test.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,187 @@ describe('serialized request body handling', () => {
168168
},
169169
);
170170
});
171+
172+
describe('calling axios instance', () => {
173+
it.each([
174+
{
175+
description: 'with absolute baseURL',
176+
expectedURL: 'https://api.example.com/users',
177+
instanceBaseURL: 'https://api.example.com',
178+
optionsURL: '/users',
179+
},
180+
{
181+
description: 'without baseURL',
182+
expectedURL: '/users',
183+
instanceBaseURL: undefined,
184+
optionsURL: '/users',
185+
},
186+
{
187+
description: 'with relative baseURL',
188+
expectedURL: '/some-base-url/users',
189+
instanceBaseURL: '/some-base-url',
190+
optionsURL: '/users',
191+
},
192+
])(
193+
'should call the axios instance with correct baseURL and url $description configured via createClient',
194+
async ({ expectedURL, instanceBaseURL, optionsURL }) => {
195+
const client = createClient({ baseURL: instanceBaseURL });
196+
const mockAxios = vi.fn().mockResolvedValue({ data: { ok: true } });
197+
198+
const options = {
199+
axios: mockAxios as Partial<AxiosInstance> as AxiosInstance,
200+
headers: {},
201+
url: optionsURL,
202+
};
203+
204+
await client.get(options);
205+
206+
expect(mockAxios).toHaveBeenCalledWith(
207+
expect.objectContaining({
208+
baseURL: '',
209+
url: expectedURL,
210+
}),
211+
);
212+
},
213+
);
214+
215+
it.each([
216+
{
217+
description: 'with absolute baseURL',
218+
expectedURL: 'https://api.example.com/some-base-url/users',
219+
optionsBaseURL: 'https://api.example.com/some-base-url',
220+
optionsURL: '/users',
221+
},
222+
{
223+
description: 'with relative baseURL',
224+
expectedURL: '/some-base-url/users',
225+
optionsBaseURL: '/some-base-url',
226+
optionsURL: '/users',
227+
},
228+
])(
229+
'should call the axios instance with correct url $description configured via request options',
230+
async ({ expectedURL, optionsBaseURL, optionsURL }) => {
231+
const client = createClient({
232+
baseURL: 'base-url-that-will-be-overridden',
233+
});
234+
235+
const mockAxios = vi.fn().mockResolvedValue({ data: { ok: true } });
236+
237+
const options = {
238+
axios: mockAxios as Partial<AxiosInstance> as AxiosInstance,
239+
baseURL: optionsBaseURL,
240+
headers: {},
241+
url: optionsURL,
242+
};
243+
244+
await client.get(options);
245+
246+
expect(mockAxios).toHaveBeenCalledWith(
247+
expect.objectContaining({
248+
baseURL: '',
249+
url: expectedURL,
250+
}),
251+
);
252+
},
253+
);
254+
255+
it('should call the axios instance with correct url when baseURL configured via axios defaults', async () => {
256+
const client = createClient();
257+
258+
const mockAxios = vi.fn().mockResolvedValue({
259+
data: { ok: true },
260+
}) as Partial<AxiosInstance> as AxiosInstance;
261+
mockAxios.defaults = { baseURL: '/some-base-url', headers: {} as any };
262+
263+
const options = {
264+
axios: mockAxios,
265+
headers: {},
266+
url: '/users',
267+
};
268+
269+
await client.get(options);
270+
271+
expect(mockAxios).toHaveBeenCalledWith(
272+
expect.objectContaining({
273+
baseURL: '',
274+
url: '/some-base-url/users',
275+
}),
276+
);
277+
});
278+
279+
it('should prefer passed baseUrl over configs', async () => {
280+
const client = createClient({ baseURL: 'base-url-from-config-1' });
281+
282+
const mockAxios = vi.fn().mockResolvedValue({
283+
data: { ok: true },
284+
}) as Partial<AxiosInstance> as AxiosInstance;
285+
mockAxios.defaults = {
286+
baseURL: 'base-url-from-config-2',
287+
headers: {} as any,
288+
};
289+
290+
const options = {
291+
axios: mockAxios,
292+
baseURL: '/some-base-url',
293+
headers: {},
294+
url: '/users',
295+
};
296+
297+
await client.get(options);
298+
299+
expect(mockAxios).toHaveBeenCalledWith(
300+
expect.objectContaining({
301+
baseURL: '',
302+
url: '/some-base-url/users',
303+
}),
304+
);
305+
});
306+
});
307+
308+
// TODO: remove this after confirmation
309+
describe('confirming axios behaviour for constructing URLs', () => {
310+
it('resolves relative URLs against baseURL', async () => {
311+
const client = axios.create({
312+
baseURL: 'https://api.example.com',
313+
});
314+
315+
const config = client.getUri({ url: '/users' });
316+
expect(config).toBe('https://api.example.com/users');
317+
});
318+
319+
it('resolves relative URLs against relative baseURL', async () => {
320+
const client = axios.create({
321+
baseURL: '/example',
322+
});
323+
324+
const config = client.getUri({ url: '/users' });
325+
expect(config).toBe('/example/users');
326+
});
327+
328+
it('does not prepend baseURL when url is absolute', async () => {
329+
const client = axios.create({
330+
baseURL: 'https://api.example.com',
331+
});
332+
333+
const config = client.getUri({ url: 'https://other.com/users' });
334+
expect(config).toBe('https://other.com/users');
335+
});
336+
337+
it('does not prepend baseURL when overriding baseURL with empty string', async () => {
338+
const client = axios.create({
339+
baseURL: 'https://api.example.com',
340+
});
341+
342+
const config = client.getUri({ baseURL: '', url: '/users' });
343+
expect(config).toBe('/users');
344+
});
345+
346+
it('does prepend baseURL when overriding baseURL with undefined', async () => {
347+
const client = axios.create({
348+
baseURL: 'https://api.example.com',
349+
});
350+
351+
const config = client.getUri({ baseURL: undefined, url: '/users' });
352+
expect(config).toBe('https://api.example.com/users');
353+
});
354+
});

packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const createClient = (config: Config = {}): Client => {
7878
const { auth, ...optsWithoutAuth } = opts;
7979
const response = await _axios({
8080
...optsWithoutAuth,
81-
baseURL: opts.baseURL as string,
81+
baseURL: '', // the baseURL is already included in `url`
8282
data: getValidRequestBody(opts),
8383
headers: opts.headers as RawAxiosRequestHeaders,
8484
// let `paramsSerializer()` handle query params if it exists

packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ type BuildUrlFn = <
149149
url: string;
150150
},
151151
>(
152-
options: Pick<TData, 'url'> & Omit<Options<TData>, 'axios'>,
152+
options: Pick<TData, 'url'> & Options<TData>,
153153
) => string;
154154

155155
export type Client = CoreClient<

packages/openapi-ts/src/plugins/@hey-api/client-axios/bundle/utils.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,16 @@ export const setAuthParams = async ({
123123
}
124124
};
125125

126-
export const buildUrl: Client['buildUrl'] = (options) =>
127-
getUrl({
126+
export const buildUrl: Client['buildUrl'] = (options) => {
127+
const instanceBaseUrl = options.axios?.defaults?.baseURL;
128+
129+
const baseUrl =
130+
!!options.baseURL && typeof options.baseURL === 'string'
131+
? options.baseURL
132+
: instanceBaseUrl;
133+
134+
return getUrl({
135+
baseUrl: baseUrl as string,
128136
path: options.path,
129137
// let `paramsSerializer()` handle query params if it exists
130138
query: !options.paramsSerializer ? options.query : undefined,
@@ -134,6 +142,7 @@ export const buildUrl: Client['buildUrl'] = (options) =>
134142
: createQuerySerializer(options.querySerializer),
135143
url: options.url,
136144
});
145+
};
137146

138147
export const mergeConfigs = (a: Config, b: Config): Config => {
139148
const config = { ...a, ...b };

0 commit comments

Comments
 (0)