Files
bruno/packages/bruno-requests/src/scripting/send-request.spec.ts
lohit bafb235e72 feat: add certs and proxy config to bru.sendRequest API (#6988)
* feat: add certs and proxy config to bru.sendRequest API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: handle URL string argument in bru.sendRequest

When bru.sendRequest is called with a plain URL string instead of a
config object, the function now normalizes it to { url: string } before
processing. This fixes the case where spreading a string created an
invalid config object.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: add variable interpolation to bru.sendRequest certs and proxy config

Interpolate environment variables in clientCertificates and proxy
configuration for bru.sendRequest API, enabling use of variables like
{{CERT_PATH}} or {{PROXY_HOST}} in certificate paths and proxy settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: use interpolateObject for certs and proxy config interpolation

- Add interpolateObject to electron's interpolate-string.js using
  buildCombinedVars pattern (matches CLI implementation)
- Simplify cert-utils.js by using interpolateObject instead of
  manual field-by-field interpolation
- Add interpolation for clientCertificates and proxy config in CLI's
  run-single-request.js for bru.sendRequest

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: add all variable types to sendRequest interpolation options

- Add globalEnvVars, collectionVariables, folderVariables, requestVariables
  to sendRequestInterpolationOptions for complete variable support
- Use cached system proxy instead of redundant getSystemProxy() call
- Remove duplicate getOptions() call

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: skip CA cert loading when TLS verification is disabled

Only load CA certificates when shouldVerifyTls is true, since they
are not used for validation when TLS verification is disabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 17:59:46 +05:30

199 lines
6.3 KiB
TypeScript

import sendRequest, { createSendRequest } 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('sendRequest', () => {
let mockAxios: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockAxios = jest.fn();
mockMakeAxiosInstance.mockReturnValue(mockAxios);
mockGetHttpHttpsAgents.mockResolvedValue({ httpAgent: null, httpsAgent: null });
});
describe('without callback', () => {
test('should return response directly', async () => {
const mockResponse = { data: 'test', status: 200 };
mockAxios.mockResolvedValue(mockResponse);
const result = await sendRequest({ url: 'http://example.com' });
expect(result).toBe(mockResponse);
});
test('should reject on request error', async () => {
const error = new Error('Network error');
mockAxios.mockRejectedValue(error);
await expect(sendRequest({ url: 'http://example.com' })).rejects.toThrow('Network error');
});
test('should handle URL string instead of config object', async () => {
const mockResponse = { data: 'pong', status: 200 };
mockAxios.mockResolvedValue(mockResponse);
const result = await sendRequest('http://example.com/ping');
expect(result).toBe(mockResponse);
expect(mockAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'http://example.com/ping'
})
);
});
});
describe('with callback', () => {
test('should call callback with response and return response', async () => {
const mockResponse = { data: 'test', status: 200 };
mockAxios.mockResolvedValue(mockResponse);
const callback = jest.fn();
const result = await sendRequest({ url: 'http://example.com' }, callback);
expect(callback).toHaveBeenCalledWith(null, mockResponse);
expect(result).toBe(mockResponse);
});
test('should call callback with error on request failure', async () => {
const error = new Error('Network error');
mockAxios.mockRejectedValue(error);
const callback = jest.fn();
await sendRequest({ url: 'http://example.com' }, callback);
expect(callback).toHaveBeenCalledWith(error, null);
});
test('should reject if callback throws on success', async () => {
const mockResponse = { data: 'test', status: 200 };
mockAxios.mockResolvedValue(mockResponse);
const callbackError = new Error('Callback error');
const callback = jest.fn().mockRejectedValue(callbackError);
await expect(sendRequest({ url: 'http://example.com' }, callback)).rejects.toThrow(
'Callback error'
);
});
test('should reject if callback throws on error', async () => {
const requestError = new Error('Network error');
mockAxios.mockRejectedValue(requestError);
const callbackError = new Error('Callback error');
const callback = jest.fn().mockRejectedValue(callbackError);
await expect(sendRequest({ url: 'http://example.com' }, callback)).rejects.toThrow(
'Callback error'
);
});
});
});
describe('createSendRequest', () => {
let mockAxios: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockAxios = jest.fn();
mockMakeAxiosInstance.mockReturnValue(mockAxios);
});
test('should apply agents from config', async () => {
const mockHttpAgent = { name: 'httpAgent' };
const mockHttpsAgent = { name: 'httpsAgent' };
mockGetHttpHttpsAgents.mockResolvedValue({
httpAgent: mockHttpAgent,
httpsAgent: mockHttpsAgent
});
const mockResponse = { data: 'test' };
mockAxios.mockResolvedValue(mockResponse);
const customSendRequest = createSendRequest({ proxyConfig: {} });
await customSendRequest({ url: 'https://example.com' });
expect(mockGetHttpHttpsAgents).toHaveBeenCalledWith({
proxyConfig: {},
requestUrl: 'https://example.com'
});
expect(mockAxios).toHaveBeenCalledWith(
expect.objectContaining({
httpAgent: mockHttpAgent,
httpsAgent: mockHttpsAgent
})
);
});
test('should not override agents if already set in requestConfig', async () => {
const configHttpAgent = { name: 'configAgent' };
const configHttpsAgent = { name: 'configHttpsAgent' };
mockGetHttpHttpsAgents.mockResolvedValue({
httpAgent: { name: 'ignored' },
httpsAgent: { name: 'ignored' }
});
mockAxios.mockResolvedValue({ data: 'test' });
const customSendRequest = createSendRequest({ proxyConfig: {} });
await customSendRequest({
url: 'https://example.com',
httpAgent: configHttpAgent,
httpsAgent: configHttpsAgent
});
expect(mockAxios).toHaveBeenCalledWith(
expect.objectContaining({
httpAgent: configHttpAgent,
httpsAgent: configHttpsAgent
})
);
});
test('should not call getHttpHttpsAgents when no config provided', async () => {
mockAxios.mockResolvedValue({ data: 'test' });
const customSendRequest = createSendRequest();
await customSendRequest({ url: 'https://example.com' });
expect(mockGetHttpHttpsAgents).not.toHaveBeenCalled();
});
test('should handle URL string and apply agents from config', async () => {
const mockHttpAgent = { name: 'httpAgent' };
const mockHttpsAgent = { name: 'httpsAgent' };
mockGetHttpHttpsAgents.mockResolvedValue({
httpAgent: mockHttpAgent,
httpsAgent: mockHttpsAgent
});
const mockResponse = { data: 'pong' };
mockAxios.mockResolvedValue(mockResponse);
const customSendRequest = createSendRequest({ collectionPath: '/test' });
const result = await customSendRequest('https://example.com/ping');
expect(result).toBe(mockResponse);
expect(mockGetHttpHttpsAgents).toHaveBeenCalledWith({
collectionPath: '/test',
requestUrl: 'https://example.com/ping'
});
expect(mockAxios).toHaveBeenCalledWith(
expect.objectContaining({
url: 'https://example.com/ping',
httpAgent: mockHttpAgent,
httpsAgent: mockHttpsAgent
})
);
});
});