mirror of
https://github.com/Kong/insomnia.git
synced 2026-02-15 08:32:11 -05:00
feat(inso): support request timeouts (#9363)
* feat: support request timeouts in inso --------- Co-authored-by: Jack Kavanagh <jackkav@gmail.com>
This commit is contained in:
@@ -43,6 +43,7 @@ export default tseslint.config(
|
||||
'unicorn/relative-url-style': 'error',
|
||||
'unicorn/switch-case-braces': 'error',
|
||||
'unicorn/throw-new-error': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
// 'unicorn/custom-error-definition': 'error', //TODO: Enable this rule
|
||||
// 'unicorn/expiring-todo-comments': 'error', //TODO: Enable this rule
|
||||
// 'unicorn/explicit-length-check': 'error', //TODO: Enable this rule
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .js,.ts,.tsx --cache",
|
||||
"test:unit": "cross-env NO_COLOR=1 vitest run --exclude '**/cli.test.ts'",
|
||||
"test:unit:color": "vitest run --exclude '**/cli.test.ts'",
|
||||
"test:bundle": "vitest cli.test.ts -t \"inso dev bundle\"",
|
||||
"test:binary": "vitest cli.test.ts -t \"inso packaged binary\"",
|
||||
"type-check": "tsc --noEmit --project tsconfig.json",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
// Tests both bundle and packaged versions of the CLI with the same commands and expectations.
|
||||
// Intended to be coarse grained (only checks for success or failure) smoke test to ensure packaging worked as expected.
|
||||
|
||||
@@ -57,6 +58,8 @@ const shouldReturnSuccessCode = [
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/with-missing-env-vars.yml -i req_3fd28aabbb18447abab1f45e6ee4bdc1 --env-var firstkey=first --env-var secondkey=second wrk_c992d40',
|
||||
// globals file path env overrides
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/with-missing-env-vars.yml -i req_3fd28aabbb18447abab1f45e6ee4bdc1 --globals packages/insomnia-inso/src/examples/global-environment.yml wrk_c992d40',
|
||||
// with timeout success
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/timeout-test.yml -i req_two_seconds --requestTimeout 3000 wrk_timeout_test',
|
||||
];
|
||||
|
||||
const shouldReturnErrorCode = [
|
||||
@@ -68,6 +71,8 @@ const shouldReturnErrorCode = [
|
||||
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/db/fixtures/insomnia-v5/with-tests.yaml -e env_env_7c2769 uts_1c6207',
|
||||
// after-response script and test
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/after-response-failed-test.yml wrk_616795 --verbose',
|
||||
// with timeout failure
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-inso/src/examples/timeout-test.yml -i req_two_seconds --requestTimeout 1000 wrk_timeout_test',
|
||||
];
|
||||
beforeAll(async () => {
|
||||
// ensure the test server is running
|
||||
|
||||
@@ -24,7 +24,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type { Workspace } from '~/models/workspace';
|
||||
|
||||
import { type RequestTestResult } from '../../insomnia-scripting-environment/src/objects';
|
||||
import type { RequestTestResult } from '../../insomnia-scripting-environment/src/objects';
|
||||
import packageJson from '../package.json';
|
||||
import { exportSpecification, writeFileWithCliOptions } from './commands/export-specification';
|
||||
import { getRuleSetFileFromFolderByFilename, lintSpecification } from './commands/lint-specification';
|
||||
@@ -402,6 +402,7 @@ export const go = (args?: string[]) => {
|
||||
.option('-r, --reporter <reporter>', `reporter to use, options are [${reporterTypes.join(', ')}]`, defaultReporter)
|
||||
.option('-b, --bail', 'abort ("bail") after first test failure', false)
|
||||
.option('--keepFile', 'do not delete the generated test file', false)
|
||||
.option('--requestTimeout <duration>', 'milliseconds before request times out', undefined) // defaults to user settings
|
||||
.option('-k, --disableCertValidation', 'disable certificate validation for requests with SSL', false)
|
||||
.option('--httpsProxy <proxy>', 'URL for the proxy server for https requests.', proxySettings.httpsProxy)
|
||||
.option('--httpProxy <proxy>', 'URL for the proxy server for http requests.', proxySettings.httpProxy)
|
||||
@@ -430,6 +431,7 @@ export const go = (args?: string[]) => {
|
||||
httpProxy?: string;
|
||||
noProxy?: string;
|
||||
dataFolders: string[];
|
||||
requestTimeout?: string;
|
||||
},
|
||||
) => {
|
||||
const options = await mergeOptionsAndInit(cmd);
|
||||
@@ -494,6 +496,7 @@ export const go = (args?: string[]) => {
|
||||
validateSSL: !options.disableCertValidation,
|
||||
...proxyOptions,
|
||||
dataFolders: options.dataFolders,
|
||||
...(options.requestTimeout ? { timeout: parseInt(options.requestTimeout, 10) } : {}),
|
||||
});
|
||||
// Generate test file
|
||||
const testFileContents = generate(
|
||||
@@ -532,6 +535,7 @@ export const go = (args?: string[]) => {
|
||||
.option('-e, --env <identifier>', 'environment to use', '')
|
||||
.option('-g, --globals <identifier>', 'global environment to use (filepath or id)', '')
|
||||
.option('--delay-request <duration>', 'milliseconds to delay between requests', '0')
|
||||
.option('--requestTimeout <duration>', 'milliseconds before request times out', undefined) // defaults to user settings
|
||||
.option('--env-var <key=value>', 'override environment variables', collect, [])
|
||||
.option('-n, --iteration-count <count>', 'number of times to repeat', '1')
|
||||
.option('-d, --iteration-data <path/url>', 'file path or url (JSON or CSV)', '')
|
||||
@@ -584,6 +588,7 @@ export const go = (args?: string[]) => {
|
||||
output?: string;
|
||||
includeFullData?: 'redact' | 'plaintext';
|
||||
acceptRisk: boolean;
|
||||
requestTimeout?: string;
|
||||
},
|
||||
) => {
|
||||
const options = await mergeOptionsAndInit(cmd);
|
||||
@@ -824,7 +829,12 @@ export const go = (args?: string[]) => {
|
||||
environment._id,
|
||||
db,
|
||||
transientVariables,
|
||||
{ validateSSL: !options.disableCertValidation, ...proxyOptions, dataFolders: options.dataFolders },
|
||||
{
|
||||
validateSSL: !options.disableCertValidation,
|
||||
...proxyOptions,
|
||||
dataFolders: options.dataFolders,
|
||||
...(options.requestTimeout ? { timeout: parseInt(options.requestTimeout, 10) } : {}),
|
||||
},
|
||||
iterationData,
|
||||
iterationCount,
|
||||
);
|
||||
@@ -926,8 +936,8 @@ export const go = (args?: string[]) => {
|
||||
isIdentifierAFile = identifier && (await fs.promises.stat(identifierAsAbsPath)).isFile();
|
||||
} catch (err) {}
|
||||
const pathToSearch = '';
|
||||
let specContent;
|
||||
let rulesetFileName;
|
||||
let specContent: string | undefined;
|
||||
let rulesetFileName: string | undefined;
|
||||
if (isIdentifierAFile) {
|
||||
// try load as a file
|
||||
logger.trace(`Linting specification file from identifier: \`${identifierAsAbsPath}\``);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"_id":"crt_c8325243678d49e19d7282f4c0f156d8","type":"CaCertificate","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1729664881251,"created":1729664881251,"disabled":false,"path":"packages/insomnia-inso/src/db/fixtures/certs/fake_ca.pem","isPrivate":false}
|
||||
{"_id":"crt_c8325243678d49e19d7282f4c0f156d8","type":"CaCertificate","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1729664881251,"created":1729664881251,"disabled":false,"path":"packages/insomnia-smoke-test/fixtures/certificates/rootCA.pem","isPrivate":false}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"_id":"crt_998cf6641ec249389690c821fb16a07b","type":"ClientCertificate","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1727072507372,"created":1727072507372,"host":"insomnia.rest","passphrase":"","disabled":false,"cert":"packages/insomnia-inso/src/db/fixtures/certs/fake_cert.pem","key":"packages/insomnia-inso/src/db/fixtures/certs/fake_key.pem","pfx":null,"isPrivate":false}
|
||||
{"_id":"crt_998cf6641ec249389690c821fb16a07b","type":"ClientCertificate","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1727072507372,"created":1727072507372,"host":"localhost:4011","passphrase":"","disabled":false,"cert":"packages/insomnia-smoke-test/fixtures/certificates/client.crt","key":"packages/insomnia-smoke-test/fixtures/certificates/client.key","pfx":null,"isPrivate":false}
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
{"_id":"req_d50632f88775493485008430eb603237","type":"Request","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1748937359916,"created":1748932973049,"url":"http://localhost:4010/echo","name":"Request B","description":"","method":"POST","body":{"mimeType":"text/plain","text":"{% response 'body', 'req_78933d6e08d642d191577df68979e7e4', 'b64::JC5oZWFkZXJzLmhvc3Q=::46b', 'always', 60 %}"},"parameters":[{"id":"pair_8eadf0929d324c168c70ebbb6fa36ace","name":"","value":"","description":"","disabled":false}],"headers":[{"name":"Content-Type","value":"text/plain","id":"pair_4c18bb0ced1b4b49babd2190c509959c"},{"id":"pair_1cb7018af60042b897aab030f5de8b96","name":"client-id","value":"{{ _['client-id'] }}","description":"","disabled":false}],"authentication":{},"metaSortKey":-1748760800983.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
{"_id":"req_wrk_012d4860c7da418a85ffea7406e1292a21946b60","type":"Request","parentId":"fld_wrk_012d4860c7da418a85ffea7406e1292a30baa249","modified":1593669699112,"created":1593669324881,"url":"{{ base_url }}/global","name":"/global","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1593669324881,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
{"_id":"req_wrk_012d4860c7da418a85ffea7406e1292ab410454b","type":"Request","parentId":"wrk_012d4860c7da418a85ffea7406e1292a","modified":1593669699110,"created":1593669324879,"url":"{{ base_url }}/override","name":"/override","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1593669324879,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
{"_id":"req_wrk_012d4860c7da418a85ffea7406e1292ab410454c","type":"Request","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1593669699110,"created":1593669324879,"url":"https://insomnia.rest","name":"withCertAndCA","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1593669324879,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
{"_id":"req_wrk_012d4860c7da418a85ffea7406e1292ab410454c","type":"Request","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1593669699110,"created":1593669324879,"url":"https://localhost:4011/protected/pets/2","name":"withCertAndCA","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1593669324879,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
{"_id":"req_wrk_012d4860c7da418a85ffea7406e1292ab410454d","type":"Request","parentId":"wrk_0b96eff84c1c4eaa9c6e67ad74bbc85b","modified":1593669699110,"created":1593669324879,"url":"httpbin.org/redirect-to?url=https%3A%2F%2Finsomnia.rest&status_code=302","name":"withSettings","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1593669324879,"isPrivate":false,"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global"}
|
||||
|
||||
43
packages/insomnia-inso/src/examples/timeout-test.yml
Normal file
43
packages/insomnia-inso/src/examples/timeout-test.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
_type: export
|
||||
__export_format: 4
|
||||
__export_date: 2021-01-21T00:00:00.000Z
|
||||
__export_source: insomnia.desktop.app:v2024.1.0
|
||||
resources:
|
||||
- _id: req_two_seconds
|
||||
parentId: wrk_timeout_test
|
||||
modified: 1611212400000
|
||||
created: 1611212400000
|
||||
url: http://127.0.0.1:4010/delay/seconds/2
|
||||
name: request that takes 2 seconds
|
||||
description: ''
|
||||
method: GET
|
||||
body: {}
|
||||
parameters: []
|
||||
metaSortKey: -2
|
||||
isPrivate: false
|
||||
settingStoreCookies: true
|
||||
settingSendCookies: true
|
||||
settingDisableRenderRequestBody: false
|
||||
settingEncodeUrl: true
|
||||
settingRebuildPath: true
|
||||
settingFollowRedirects: global
|
||||
_type: request
|
||||
- _id: wrk_timeout_test
|
||||
parentId: null
|
||||
modified: 1611212400000
|
||||
created: 1611212400000
|
||||
name: Timeout Test Collection
|
||||
description: ''
|
||||
scope: collection
|
||||
_type: workspace
|
||||
- _id: env_timeout_base
|
||||
parentId: wrk_timeout_test
|
||||
modified: 1611212400000
|
||||
created: 1611212400000
|
||||
name: Base Environment
|
||||
data: {}
|
||||
dataPropertyOrder: null
|
||||
color: null
|
||||
isPrivate: false
|
||||
metaSortKey: 1
|
||||
_type: environment
|
||||
@@ -272,7 +272,7 @@ export class Variables {
|
||||
* @returns The value of the variable if found, otherwise undefined
|
||||
*/
|
||||
get = (variableName: string) => {
|
||||
let finalVal: boolean | number | string | object | undefined = undefined;
|
||||
let finalVal: boolean | number | string | object | undefined;
|
||||
[
|
||||
this.localVars,
|
||||
mergeFolderLevelVars(this.folderLevelVars),
|
||||
@@ -387,13 +387,13 @@ export class Vault extends Environment {
|
||||
// throw error on get or set method call if enableVaultInScripts is false
|
||||
get: (target, prop, receiver) => {
|
||||
if (!enableVaultInScripts) {
|
||||
throw 'Vault is disabled in script';
|
||||
throw new Error('Vault is disabled in script');
|
||||
}
|
||||
return Reflect.get(target, prop, receiver);
|
||||
},
|
||||
set: (target, prop, value, receiver) => {
|
||||
if (!enableVaultInScripts) {
|
||||
throw 'Vault is disabled in script';
|
||||
throw new Error('Vault is disabled in script');
|
||||
}
|
||||
return Reflect.set(target, prop, value, receiver);
|
||||
},
|
||||
@@ -402,16 +402,16 @@ export class Vault extends Environment {
|
||||
|
||||
/** @ignore */
|
||||
unset = () => {
|
||||
throw 'Vault can not be unset in script';
|
||||
throw new Error('Vault can not be unset in script');
|
||||
};
|
||||
|
||||
/** @ignore */
|
||||
clear = () => {
|
||||
throw 'Vault can not be cleared in script';
|
||||
throw new Error('Vault can not be cleared in script');
|
||||
};
|
||||
|
||||
/** @ignore */
|
||||
set = () => {
|
||||
throw 'Vault can not be set in script';
|
||||
throw new Error('Vault can not be set in script');
|
||||
};
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@ export function transformToSdkProxyOptions(
|
||||
let sanitizedProxy = bestProxy;
|
||||
if (!bestProxy.includes('://')) {
|
||||
getExistingConsole().warn(`The protocol is missing for proxy, 'https:' is enabled for: ${bestProxy}`);
|
||||
sanitizedProxy = 'https://' + bestProxy;
|
||||
sanitizedProxy = `https://${bestProxy}`;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -360,7 +360,7 @@ export function transformToSdkProxyOptions(
|
||||
proxy.authenticate = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw `Failed to parse proxy (${sanitizedProxy}): ${e.message}`;
|
||||
throw new Error(`Failed to parse proxy (${sanitizedProxy}): ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ collection:
|
||||
send: true
|
||||
store: true
|
||||
- name: New Request
|
||||
url: http://localhost:4010/echo
|
||||
meta:
|
||||
id: req_845c9ad3b2934f2098cb77a5dce8b0f5
|
||||
created: 1745481338217
|
||||
|
||||
@@ -28,7 +28,7 @@ test.describe('gRPC interactions', () => {
|
||||
await page.click('text=Send');
|
||||
|
||||
// Check for the single Unary response
|
||||
await page.click('text=Response 1');
|
||||
await page.getByRole('tab', { name: 'Response 1', exact: true }).click();
|
||||
await expect.soft(statusTag).toContainText('0 OK');
|
||||
await expect.soft(responseBody).toContainText('Berkshire Valley Management Area Trail');
|
||||
|
||||
@@ -42,8 +42,8 @@ test.describe('gRPC interactions', () => {
|
||||
await streamMessage.click();
|
||||
|
||||
// Check for the 3rd stream and response
|
||||
await page.locator('text=Stream 3').click();
|
||||
await page.locator('text=Response 3').click();
|
||||
await page.getByRole('tab', { name: 'Stream 3', exact: true }).click();
|
||||
await page.getByRole('tab', { name: 'Response 3', exact: true }).click();
|
||||
|
||||
// Finish the stream
|
||||
await page.locator('text=Commit').click();
|
||||
@@ -60,8 +60,8 @@ test.describe('gRPC interactions', () => {
|
||||
|
||||
// Finish the stream and check response
|
||||
await page.locator('text=Commit').click();
|
||||
await page.locator('text=Stream 3').click();
|
||||
await page.locator('text=Response 1').click();
|
||||
await page.getByRole('tab', { name: 'Stream 3', exact: true }).click();
|
||||
await page.getByRole('tab', { name: 'Response 1', exact: true }).click();
|
||||
await expect.soft(statusTag).toContainText('0 OK');
|
||||
await expect.soft(responseBody).toContainText('point_count": 3');
|
||||
|
||||
@@ -72,6 +72,6 @@ test.describe('gRPC interactions', () => {
|
||||
// Check response
|
||||
await expect.soft(statusTag).toContainText('0 OK');
|
||||
await expect.soft(responseBody).toContainText('Patriots Path');
|
||||
await page.locator('text=Response 64').click();
|
||||
await page.getByRole('tab', { name: 'Response 64', exact: true }).click();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -382,13 +382,14 @@ test.describe('pre-request features tests', () => {
|
||||
// update proxy configuration
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.locator('text=Insomnia Preferences').first().click();
|
||||
|
||||
await page.getByLabel('Request timeout (ms)').fill('5000');
|
||||
await page.getByRole('tab', { name: 'Proxy' }).click();
|
||||
await page.locator('text=Enable proxy').click();
|
||||
await page.locator('[name="httpProxy"]').fill('localhost:1111');
|
||||
await page.locator('[name="httpsProxy"]').fill('localhost:2222');
|
||||
await page.locator('[name="noProxy"]').fill('http://a.com,https://b.com');
|
||||
await page.locator('.app').press('Escape');
|
||||
// add 1s timeout to ensure noProxy settings is applied - INS-4155
|
||||
|
||||
await page.getByLabel('Request Collection').getByTestId('test proxies manipulation').press('Enter');
|
||||
await page.getByRole('tab', { name: 'Body' }).click();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { type BaseModel } from '../models';
|
||||
import type { BaseModel } from '../models';
|
||||
import * as models from '../models';
|
||||
import type { Environment, UserUploadEnvironment } from '../models/environment';
|
||||
import { getBodyBuffer } from '../models/response';
|
||||
import type { Settings } from '../models/settings';
|
||||
import {
|
||||
defaultSendActionRuntime,
|
||||
fetchRequestData,
|
||||
responseTransform,
|
||||
sendCurlAndWriteTimeline,
|
||||
@@ -14,12 +15,15 @@ import {
|
||||
tryToExecutePreRequestScript,
|
||||
tryToInterpolateRequest,
|
||||
} from '../network/network';
|
||||
import { defaultSendActionRuntime } from '../network/network';
|
||||
import { database } from './database';
|
||||
|
||||
// The network layer uses settings from the settings model
|
||||
// We want to give consumers the ability to override certain settings
|
||||
type SettingsOverride = Pick<Settings, 'validateSSL' | 'dataFolders'>;
|
||||
interface SettingsOverride {
|
||||
validateSSL?: Settings['validateSSL'];
|
||||
dataFolders?: Settings['dataFolders'];
|
||||
timeout?: Settings['timeout'];
|
||||
}
|
||||
const wrapAroundIterationOverIterationData = (
|
||||
list?: UserUploadEnvironment[],
|
||||
currentIteration?: number,
|
||||
@@ -106,6 +110,11 @@ export async function getSendRequestCallbackMemDb(
|
||||
requestData.responseId,
|
||||
);
|
||||
const res = await responseTransform(response, environmentId, renderedRequest, renderedResult.context);
|
||||
|
||||
if (res.error) {
|
||||
throw new Error(res.error);
|
||||
}
|
||||
|
||||
const postMutatedContext = await tryToExecuteAfterResponseScript({
|
||||
...requestData,
|
||||
...mutatedContext,
|
||||
@@ -113,21 +122,12 @@ export async function getSendRequestCallbackMemDb(
|
||||
transientVariables: mutatedContext.transientVariables || transientVariables,
|
||||
response,
|
||||
});
|
||||
// TODO: figure out how to handle this error
|
||||
if ('error' in postMutatedContext) {
|
||||
console.error(
|
||||
'[network] An error occurred while running after-response script for request named:',
|
||||
renderedRequest.name,
|
||||
);
|
||||
throw {
|
||||
error: postMutatedContext.error,
|
||||
response: await responseTransform(
|
||||
response,
|
||||
requestData.activeEnvironmentId,
|
||||
renderedRequest,
|
||||
renderedResult.context,
|
||||
),
|
||||
};
|
||||
throw new Error(postMutatedContext.error);
|
||||
}
|
||||
const { statusCode: status, statusMessage, headers: headerArray, elapsedTime: responseTime } = res;
|
||||
|
||||
|
||||
@@ -38,9 +38,14 @@ window.bridge.onmessage(
|
||||
const result = await window.bridge.Promise.race([timeoutPromise, runScript(data)]);
|
||||
callback(result);
|
||||
} catch (err) {
|
||||
const errMessage = err.message ? `Error from Pre-request or after-response script:\n${err.message};` : err;
|
||||
const errStack = err.stack ? `Stack: ${err.stack};` : '';
|
||||
const fullErrMessage = `${errMessage}\n${errStack}`;
|
||||
const errMessage = err.message
|
||||
? `Error from Pre-request or after-response script:
|
||||
|
||||
${err.message}`
|
||||
: err;
|
||||
const fullErrMessage = `${errMessage}
|
||||
|
||||
${err.stack ? `Stack: ${err.stack}` : ''}`;
|
||||
Sentry.captureException(errMessage, {
|
||||
tags: {
|
||||
source: 'hidden-window',
|
||||
|
||||
@@ -489,7 +489,6 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe
|
||||
request,
|
||||
environment,
|
||||
timelinePath,
|
||||
responseId,
|
||||
baseEnvironment,
|
||||
clientCertificates,
|
||||
cookieJar,
|
||||
@@ -663,21 +662,8 @@ export const tryToExecuteScript = async (context: RequestAndContextAndOptionalRe
|
||||
timelinePath,
|
||||
serializeNDJSON([{ value: err.message, name: 'Text', timestamp: Date.now() }]),
|
||||
);
|
||||
|
||||
const requestId = request._id;
|
||||
// stack trace is ignored as it is always from preload
|
||||
const errMessage = err.message ? err.message : err;
|
||||
const responsePatch = {
|
||||
_id: responseId,
|
||||
parentId: requestId,
|
||||
environemntId: environment._id,
|
||||
globalEnvironmentId: globals?._id,
|
||||
timelinePath,
|
||||
statusMessage: 'Error',
|
||||
error: errMessage,
|
||||
};
|
||||
const res = await models.response.create(responsePatch, settings.maxHistoryResponses);
|
||||
models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: res._id });
|
||||
return { error: errMessage };
|
||||
}
|
||||
};
|
||||
@@ -951,6 +937,7 @@ export async function sendCurlAndWriteTimeline(
|
||||
};
|
||||
}
|
||||
|
||||
// Apply plugins to response
|
||||
export const responseTransform = async (
|
||||
patch: ResponsePatch,
|
||||
environmentId: string | null,
|
||||
|
||||
@@ -93,6 +93,11 @@ const writeToDownloadPath = (
|
||||
});
|
||||
};
|
||||
|
||||
// Can fail with errors from:
|
||||
// 1. pre-request script
|
||||
// 2. request sending
|
||||
// 3. after-response script
|
||||
// In each case we create a new response with the error message and set it to active response
|
||||
export const sendActionImplementation = async (options: {
|
||||
requestId: string;
|
||||
shouldPromptForPathAfterResponse: boolean | undefined;
|
||||
@@ -103,7 +108,7 @@ export const sendActionImplementation = async (options: {
|
||||
userUploadEnvironment?: UserUploadEnvironment;
|
||||
transientVariables?: Environment;
|
||||
runtime?: SendActionRuntime;
|
||||
}) => {
|
||||
}): Promise<{ nextRequestIdOrName: string | undefined } | undefined> => {
|
||||
const {
|
||||
requestId,
|
||||
userUploadEnvironment,
|
||||
@@ -118,7 +123,7 @@ export const sendActionImplementation = async (options: {
|
||||
|
||||
window.main.startExecution({ requestId });
|
||||
const requestData = await fetchRequestData(requestId);
|
||||
const requestMeta = await models.requestMeta.getByParentId(requestId);
|
||||
const requestMeta = await models.requestMeta.getOrCreateByParentId(requestId);
|
||||
const transientVariables = nullableTransientVariables || {
|
||||
...models.environment.init(),
|
||||
_id: uuidv4(),
|
||||
@@ -139,37 +144,42 @@ export const sendActionImplementation = async (options: {
|
||||
iterationCount,
|
||||
runtime,
|
||||
);
|
||||
|
||||
if ('error' in mutatedContext) {
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
throw {
|
||||
// create response with error info, so that we can store response in db and show it in response viewer
|
||||
response: {
|
||||
const createdResponse = await models.response.create(
|
||||
{
|
||||
_id: requestData.responseId,
|
||||
parentId: requestId,
|
||||
environemntId: requestData.environment,
|
||||
environmentId: requestData.environment._id,
|
||||
statusMessage: 'Error',
|
||||
error: mutatedContext.error,
|
||||
timelinePath: requestData.timelinePath,
|
||||
},
|
||||
maxHistoryResponses: requestData.settings.maxHistoryResponses,
|
||||
requestMeta,
|
||||
error: mutatedContext.error,
|
||||
};
|
||||
requestData.settings.maxHistoryResponses,
|
||||
);
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: createdResponse._id });
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
return { nextRequestIdOrName: mutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
|
||||
if (mutatedContext.execution?.skipRequest) {
|
||||
// cancel request running if skipRequest in pre-request script
|
||||
const responseId = requestData.responseId;
|
||||
const responsePatch = {
|
||||
_id: responseId,
|
||||
parentId: requestId,
|
||||
environemntId: requestData.environment,
|
||||
statusMessage: 'Cancelled',
|
||||
error: 'Request was cancelled by pre-request script',
|
||||
};
|
||||
|
||||
// create and update response to activeResponse
|
||||
await models.response.create(responsePatch, requestData.settings.maxHistoryResponses);
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: responseId });
|
||||
const createdResponse = await models.response.create(
|
||||
{
|
||||
_id: requestData.responseId,
|
||||
parentId: requestId,
|
||||
environmentId: requestData.environment._id,
|
||||
statusMessage: 'Cancelled',
|
||||
error: 'Request was cancelled by pre-request script',
|
||||
timelinePath: requestData.timelinePath,
|
||||
},
|
||||
requestData.settings.maxHistoryResponses,
|
||||
);
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: createdResponse._id });
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
return mutatedContext;
|
||||
return { nextRequestIdOrName: mutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
@@ -198,8 +208,6 @@ export const sendActionImplementation = async (options: {
|
||||
// TODO: remove this temporary hack to support GraphQL variables in the request body properly
|
||||
parseGraphQLReqeustBody(renderedRequest);
|
||||
|
||||
invariant(requestMeta, 'RequestMeta not found');
|
||||
|
||||
window.main.addExecutionStep({ requestId, stepName: 'Sending request' });
|
||||
const response = await sendCurlAndWriteTimeline(
|
||||
renderedRequest,
|
||||
@@ -211,18 +219,22 @@ export const sendActionImplementation = async (options: {
|
||||
runtime,
|
||||
);
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
|
||||
if ('error' in response) {
|
||||
throw {
|
||||
response: await responseTransform(
|
||||
response,
|
||||
requestData.activeEnvironmentId,
|
||||
renderedRequest,
|
||||
renderedResult.context,
|
||||
),
|
||||
maxHistoryResponses: requestData.settings.maxHistoryResponses,
|
||||
requestMeta,
|
||||
error: response.error,
|
||||
};
|
||||
const createdResponse = await models.response.create(
|
||||
{
|
||||
_id: requestData.responseId,
|
||||
parentId: requestId,
|
||||
environmentId: requestData.environment._id,
|
||||
statusMessage: 'Error',
|
||||
error: response.error,
|
||||
timelinePath: requestData.timelinePath,
|
||||
},
|
||||
requestData.settings.maxHistoryResponses,
|
||||
);
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: createdResponse._id });
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
return { nextRequestIdOrName: mutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
|
||||
const baseResponsePatch = await responseTransform(
|
||||
@@ -249,18 +261,22 @@ export const sendActionImplementation = async (options: {
|
||||
iterationCount,
|
||||
runtime,
|
||||
});
|
||||
|
||||
if ('error' in postMutatedContext) {
|
||||
throw {
|
||||
response: await responseTransform(
|
||||
response,
|
||||
requestData.activeEnvironmentId,
|
||||
renderedRequest,
|
||||
renderedResult.context,
|
||||
),
|
||||
maxHistoryResponses: requestData.settings.maxHistoryResponses,
|
||||
requestMeta,
|
||||
error: postMutatedContext.error,
|
||||
};
|
||||
const createdResponse = await models.response.create(
|
||||
{
|
||||
_id: requestData.responseId,
|
||||
parentId: requestId,
|
||||
environmentId: requestData.environment._id,
|
||||
statusMessage: 'Error',
|
||||
error: postMutatedContext.error,
|
||||
timelinePath: requestData.timelinePath,
|
||||
},
|
||||
requestData.settings.maxHistoryResponses,
|
||||
);
|
||||
await models.requestMeta.updateOrCreateByParentId(requestId, { activeResponseId: createdResponse._id });
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
return { nextRequestIdOrName: postMutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
@@ -291,7 +307,7 @@ export const sendActionImplementation = async (options: {
|
||||
if (!shouldWriteToFile) {
|
||||
const response = await models.response.create(responsePatch, requestData.settings.maxHistoryResponses);
|
||||
await models.requestMeta.update(requestMeta, { activeResponseId: response._id });
|
||||
return postMutatedContext;
|
||||
return { nextRequestIdOrName: postMutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
|
||||
if (requestMeta.downloadPath) {
|
||||
@@ -299,12 +315,13 @@ export const sendActionImplementation = async (options: {
|
||||
const name = header
|
||||
? contentDisposition.parse(header.value).parameters.filename
|
||||
: `${requestData.request.name.replace(/\s/g, '-').toLowerCase()}.${(responsePatch.contentType && mimeExtension(responsePatch.contentType)) || 'unknown'}`;
|
||||
return writeToDownloadPath(
|
||||
writeToDownloadPath(
|
||||
path.join(requestMeta.downloadPath, name),
|
||||
responsePatch,
|
||||
requestMeta,
|
||||
requestData.settings.maxHistoryResponses,
|
||||
);
|
||||
return { nextRequestIdOrName: postMutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
const defaultPath = window.localStorage.getItem('insomnia.sendAndDownloadLocation');
|
||||
const { filePath } = await window.dialog.showSaveDialog({
|
||||
@@ -314,10 +331,11 @@ export const sendActionImplementation = async (options: {
|
||||
...(defaultPath ? { defaultPath } : {}),
|
||||
});
|
||||
if (!filePath) {
|
||||
return null;
|
||||
return { nextRequestIdOrName: postMutatedContext.execution?.nextRequestIdOrName };
|
||||
}
|
||||
window.localStorage.setItem('insomnia.sendAndDownloadLocation', filePath);
|
||||
return writeToDownloadPath(filePath, responsePatch, requestMeta, requestData.settings.maxHistoryResponses);
|
||||
writeToDownloadPath(filePath, responsePatch, requestMeta, requestData.settings.maxHistoryResponses);
|
||||
return { nextRequestIdOrName: postMutatedContext.execution?.nextRequestIdOrName };
|
||||
};
|
||||
|
||||
export async function clientAction({ request, params }: Route.ClientActionArgs) {
|
||||
@@ -325,41 +343,24 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
const { shouldPromptForPathAfterResponse, ignoreUndefinedEnvVariable } = (await request.json()) as SendActionParams;
|
||||
|
||||
try {
|
||||
return await sendActionImplementation({
|
||||
await sendActionImplementation({
|
||||
requestId,
|
||||
shouldPromptForPathAfterResponse,
|
||||
ignoreUndefinedEnvVariable,
|
||||
});
|
||||
return null;
|
||||
} catch (error) {
|
||||
const err = error as unknown as {
|
||||
error: any;
|
||||
response?: ResponsePatch & { _id: string };
|
||||
requestMeta?: RequestMeta;
|
||||
maxHistoryResponses?: number;
|
||||
};
|
||||
|
||||
console.log('[request] Failed to send request', err);
|
||||
const e = err.error || err;
|
||||
console.error('[request] Failed to send request', error);
|
||||
// TODO: consider if interpolation errors should be handled in the send request catch block
|
||||
// idea: move missing env variable detection to tryToInterpolateRequest
|
||||
const url = new URL(request.url);
|
||||
|
||||
// when after-script error, there is no error in response, we need to set error info into response, so that we can show it in response viewer
|
||||
if (err.response && err.requestMeta && err.response._id) {
|
||||
if (!err.response.error) {
|
||||
err.response.error = e;
|
||||
err.response.statusMessage = 'Error';
|
||||
err.response.statusCode = 0;
|
||||
}
|
||||
// this part is for persisting useful info (e.g. timeline) for debugging, even there is an error
|
||||
const existingResponse = await models.response.getById(err.response._id);
|
||||
const response = existingResponse || (await models.response.create(err.response, err.maxHistoryResponses));
|
||||
await models.requestMeta.update(err.requestMeta, { activeResponseId: response._id });
|
||||
} else {
|
||||
// if the error is not from response, we need to set it to url param and show it in modal
|
||||
url.searchParams.set('error', e);
|
||||
if (e?.extraInfo && e?.extraInfo?.subType === 'environmentVariable') {
|
||||
url.searchParams.set('envVariableMissing', '1');
|
||||
url.searchParams.set('undefinedEnvironmentVariables', e?.extraInfo?.undefinedEnvironmentVariables);
|
||||
}
|
||||
// if the error is not from response, we need to set it to url param and show it in modal
|
||||
const e = error.error || error;
|
||||
url.searchParams.set('error', e);
|
||||
if (e?.extraInfo && e?.extraInfo?.subType === 'environmentVariable') {
|
||||
url.searchParams.set('envVariableMissing', '1');
|
||||
url.searchParams.set('undefinedEnvironmentVariables', e?.extraInfo?.undefinedEnvironmentVariables);
|
||||
}
|
||||
|
||||
window.main.completeExecutionStep({ requestId });
|
||||
|
||||
@@ -54,7 +54,6 @@ import { useRunnerRequestList } from '~/ui/hooks/use-runner-request-list';
|
||||
import { moveAfter, moveBefore } from '~/utils';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
|
||||
import { type RequestContext } from '../../../insomnia-scripting-environment/src/objects';
|
||||
import type { Route } from './+types/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.runner';
|
||||
|
||||
const inputStyle =
|
||||
@@ -64,7 +63,7 @@ const iterationInputStyle =
|
||||
|
||||
// TODO: improve the performance for a lot of logs
|
||||
async function aggregateAllTimelines(errorMsg: string | null, testResult: RunnerTestResult) {
|
||||
let timelines = new Array<ResponseTimelineEntry>();
|
||||
let timelines: ResponseTimelineEntry[] = [];
|
||||
const responsesInfo = testResult.responsesInfo;
|
||||
|
||||
for (const respInfo of responsesInfo) {
|
||||
@@ -135,7 +134,7 @@ const defaultAdvancedConfig = {
|
||||
keepLog: true,
|
||||
};
|
||||
|
||||
export const Runner: FC<{}> = () => {
|
||||
export const Runner: FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const [errorMsg, setErrorMsg] = useState<null | string>(null);
|
||||
|
||||
@@ -952,7 +951,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
while (j < requests.length) {
|
||||
// TODO: we might find a better way to do runner cancellation
|
||||
if (getExecution(runnerId) === undefined) {
|
||||
throw 'Runner has been stopped';
|
||||
throw new Error('Runner has been stopped');
|
||||
}
|
||||
|
||||
const targetRequest = requests[j];
|
||||
@@ -1001,7 +1000,7 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
const mutatedContext = (await sendActionImplementation({
|
||||
const execution = await sendActionImplementation({
|
||||
requestId: targetRequest.id,
|
||||
iteration: i + 1,
|
||||
iterationCount,
|
||||
@@ -1011,9 +1010,9 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
testResultCollector: resultCollector,
|
||||
runtime,
|
||||
transientVariables: testCtx.transientVariables,
|
||||
})) as RequestContext | null;
|
||||
if (mutatedContext?.execution?.nextRequestIdOrName) {
|
||||
nextRequestIdOrName = mutatedContext.execution.nextRequestIdOrName || '';
|
||||
});
|
||||
if (execution?.nextRequestIdOrName) {
|
||||
nextRequestIdOrName = execution.nextRequestIdOrName || '';
|
||||
}
|
||||
|
||||
const requestResults: RunnerResultPerRequest = {
|
||||
@@ -1086,10 +1085,8 @@ export async function clientAction({ request, params }: Route.ClientActionArgs)
|
||||
window.main.completeExecutionStep({ requestId: runnerId });
|
||||
} catch (e) {
|
||||
// the error could be from third party
|
||||
const errMsg = e.error || e;
|
||||
updateExecution(runnerId, {
|
||||
error: errMsg,
|
||||
});
|
||||
const errMsg = e.message || e.error || e;
|
||||
updateExecution(runnerId, { error: errMsg });
|
||||
return null;
|
||||
} finally {
|
||||
cancelExecution(runnerId);
|
||||
|
||||
@@ -486,8 +486,8 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(
|
||||
onCancel={() => setShowEnvVariableMissingModal(false)}
|
||||
>
|
||||
<div>
|
||||
These environment variables have been defined, but have not been valued with in the currently active
|
||||
environment:
|
||||
These environment variables have been defined, but have not been assigned a value within the currently
|
||||
active environment:
|
||||
<div className="flex max-h-80 flex-wrap gap-2 overflow-y-auto">
|
||||
{undefinedEnvironmentVariableList?.map(item => {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user