286 lines
No EOL
8.6 KiB
TypeScript
286 lines
No EOL
8.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, mock } from 'bun:test';
|
|
import { fetch } from '../src/fetch';
|
|
|
|
describe('Enhanced Fetch', () => {
|
|
let originalFetch: typeof globalThis.fetch;
|
|
let mockFetch: any;
|
|
let mockLogger: any;
|
|
|
|
beforeEach(() => {
|
|
originalFetch = globalThis.fetch;
|
|
mockFetch = mock(() => Promise.resolve(new Response('test')));
|
|
globalThis.fetch = mockFetch;
|
|
|
|
mockLogger = {
|
|
debug: mock(() => {}),
|
|
info: mock(() => {}),
|
|
error: mock(() => {}),
|
|
};
|
|
});
|
|
|
|
afterEach(() => {
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
describe('basic fetch', () => {
|
|
it('should make simple GET request', async () => {
|
|
const mockResponse = new Response('test data', { status: 200 });
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const response = await fetch('https://api.example.com/data');
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
|
|
method: 'GET',
|
|
headers: {},
|
|
});
|
|
expect(response).toBe(mockResponse);
|
|
});
|
|
|
|
it('should make POST request with body', async () => {
|
|
const mockResponse = new Response('created', { status: 201 });
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const body = JSON.stringify({ name: 'test' });
|
|
const response = await fetch('https://api.example.com/data', {
|
|
method: 'POST',
|
|
body,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
|
|
method: 'POST',
|
|
body,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
});
|
|
expect(response).toBe(mockResponse);
|
|
});
|
|
|
|
it('should handle URL objects', async () => {
|
|
const mockResponse = new Response('test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const url = new URL('https://api.example.com/data');
|
|
await fetch(url);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(url, expect.any(Object));
|
|
});
|
|
|
|
it('should handle Request objects', async () => {
|
|
const mockResponse = new Response('test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const request = new Request('https://api.example.com/data', {
|
|
method: 'PUT',
|
|
});
|
|
await fetch(request);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(request, expect.any(Object));
|
|
});
|
|
});
|
|
|
|
describe('proxy support', () => {
|
|
it('should add proxy to request options', async () => {
|
|
const mockResponse = new Response('proxy test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await fetch('https://api.example.com/data', {
|
|
proxy: 'http://proxy.example.com:8080',
|
|
});
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining({
|
|
proxy: 'http://proxy.example.com:8080',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should handle null proxy', async () => {
|
|
const mockResponse = new Response('no proxy');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await fetch('https://api.example.com/data', {
|
|
proxy: null,
|
|
});
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.not.objectContaining({
|
|
proxy: expect.anything(),
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('timeout support', () => {
|
|
it('should handle timeout', async () => {
|
|
mockFetch.mockImplementation((url, options) => {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => resolve(new Response('delayed')), 100);
|
|
|
|
// Listen for abort signal
|
|
if (options?.signal) {
|
|
options.signal.addEventListener('abort', () => {
|
|
clearTimeout(timeoutId);
|
|
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
await expect(
|
|
fetch('https://api.example.com/data', { timeout: 50 })
|
|
).rejects.toThrow('The operation was aborted');
|
|
});
|
|
|
|
it('should clear timeout on success', async () => {
|
|
const mockResponse = new Response('quick response');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const response = await fetch('https://api.example.com/data', {
|
|
timeout: 1000,
|
|
});
|
|
|
|
expect(response).toBe(mockResponse);
|
|
});
|
|
|
|
it('should clear timeout on error', async () => {
|
|
mockFetch.mockRejectedValue(new Error('Network error'));
|
|
|
|
await expect(
|
|
fetch('https://api.example.com/data', { timeout: 1000 })
|
|
).rejects.toThrow('Network error');
|
|
});
|
|
});
|
|
|
|
describe('logging', () => {
|
|
it('should log request details', async () => {
|
|
const mockResponse = new Response('test', {
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: new Headers({ 'content-type': 'text/plain' }),
|
|
});
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await fetch('https://api.example.com/data', {
|
|
logger: mockLogger,
|
|
method: 'POST',
|
|
headers: { Authorization: 'Bearer token' },
|
|
});
|
|
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('HTTP request', {
|
|
method: 'POST',
|
|
url: 'https://api.example.com/data',
|
|
headers: { Authorization: 'Bearer token' },
|
|
proxy: null,
|
|
});
|
|
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('HTTP response', {
|
|
url: 'https://api.example.com/data',
|
|
status: 200,
|
|
statusText: 'OK',
|
|
ok: true,
|
|
headers: { 'content-type': 'text/plain' },
|
|
});
|
|
});
|
|
|
|
it('should log errors', async () => {
|
|
const error = new Error('Connection failed');
|
|
mockFetch.mockRejectedValue(error);
|
|
|
|
await expect(
|
|
fetch('https://api.example.com/data', { logger: mockLogger })
|
|
).rejects.toThrow('Connection failed');
|
|
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('HTTP error', {
|
|
url: 'https://api.example.com/data',
|
|
error: 'Connection failed',
|
|
name: 'Error',
|
|
});
|
|
});
|
|
|
|
it('should use console as default logger', async () => {
|
|
const consoleSpy = mock(console.debug);
|
|
console.debug = consoleSpy;
|
|
|
|
const mockResponse = new Response('test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await fetch('https://api.example.com/data');
|
|
|
|
expect(consoleSpy).toHaveBeenCalledTimes(2); // Request and response
|
|
|
|
console.debug = originalFetch as any;
|
|
});
|
|
});
|
|
|
|
describe('request options', () => {
|
|
it('should forward all standard RequestInit options', async () => {
|
|
const mockResponse = new Response('test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
const controller = new AbortController();
|
|
const options = {
|
|
method: 'PATCH' as const,
|
|
headers: { 'X-Custom': 'value' },
|
|
body: 'data',
|
|
signal: controller.signal,
|
|
credentials: 'include' as const,
|
|
cache: 'no-store' as const,
|
|
redirect: 'manual' as const,
|
|
referrer: 'https://referrer.com',
|
|
referrerPolicy: 'no-referrer' as const,
|
|
integrity: 'sha256-hash',
|
|
keepalive: true,
|
|
mode: 'cors' as const,
|
|
};
|
|
|
|
await fetch('https://api.example.com/data', options);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining(options)
|
|
);
|
|
});
|
|
|
|
it('should handle undefined options', async () => {
|
|
const mockResponse = new Response('test');
|
|
mockFetch.mockResolvedValue(mockResponse);
|
|
|
|
await fetch('https://api.example.com/data', undefined);
|
|
|
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
'https://api.example.com/data',
|
|
expect.objectContaining({
|
|
method: 'GET',
|
|
headers: {},
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('should propagate fetch errors', async () => {
|
|
const error = new TypeError('Failed to fetch');
|
|
mockFetch.mockRejectedValue(error);
|
|
|
|
await expect(fetch('https://api.example.com/data')).rejects.toThrow(
|
|
'Failed to fetch'
|
|
);
|
|
});
|
|
|
|
it('should handle non-Error objects', async () => {
|
|
mockFetch.mockRejectedValue('string error');
|
|
|
|
await expect(
|
|
fetch('https://api.example.com/data', { logger: mockLogger })
|
|
).rejects.toBe('string error');
|
|
|
|
expect(mockLogger.debug).toHaveBeenCalledWith('HTTP error', {
|
|
url: 'https://api.example.com/data',
|
|
error: 'string error',
|
|
name: 'Unknown',
|
|
});
|
|
});
|
|
});
|
|
}); |