163 lines
4.3 KiB
TypeScript
163 lines
4.3 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { createPlatformClient, ApiError } from './index.js';
|
|
|
|
describe('createPlatformClient', () => {
|
|
const baseConfig = {
|
|
baseUrl: 'http://localhost:4003/api',
|
|
productId: 'testapp',
|
|
getAccessToken: () => 'test-token',
|
|
};
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should make GET requests with auth header', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({ items: [] }),
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
const api = createPlatformClient(baseConfig);
|
|
const result = await api.get('/items');
|
|
|
|
expect(result).toEqual({ items: [] });
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
'http://localhost:4003/api/items',
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
headers: expect.objectContaining({
|
|
Authorization: 'Bearer test-token',
|
|
'x-product-id': 'testapp',
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should make POST requests with body', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({ id: '1' }),
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
const api = createPlatformClient(baseConfig);
|
|
const result = await api.post('/items', { name: 'test' });
|
|
|
|
expect(result).toEqual({ id: '1' });
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
'http://localhost:4003/api/items',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({ name: 'test' }),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle 204 No Content', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 204,
|
|
json: () => Promise.reject(new Error('no body')),
|
|
})
|
|
);
|
|
|
|
const api = createPlatformClient(baseConfig);
|
|
const result = await api.del('/items/1');
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it('should throw ApiError on non-OK response', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockResolvedValue({
|
|
ok: false,
|
|
status: 400,
|
|
json: () => Promise.resolve({ message: 'Bad request' }),
|
|
})
|
|
);
|
|
|
|
const api = createPlatformClient(baseConfig);
|
|
|
|
await expect(api.get('/items')).rejects.toThrow(ApiError);
|
|
try {
|
|
await api.get('/items');
|
|
} catch (e) {
|
|
expect(e).toBeInstanceOf(ApiError);
|
|
expect((e as ApiError).status).toBe(400);
|
|
}
|
|
});
|
|
|
|
it('should attempt token refresh on 401', async () => {
|
|
let callCount = 0;
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn().mockImplementation(() => {
|
|
callCount++;
|
|
if (callCount === 1) {
|
|
return Promise.resolve({
|
|
ok: false,
|
|
status: 401,
|
|
json: () => Promise.resolve({ message: 'Unauthorized' }),
|
|
});
|
|
}
|
|
return Promise.resolve({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({ refreshed: true }),
|
|
});
|
|
})
|
|
);
|
|
|
|
const refreshFn = vi.fn().mockResolvedValue(true);
|
|
const api = createPlatformClient({
|
|
...baseConfig,
|
|
refreshAccessToken: refreshFn,
|
|
});
|
|
|
|
const result = await api.get('/items');
|
|
expect(refreshFn).toHaveBeenCalledOnce();
|
|
expect(result).toEqual({ refreshed: true });
|
|
});
|
|
|
|
it('should not include auth header when token is null', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({}),
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
const api = createPlatformClient({
|
|
...baseConfig,
|
|
getAccessToken: () => null,
|
|
});
|
|
await api.get('/public/items');
|
|
|
|
const headers = fetchMock.mock.calls[0][1].headers as Record<string, string>;
|
|
expect(headers['Authorization']).toBeUndefined();
|
|
});
|
|
|
|
it('should include x-request-id header', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve({}),
|
|
});
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
|
|
const api = createPlatformClient(baseConfig);
|
|
await api.get('/items');
|
|
|
|
const headers = fetchMock.mock.calls[0][1].headers as Record<string, string>;
|
|
expect(headers['x-request-id']).toBeDefined();
|
|
expect(headers['x-request-id'].length).toBeGreaterThan(0);
|
|
});
|
|
});
|