mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-27 22:54:07 +00:00
feat: support onFail api to catch errors in pre req (#4581)
support `onFail` api to catch errors in pre req --------- Co-authored-by: Anoop M D <anoop.md1421@gmail.com> Co-authored-by: lohit <lohit@usebruno.com>
This commit is contained in:
@@ -171,7 +171,9 @@ const QueryResult = ({ item, collection, data, dataBuffer, disableRunEventListen
|
||||
</div>
|
||||
{error ? (
|
||||
<div>
|
||||
{hasScriptError ? null : <div className="text-red-500">{formatErrorMessage(error)}</div>}
|
||||
{hasScriptError ? null : (
|
||||
<div className="text-red-500" style={{ whiteSpace: 'pre-line' }}>{formatErrorMessage(error)}</div>
|
||||
)}
|
||||
|
||||
{error && typeof error === 'string' && error.toLowerCase().includes('self signed certificate') ? (
|
||||
<div className="mt-6 muted text-xs">
|
||||
|
||||
@@ -27,7 +27,8 @@ const STATIC_API_HINTS = {
|
||||
'req.setTimeout(timeout)',
|
||||
'req.getExecutionMode()',
|
||||
'req.getName()',
|
||||
'req.disableParsingResponseJson()'
|
||||
'req.disableParsingResponseJson()',
|
||||
'req.onFail(function(err) {})',
|
||||
],
|
||||
res: [
|
||||
'res',
|
||||
|
||||
@@ -697,7 +697,6 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
timeline: error.timeline
|
||||
};
|
||||
}
|
||||
|
||||
if (error?.response) {
|
||||
response = error.response;
|
||||
|
||||
@@ -705,6 +704,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
responseTime = response.headers.get('request-duration');
|
||||
response.headers.delete('request-duration');
|
||||
} else {
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
// if it's not a network error, don't continue
|
||||
// we are not rejecting the promise here and instead returning a response object with `error` which is handled in the `send-http-request` invocation
|
||||
// timeline prop won't be accessible in the usual way in the renderer process if we reject the promise
|
||||
@@ -1163,7 +1164,12 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
...eventData
|
||||
});
|
||||
} catch (error) {
|
||||
if (error?.response && !axios.isCancel(error)) {
|
||||
// Skip further processing if request was cancelled
|
||||
if (axios.isCancel(error)) {
|
||||
throw Promise.reject(error);
|
||||
}
|
||||
|
||||
if (error?.response) {
|
||||
const { data, dataBuffer } = parseDataFromResponse(error.response);
|
||||
error.response.data = data;
|
||||
|
||||
@@ -1187,6 +1193,8 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
...eventData
|
||||
});
|
||||
} else {
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
// if it's not a network error, don't continue
|
||||
throw Promise.reject(error);
|
||||
}
|
||||
@@ -1432,7 +1440,27 @@ const registerNetworkIpc = (mainWindow) => {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes the custom error handler if it exists on the request
|
||||
* @param {Object} request - The request object that may contain an onFailHandler
|
||||
* @param {Error} error - The error that occurred
|
||||
*/
|
||||
const executeRequestOnFailHandler = async (request, error) => {
|
||||
if (!request || typeof request.onFailHandler !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await request.onFailHandler(error);
|
||||
} catch (handlerError) {
|
||||
console.error('Error executing onFail handler', handlerError);
|
||||
// @TODO: This is a temporary solution to display the error message in the response pane. Revisit and handle properly.
|
||||
error.message = `1. Request failed: ${error.message || 'Error occured while executing the request!'}\n2. Error executing onFail handler: ${handlerError.message || 'Unknown error'}`;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = registerNetworkIpc;
|
||||
module.exports.configureRequest = configureRequest;
|
||||
module.exports.getCertsAndProxyConfig = getCertsAndProxyConfig;
|
||||
module.exports.fetchGqlSchemaHandler = fetchGqlSchemaHandler;
|
||||
module.exports.executeRequestOnFailHandler = executeRequestOnFailHandler;
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
const { executeRequestOnFailHandler } = require('../../src/ipc/network/index');
|
||||
const axios = require('axios');
|
||||
|
||||
describe('executeRequestOnFailHandler', () => {
|
||||
let consoleSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should do nothing when request is null', async () => {
|
||||
const error = new Error('Test error');
|
||||
|
||||
await executeRequestOnFailHandler(null, error);
|
||||
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing when request is undefined', async () => {
|
||||
const error = new Error('Test error');
|
||||
|
||||
await executeRequestOnFailHandler(undefined, error);
|
||||
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should do nothing when onFailHandler is not a function', async () => {
|
||||
const request = { onFailHandler: 'not a function' };
|
||||
const error = new Error('Test error');
|
||||
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onFailHandler when it exists and is a function', async () => {
|
||||
const mockHandler = jest.fn();
|
||||
const request = { onFailHandler: mockHandler };
|
||||
const error = new Error('Test error');
|
||||
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
expect(mockHandler).toHaveBeenCalledWith(error);
|
||||
expect(mockHandler).toHaveBeenCalledTimes(1);
|
||||
expect(consoleSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle errors when onFailHandler fails by mutating the error message', async () => {
|
||||
const handlerError = new Error('Handler failed');
|
||||
const mockHandler = jest.fn(() => {
|
||||
throw handlerError;
|
||||
});
|
||||
const request = { onFailHandler: mockHandler };
|
||||
const error = new Error('Original error');
|
||||
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
expect(mockHandler).toHaveBeenCalledWith(error);
|
||||
expect(error.message).toContain('1. Request failed: Original error');
|
||||
expect(error.message).toContain('2. Error executing onFail handler: Handler failed');
|
||||
});
|
||||
|
||||
it('should pass the correct hard error object to the handler for DNS failure', async () => {
|
||||
const mockHandler = jest.fn();
|
||||
const request = { onFailHandler: mockHandler };
|
||||
|
||||
let error;
|
||||
try {
|
||||
await axios.get('https://this-domain-definitely-does-not-exist-12345.com/api/test', {
|
||||
timeout: 5000
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
// Verify this is actually a hard error (no response)
|
||||
expect(error.response).toBeUndefined();
|
||||
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
expect(mockHandler).toHaveBeenCalledWith(error);
|
||||
expect(error.message).toContain('ENOTFOUND'); // DNS resolution failed
|
||||
});
|
||||
|
||||
it('should pass the correct hard error object to the handler for connection timeout', async () => {
|
||||
const mockHandler = jest.fn();
|
||||
const request = { onFailHandler: mockHandler };
|
||||
|
||||
let error;
|
||||
try {
|
||||
await axios.get('http://192.168.255.255:9999/api/test', {
|
||||
timeout: 100
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
// Verify this is actually a hard error (no response)
|
||||
expect(error.response).toBeUndefined();
|
||||
|
||||
await executeRequestOnFailHandler(request, error);
|
||||
|
||||
expect(mockHandler).toHaveBeenCalledWith(error);
|
||||
const passedError = mockHandler.mock.calls[0][0];
|
||||
expect(passedError.response).toBeUndefined(); // Should be undefined for hard errors
|
||||
expect(passedError.code).toBe('ECONNABORTED'); // Connection aborted due to timeout
|
||||
});
|
||||
});
|
||||
@@ -148,6 +148,14 @@ class BrunoRequest {
|
||||
this.timeout = timeout;
|
||||
this.req.timeout = timeout;
|
||||
}
|
||||
|
||||
onFail(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.req.onFailHandler = callback;
|
||||
} else if (callback) {
|
||||
throw new Error(`${callback} is not a function`);
|
||||
}
|
||||
}
|
||||
|
||||
__safeParseJSON(str) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user