Skip to content

Commit 824e262

Browse files
committed
Add unit tests for baseFetch helpers
1 parent fbc3b70 commit 824e262

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { buildUrl, buildHeaders } from '../base-fetch.js';
3+
4+
describe('internal: buildUrl', () => {
5+
describe('base URL normalization', () => {
6+
it('strips trailing slash from base URL', () => {
7+
expect(buildUrl('https://example.com/', '/endpoint')).toBe('https://example.com/endpoint');
8+
});
9+
10+
it('handles base URL without trailing slash', () => {
11+
expect(buildUrl('https://example.com', '/endpoint')).toBe('https://example.com/endpoint');
12+
});
13+
});
14+
15+
describe('endpoint normalization', () => {
16+
it('handles endpoint with leading slash', () => {
17+
expect(buildUrl('https://example.com', '/endpoint')).toBe('https://example.com/endpoint');
18+
});
19+
20+
it('adds leading slash to endpoint when missing', () => {
21+
expect(buildUrl('https://example.com', 'endpoint')).toBe('https://example.com/endpoint');
22+
});
23+
});
24+
25+
describe('query params', () => {
26+
it('appends query params to URL', () => {
27+
expect(buildUrl('https://example.com', '/endpoint', { foo: 'bar' })).toBe(
28+
'https://example.com/endpoint?foo=bar',
29+
);
30+
});
31+
32+
it('appends multiple query params', () => {
33+
expect(buildUrl('https://example.com', '/endpoint', { foo: 'bar', baz: 'qux' })).toBe(
34+
'https://example.com/endpoint?foo=bar&baz=qux',
35+
);
36+
});
37+
38+
it('encodes query param values', () => {
39+
expect(buildUrl('https://example.com', '/endpoint', { foo: 'bar baz' })).toBe(
40+
'https://example.com/endpoint?foo=bar+baz',
41+
);
42+
});
43+
44+
it('returns URL without query string when queryParams is undefined', () => {
45+
expect(buildUrl('https://example.com', '/endpoint')).toBe('https://example.com/endpoint');
46+
});
47+
48+
it('returns URL without query string when queryParams is empty', () => {
49+
expect(buildUrl('https://example.com', '/endpoint', {})).toBe('https://example.com/endpoint');
50+
});
51+
});
52+
});
53+
54+
describe('internal: buildHeaders', () => {
55+
describe('extra headers', () => {
56+
it('returns extra headers when provided', () => {
57+
expect(buildHeaders(undefined, { 'X-Custom': 'value' })).toEqual({ 'X-Custom': 'value' });
58+
});
59+
60+
it('returns empty object when no args provided', () => {
61+
expect(buildHeaders()).toEqual({});
62+
});
63+
});
64+
65+
describe('User-Agent', () => {
66+
it('uses custom userAgent when provided', () => {
67+
const result = buildHeaders('custom-agent');
68+
expect(result['User-Agent']).toBe('custom-agent');
69+
});
70+
71+
it('merges custom userAgent with extra headers', () => {
72+
const result = buildHeaders('custom-agent', { 'X-Custom': 'value' });
73+
expect(result).toEqual({ 'User-Agent': 'custom-agent', 'X-Custom': 'value' });
74+
});
75+
76+
it('uses default User-Agent when outside browser (no navigator)', () => {
77+
const originalNavigator = global.navigator;
78+
// @ts-expect-error: faking a non-browser (Node) environment
79+
delete global.navigator;
80+
81+
const result = buildHeaders();
82+
expect(result['User-Agent']).toMatch(/^hibp \d+\.\d+\.\d+/);
83+
84+
global.navigator = originalNavigator;
85+
});
86+
87+
it('does not set User-Agent when inside browser (navigator exists)', () => {
88+
const originalNavigator = global.navigator;
89+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
90+
global.navigator = {} as Navigator;
91+
92+
const result = buildHeaders();
93+
expect(result['User-Agent']).toBeUndefined();
94+
95+
global.navigator = originalNavigator;
96+
});
97+
});
98+
});

src/api/base-fetch.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export async function baseFetch({
2626
return fetch(url, requestInit);
2727
}
2828

29-
function buildUrl(baseUrl: string, endpoint: string, queryParams?: Record<string, string>): string {
29+
export function buildUrl(
30+
baseUrl: string,
31+
endpoint: string,
32+
queryParams?: Record<string, string>,
33+
): string {
3034
const base = baseUrl.replace(/\/$/g, '');
3135
const normalizedEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
3236
const url = new URL(`${base}${normalizedEndpoint}`);
@@ -40,7 +44,10 @@ function buildUrl(baseUrl: string, endpoint: string, queryParams?: Record<string
4044
return url.toString();
4145
}
4246

43-
function buildHeaders(userAgent?: string, extra?: Record<string, string>): Record<string, string> {
47+
export function buildHeaders(
48+
userAgent?: string,
49+
extra?: Record<string, string>,
50+
): Record<string, string> {
4451
const headers: Record<string, string> = { ...extra };
4552

4653
if (userAgent) {

0 commit comments

Comments
 (0)