diff --git a/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml b/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml index 30e1179cf6..d6beb59440 100644 --- a/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml +++ b/packages/insomnia-smoke-test/fixtures/pre-request-collection.yaml @@ -603,3 +603,150 @@ resources: mimeType: "application/json" text: '{}' _type: request + - _id: req_89dade2ee9ee42fbb22d588783a9df07 + parentId: fld_01de564274824ecaad272330339ea6b2 + modified: 1636707449231 + created: 1636141014552 + url: http://127.0.0.1:4010/echo + name: eval() works in script + description: "" + method: POST + parameters: [] + headers: + - name: 'Content-Type' + value: 'application/json' + authentication: {} + metaSortKey: -1636141014553 + isPrivate: false + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + preRequestScript: |- + const evalResult = eval('8+8'); + insomnia.environment.set('evalResult', evalResult); + body: + mimeType: "application/json" + text: |- + { + "evalResult": {{ _.evalResult }} + } + _type: request + - _id: req_89dade2ee9ee42fbb22d588783a9df08 + parentId: fld_01de564274824ecaad272330339ea6b2 + modified: 1636707449231 + created: 1636141014552 + url: http://127.0.0.1:4010/echo + name: require the url module + description: "" + method: POST + parameters: [] + headers: + - name: 'Content-Type' + value: 'application/json' + authentication: {} + metaSortKey: -1636141014553 + isPrivate: false + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + preRequestScript: |- + const { URL } = require('url'); + const url = new URL('https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent'); + insomnia.environment.set('hash', "#hashcontent"); + insomnia.environment.set('host', "insomnia.com:6666"); + insomnia.environment.set('hostname', "insomnia.com"); + insomnia.environment.set('href', "https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent"); + insomnia.environment.set('origin', "https://insomnia.com:6666"); + insomnia.environment.set('password', "pwd"); + insomnia.environment.set('pathname', "/p1"); + insomnia.environment.set('port', "6666"); + insomnia.environment.set('protocol', "https:"); + insomnia.environment.set('search', "?q1=a&q2=b"); + insomnia.environment.set('username', "user"); + insomnia.environment.set('seachParam', url.searchParams.toString()); + body: + mimeType: "application/json" + text: |- + { + "hash": "{{ _.hash }}", + "host": "{{ _.host }}", + "hostname": "{{ _.hostname }}", + "href": "{{ _.href }}", + "origin": "{{ _.origin }}", + "password": "{{ _.password }}", + "pathname": "{{ _.pathname }}", + "port": "{{ _.port }}", + "protocol": "{{ _.protocol }}", + "search": "{{ _.search }}", + "username": "{{ _.username }}", + "seachParam": "{{ _.seachParam }}" + } + _type: request + - _id: req_89dade2ee9ee42fbb22d588783a9df09 + parentId: fld_01de564274824ecaad272330339ea6b2 + modified: 1636707449231 + created: 1636141014552 + url: http://127.0.0.1:4010/echo + name: require node.js modules + description: "" + method: POST + parameters: [] + headers: + - name: 'Content-Type' + value: 'application/json' + authentication: {} + metaSortKey: -1636141014553 + isPrivate: false + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + preRequestScript: |- + const path = require('path'); + const assert = require('assert'); + const buffer = require('buffer'); + const util = require('util'); + const url = require('url'); + const punycode = require('punycode'); + const querystring = require('querystring'); + const stringDecoder = require('string_decoder'); + const stream = require('stream'); + const timers = require('timers'); + const events = require('events'); + // set them + insomnia.environment.set('path', path != null); + insomnia.environment.set('assert', assert != null); + insomnia.environment.set('buffer', buffer != null); + insomnia.environment.set('util', util != null); + insomnia.environment.set('url', url != null); + insomnia.environment.set('punycode', punycode != null); + insomnia.environment.set('querystring', querystring != null); + insomnia.environment.set('stringDecoder', stringDecoder != null); + insomnia.environment.set('stream', stream != null); + insomnia.environment.set('timers', timers != null); + insomnia.environment.set('events', events != null); + body: + mimeType: "application/json" + text: |- + { + "path": {{ _.path }}, + "assert": {{ _.assert }}, + "buffer": {{ _.buffer }}, + "util": {{ _.util }}, + "url": {{ _.url }}, + "punycode": {{ _.punycode }}, + "querystring": {{ _.querystring }}, + "stringDecoder": {{ _.stringDecoder }}, + "stream": {{ _.stream }}, + "timers": {{ _.timers }}, + "events": {{ _.events }} + } + _type: request diff --git a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts index e1e0bbff13..247930fc2d 100644 --- a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-features.test.ts @@ -84,6 +84,48 @@ test.describe('pre-request features tests', async () => { expect(bodyJson.headers['authorization']).toEqual(`Basic ${expectedEncCred}`); }, }, + { + name: 'eval() works in script', + expectedBody: { + evalResult: 16, + }, + }, + { + name: 'require the url module', + customVerify: (bodyJson: any) => { + const reqBodyJsons = JSON.parse(bodyJson.data); + expect(reqBodyJsons).toEqual({ + hash: '#hashcontent', + host: 'insomnia.com:6666', + hostname: 'insomnia.com', + href: 'https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent', + origin: 'https://insomnia.com:6666', + password: 'pwd', + pathname: '/p1', + port: '6666', + protocol: 'https:', + search: '?q1=a&q2=b', + username: 'user', + seachParam: 'q1=a&q2=b', + }); + }, + }, + { + name: 'require node.js modules', + expectedBody: { + path: true, + assert: true, + buffer: true, + util: true, + url: true, + punycode: true, + querystring: true, + stringDecoder: true, + stream: true, + timers: true, + events: true, + }, + }, ]; for (let i = 0; i < testCases.length; i++) { @@ -108,7 +150,6 @@ test.describe('pre-request features tests', async () => { const bodyJson = JSON.parse(rows.join(' ')); if (tc.expectedBody) { - // await page.waitForTimeout(600000); expect(JSON.parse(bodyJson.data)).toEqual(tc.expectedBody); } if (tc.customVerify) { @@ -304,3 +345,91 @@ test.describe('pre-request features tests', async () => { await expect(responsePane).toContainText('fixtures/certificates/fake.pfx'); // original proxy }); }); + +test.describe('unhappy paths', async () => { + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + + test.beforeEach(async ({ app, page }) => { + const text = await loadFixture('pre-request-collection.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text); + + await page.getByRole('button', { name: 'Create in project' }).click(); + await page.getByRole('menuitemradio', { name: 'Import' }).click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + + await page.getByLabel('Pre-request Scripts').click(); + }); + + const testCases = [ + { + name: 'invalid result is returned', + preReqScript: ` + return; + `, + context: { + insomnia: {}, + }, + expectedResult: { + message: 'insomnia object is invalid or script returns earlier than expected.', + }, + }, + { + name: 'custom error is returned', + preReqScript: ` + throw Error('my custom error'); + `, + context: { + insomnia: {}, + }, + expectedResult: { + message: 'my custom error', + }, + }, + { + name: 'syntax error', + preReqScript: ` + insomnia.INVALID_FIELD.set('', '') + `, + context: { + insomnia: {}, + }, + expectedResult: { + message: "Cannot read properties of undefined (reading 'set')", + }, + }, + ]; + + for (let i = 0; i < testCases.length; i++) { + const tc = testCases[i]; + + test(tc.name, async ({ page }) => { + const responsePane = page.getByTestId('response-pane'); + + await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter'); + + // set request body + await page.getByRole('tab', { name: 'Body' }).click(); + await page.getByRole('button', { name: 'Body' }).click(); + await page.getByRole('menuitem', { name: 'JSON' }).click(); + + // enter script + const preRequestScriptTab = page.getByRole('tab', { name: 'Pre-request Script' }); + await preRequestScriptTab.click(); + const preRequestScriptEditor = page.getByTestId('CodeEditor').getByRole('textbox'); + await preRequestScriptEditor.fill(tc.preReqScript); + + // TODO: wait for body and pre-request script are persisted to the disk + // should improve this part, we should avoid sync this state through db as it introduces race condition + await page.waitForTimeout(500); + + // send + await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); + + // verify + await page.waitForSelector('[data-testid="response-status-tag"]:visible'); + await expect(responsePane).toContainText(tc.expectedResult.message); + }); + } +}); diff --git a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-window.test.ts b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-window.test.ts index 6710e54480..5a16b160a0 100644 --- a/packages/insomnia-smoke-test/tests/smoke/pre-request-script-window.test.ts +++ b/packages/insomnia-smoke-test/tests/smoke/pre-request-script-window.test.ts @@ -40,11 +40,6 @@ test('handle hidden browser window getting closed', async ({ app, page }) => { await page.getByRole('button', { name: 'Scan' }).click(); await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); - await page.getByText('Pre-request Scripts').click(); - - await page.getByLabel('Request Collection').getByTestId('Long running task').press('Enter'); - await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click(); - await page.getByTestId('settings-button').click(); await page.getByLabel('Request timeout (ms)').fill('1'); await page.getByRole('button', { name: '' }).click(); diff --git a/packages/insomnia/src/hidden-window-preload.ts b/packages/insomnia/src/hidden-window-preload.ts index ad88bdc711..f01b611485 100644 --- a/packages/insomnia/src/hidden-window-preload.ts +++ b/packages/insomnia/src/hidden-window-preload.ts @@ -27,8 +27,32 @@ const bridge: HiddenBrowserWindowToMainBridgeAPI = { }, requireInterceptor: (moduleName: string) => { - if (['uuid', 'fs'].includes(moduleName)) { + if ( + [ + // node.js modules + 'path', + 'assert', + 'buffer', + 'util', + 'url', + 'punycode', + 'querystring', + 'string_decoder', + 'stream', + 'timers', + 'events', + // follows should be npm modules + // but they are moved to here to avoid introducing additional dependencies + ].includes(moduleName) + ) { return require(moduleName); + } else if ( + [ + 'atob', + 'btoa', + ].includes(moduleName) + ) { + return moduleName === 'atob' ? atob : btoa; } else if (moduleName === 'insomnia-collection' || moduleName === 'postman-collection') { return CollectionModule; } diff --git a/packages/insomnia/src/hidden-window.ts b/packages/insomnia/src/hidden-window.ts index 09582bbba2..c3db98cbee 100644 --- a/packages/insomnia/src/hidden-window.ts +++ b/packages/insomnia/src/hidden-window.ts @@ -1,4 +1,9 @@ -import { initInsomniaObject } from './sdk/objects/insomnia'; +import * as fs from 'node:fs'; + +import * as _ from 'lodash'; + +import { invariant } from '../src/utils/invariant'; +import { initInsomniaObject, InsomniaObject } from './sdk/objects/insomnia'; import { RequestContext } from './sdk/objects/interfaces'; import { mergeClientCertificates, mergeRequests, mergeSettings } from './sdk/objects/request'; @@ -37,18 +42,26 @@ const runPreRequestScript = async ( const consoleInterceptor = { log: (...args: any[]) => log.push( JSON.stringify({ - value: args.map(a => JSON.stringify(a)).join('\n'), + value: args.map(a => JSON.stringify(a, null, 2)).join('\n'), name: 'Text', timestamp: Date.now(), }) + '\n', ), }; + const evalInterceptor = (script: string) => { + invariant(script && typeof script === 'string', 'eval is called with invalid or empty value'); + const result = eval(script); + return result; + }; + const AsyncFunction = (async () => { }).constructor; const executeScript = AsyncFunction( 'insomnia', 'require', 'console', + 'eval', + '_', ` const $ = insomnia, pm = insomnia; ${script}; @@ -58,14 +71,19 @@ const runPreRequestScript = async ( const mutatedInsomniaObject = await executeScript( executionContext, window.bridge.requireInterceptor, - consoleInterceptor + consoleInterceptor, + evalInterceptor, + _, ); + if (mutatedInsomniaObject == null || !(mutatedInsomniaObject instanceof InsomniaObject)) { + throw Error('insomnia object is invalid or script returns earlier than expected.'); + } const mutatedContextObject = mutatedInsomniaObject.toObject(); const updatedRequest = mergeRequests(context.request, mutatedContextObject.request); const updatedSettings = mergeSettings(context.settings, mutatedContextObject.request); const updatedCertificates = mergeClientCertificates(context.clientCertificates, mutatedContextObject.request); - await window.bridge.requireInterceptor('fs').promises.writeFile(context.timelinePath, log.join('\n')); + await fs.promises.writeFile(context.timelinePath, log.join('\n')); console.log('mutatedInsomniaObject', mutatedContextObject); console.log('context', context); diff --git a/packages/insomnia/src/ui/components/editors/pre-request-script-editor.tsx b/packages/insomnia/src/ui/components/editors/pre-request-script-editor.tsx index a6d12ae8f1..6f179a3b69 100644 --- a/packages/insomnia/src/ui/components/editors/pre-request-script-editor.tsx +++ b/packages/insomnia/src/ui/components/editors/pre-request-script-editor.tsx @@ -66,6 +66,7 @@ const lintOptions = { pm: true, require: true, console: true, + _: true, }, asi: true, // Don't require semicolons