added cli-covarage tool and fixed more tests
This commit is contained in:
parent
b63e58784c
commit
b845a8eade
57 changed files with 11917 additions and 295 deletions
286
libs/utils/test/fetch.test.ts
Normal file
286
libs/utils/test/fetch.test.ts
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue