mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-01 16:44:16 +00:00
233 lines
8.0 KiB
TypeScript
233 lines
8.0 KiB
TypeScript
// Network behavior of sendRequest lives in send-request.spec.ts.
|
|
import { createSendRequest, buildScriptedEntry } from './send-request';
|
|
|
|
jest.mock('../network', () => ({
|
|
makeAxiosInstance: jest.fn()
|
|
}));
|
|
|
|
jest.mock('../utils/http-https-agents', () => ({
|
|
getHttpHttpsAgents: jest.fn()
|
|
}));
|
|
|
|
import { makeAxiosInstance } from '../network';
|
|
import { getHttpHttpsAgents } from '../utils/http-https-agents';
|
|
|
|
const mockMakeAxiosInstance = makeAxiosInstance as jest.Mock;
|
|
const mockGetHttpHttpsAgents = getHttpHttpsAgents as jest.Mock;
|
|
|
|
describe('buildScriptedEntry', () => {
|
|
test('normalizes method to upper case and preserves request fields', () => {
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'get', url: 'https://example.com', headers: { 'x-a': '1' }, data: undefined },
|
|
response: null,
|
|
error: null,
|
|
startedAt: 1000,
|
|
completedAt: 1042
|
|
});
|
|
|
|
expect(entry.request.method).toBe('GET');
|
|
expect(entry.request.url).toBe('https://example.com');
|
|
expect(entry.request.headers).toEqual({ 'x-a': '1' });
|
|
expect(entry.response).toBeNull();
|
|
expect(entry.error).toBeNull();
|
|
expect(entry.startedAt).toBe(1000);
|
|
expect(entry.completedAt).toBe(1042);
|
|
});
|
|
|
|
test('defaults method to GET when not provided', () => {
|
|
const entry = buildScriptedEntry({
|
|
request: { url: 'https://example.com' },
|
|
response: null,
|
|
error: null,
|
|
startedAt: 0,
|
|
completedAt: 0
|
|
});
|
|
expect(entry.request.method).toBe('GET');
|
|
});
|
|
|
|
test('flattens AxiosHeaders-like objects via toJSON for both request and response', () => {
|
|
const headersLike = {
|
|
toJSON: () => ({ 'content-type': 'application/json', 'x-trace': 'abc' })
|
|
};
|
|
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'post', url: 'https://example.com', headers: headersLike, data: { hi: 1 } },
|
|
response: { status: 200, statusText: 'OK', headers: headersLike, data: { ok: true } },
|
|
error: null,
|
|
startedAt: 0,
|
|
completedAt: 10
|
|
});
|
|
|
|
expect(entry.request.headers).toEqual({ 'content-type': 'application/json', 'x-trace': 'abc' });
|
|
expect(entry.response?.headers).toEqual({ 'content-type': 'application/json', 'x-trace': 'abc' });
|
|
});
|
|
|
|
test('encodes string body to base64 dataBuffer and derives size/duration when not supplied', () => {
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'GET', url: 'https://example.com' },
|
|
response: { status: 200, statusText: 'OK', headers: {}, data: 'hello' },
|
|
error: null,
|
|
startedAt: 5,
|
|
completedAt: 15
|
|
});
|
|
|
|
expect(entry.response?.dataBuffer).toBe(Buffer.from('hello').toString('base64'));
|
|
expect(entry.response?.size).toBe(Buffer.from('hello').length);
|
|
expect(entry.response?.duration).toBe(10);
|
|
});
|
|
|
|
test('JSON-stringifies object body for dataBuffer when not provided', () => {
|
|
const body = { foo: 'bar' };
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'GET', url: 'https://example.com' },
|
|
response: { status: 201, statusText: 'Created', headers: {}, data: body },
|
|
error: null,
|
|
startedAt: 0,
|
|
completedAt: 0
|
|
});
|
|
|
|
expect(entry.response?.dataBuffer).toBe(Buffer.from(JSON.stringify(body)).toString('base64'));
|
|
});
|
|
|
|
test('honors explicit dataBuffer / size / duration on response', () => {
|
|
const explicitBuffer = Buffer.from('payload').toString('base64');
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'GET', url: 'https://example.com' },
|
|
response: {
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: {},
|
|
data: 'ignored-for-size',
|
|
dataBuffer: explicitBuffer,
|
|
size: 999,
|
|
duration: 123
|
|
},
|
|
error: null,
|
|
startedAt: 0,
|
|
completedAt: 50
|
|
});
|
|
|
|
expect(entry.response?.dataBuffer).toBe(explicitBuffer);
|
|
expect(entry.response?.size).toBe(999);
|
|
expect(entry.response?.duration).toBe(123);
|
|
});
|
|
|
|
test('maps error to { message, code } and leaves response null when absent', () => {
|
|
const err = Object.assign(new Error('boom'), { code: 'ECONNREFUSED' });
|
|
|
|
const entry = buildScriptedEntry({
|
|
request: { method: 'GET', url: 'https://example.com' },
|
|
response: null,
|
|
error: err,
|
|
startedAt: 0,
|
|
completedAt: 0
|
|
});
|
|
|
|
expect(entry.response).toBeNull();
|
|
expect(entry.error).toEqual({ message: 'boom', code: 'ECONNREFUSED' });
|
|
});
|
|
});
|
|
|
|
describe('createSendRequest onComplete', () => {
|
|
let mockAxios: jest.Mock;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockAxios = jest.fn();
|
|
mockMakeAxiosInstance.mockReturnValue(mockAxios);
|
|
mockGetHttpHttpsAgents.mockResolvedValue({ httpAgent: null, httpsAgent: null });
|
|
});
|
|
|
|
test('fires once with the entry on a successful no-callback call', async () => {
|
|
mockAxios.mockResolvedValue({
|
|
status: 200,
|
|
statusText: 'OK',
|
|
headers: { 'content-type': 'text/plain' },
|
|
data: 'pong'
|
|
});
|
|
const onComplete = jest.fn();
|
|
const send = createSendRequest(undefined, { onComplete });
|
|
|
|
await send({ method: 'get', url: 'https://example.com/ping' });
|
|
|
|
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
const entry = onComplete.mock.calls[0][0];
|
|
expect(entry.request).toEqual(
|
|
expect.objectContaining({ method: 'GET', url: 'https://example.com/ping' })
|
|
);
|
|
expect(entry.response).toEqual(
|
|
expect.objectContaining({
|
|
statusCode: 200,
|
|
statusText: 'OK',
|
|
headers: { 'content-type': 'text/plain' }
|
|
})
|
|
);
|
|
expect(entry.error).toBeNull();
|
|
});
|
|
|
|
test('records the response carried by a 4xx/5xx axios error', async () => {
|
|
const axiosError: any = new Error('Request failed with status code 404');
|
|
axiosError.response = {
|
|
status: 404,
|
|
statusText: 'Not Found',
|
|
headers: {},
|
|
data: 'missing'
|
|
};
|
|
mockAxios.mockRejectedValue(axiosError);
|
|
const onComplete = jest.fn();
|
|
const send = createSendRequest(undefined, { onComplete });
|
|
|
|
await expect(send({ url: 'https://example.com/missing' })).rejects.toBe(axiosError);
|
|
|
|
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
const entry = onComplete.mock.calls[0][0];
|
|
expect(entry.response).toEqual(
|
|
expect.objectContaining({ statusCode: 404, statusText: 'Not Found' })
|
|
);
|
|
expect(entry.error?.message).toContain('404');
|
|
});
|
|
|
|
test('records error with null response on a pure network failure', async () => {
|
|
const netErr = Object.assign(new Error('ECONNREFUSED'), { code: 'ECONNREFUSED' });
|
|
mockAxios.mockRejectedValue(netErr);
|
|
const onComplete = jest.fn();
|
|
const send = createSendRequest(undefined, { onComplete });
|
|
|
|
await expect(send({ url: 'https://nope.invalid' })).rejects.toBe(netErr);
|
|
|
|
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
const entry = onComplete.mock.calls[0][0];
|
|
expect(entry.response).toBeNull();
|
|
expect(entry.error).toEqual({ message: 'ECONNREFUSED', code: 'ECONNREFUSED' });
|
|
});
|
|
|
|
test('fires exactly once even when a callback is provided', async () => {
|
|
mockAxios.mockResolvedValue({ status: 200, statusText: 'OK', headers: {}, data: 'ok' });
|
|
const onComplete = jest.fn();
|
|
const callback = jest.fn();
|
|
const send = createSendRequest(undefined, { onComplete });
|
|
|
|
await send({ url: 'https://example.com' }, callback);
|
|
|
|
expect(callback).toHaveBeenCalledTimes(1);
|
|
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('a throwing onComplete does not break the request result', async () => {
|
|
const mockResponse = { status: 200, statusText: 'OK', headers: {}, data: 'ok' };
|
|
mockAxios.mockResolvedValue(mockResponse);
|
|
const onComplete = jest.fn(() => { throw new Error('sink blew up'); });
|
|
const send = createSendRequest(undefined, { onComplete });
|
|
|
|
await expect(send({ url: 'https://example.com' })).resolves.toBe(mockResponse);
|
|
expect(onComplete).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
test('does nothing when no onComplete is provided', async () => {
|
|
mockAxios.mockResolvedValue({ status: 200, statusText: 'OK', headers: {}, data: 'ok' });
|
|
const send = createSendRequest();
|
|
|
|
await expect(send({ url: 'https://example.com' })).resolves.toBeDefined();
|
|
});
|
|
});
|