feat(hidden-window): enable baseEnvironment in the pre-request scripting (#7102)

* feat(hidden-window): enable baseEnvironment in the pre-request scripting

* fix: input empty selected environment data to avoid incorrect environment manipulation and overriding

* test: add a test for folder environments overriding

* fix: smoke tests failed because of env overriding
This commit is contained in:
Hexxa 2024-02-23 20:00:20 +08:00 committed by GitHub
parent 62a73fa3ec
commit d7a0bc1e58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 126 additions and 11 deletions

View File

@ -177,7 +177,7 @@ resources:
settingFollowRedirects: global
_type: request
- _id: req_89dade2ee9ee42fbb22d588783a9df3c
parentId: wrk_5b5ab67830944ffcbec47528366ef403
parentId: fld_01de564274824ecaad272330339ea6b2
modified: 1636707449231
created: 1636141014552
url: http://127.0.0.1:4010/echo
@ -202,7 +202,8 @@ resources:
modified: 1636140994432
created: 1636140994432
name: Base Environment
data: {}
data:
customValue: "fromEnvManager"
dataPropertyOrder: null
color: null
isPrivate: false
@ -264,8 +265,19 @@ resources:
settingRebuildPath: true
settingFollowRedirects: global
_type: request
- _id: req_0769dca686df49358082b2183dfa073d
- _id: fld_01de564274824ecaad272330339ea6b2
parentId: wrk_5b5ab67830944ffcbec47528366ef403
modified: 1668533312225
created: 1668533312225
name: FolderWithEnv
description: ""
environment:
customValue: "fromFolder"
environmentPropertyOrder: null
metaSortKey: -1668533312225
_type: request_group
- _id: req_0769dca686df49358082b2183dfa073d
parentId: fld_01de564274824ecaad272330339ea6b2
modified: 1643892278711
created: 1643892270079
url: http://127.0.0.1:4010/echo

View File

@ -45,6 +45,59 @@ test.describe('pre-request UI tests', async () => {
predefined: 'updatedByScript',
},
},
{
name: 'environments / populate environments',
preReqScript: `
insomnia.baseEnvironment.set('fromBaseEnv', 'baseEnv');
`,
body: `{
"fromBaseEnv": "{{ _.fromBaseEnv }}"
}`,
expectedBody: {
fromBaseEnv: 'baseEnv',
},
},
{
name: 'environments / override base environments',
preReqScript: `
insomnia.baseEnvironment.set('scriptValue', 'fromBase');
insomnia.environment.set('scriptValue', 'fromEnv');
`,
body: `{
"scriptValue": "{{ _.scriptValue }}"
}`,
expectedBody: {
scriptValue: 'fromEnv',
},
},
{
name: 'environments / override predefined base environment in script',
preReqScript: `
// "preDefinedValue" is already defined in the base environment modal.
// but it is rewritten here
insomnia.baseEnvironment.set('preDefinedValue', 'fromScript');
`,
body: `{
"preDefinedValue": "{{ _.preDefinedValue }}"
}`,
expectedBody: {
preDefinedValue: 'fromScript',
},
},
{
name: 'environments/ envrionment from script should be overidden by folder environment',
preReqScript: `
// "customValue" is already defined in the folder environment.
// folder version will override the following wone
insomnia.baseEnvironment.set('customValue', 'fromScript');
`,
body: `{
"customValue": "{{ _.customValue }}"
}`,
expectedBody: {
customValue: 'fromFolder',
},
},
];
for (let i = 0; i < testCases.length; i++) {

View File

@ -298,6 +298,7 @@ interface RenderRequest<T extends Request | GrpcRequest | WebSocketRequest> {
interface BaseRenderContextOptions {
environment?: string | Environment;
baseEnvironment?: Environment;
purpose?: RenderPurpose;
extraInfo?: ExtraRenderInfo;
}
@ -309,6 +310,7 @@ export async function getRenderContext(
{
request,
environment,
baseEnvironment,
ancestors: _ancestors,
purpose,
extraInfo,
@ -322,7 +324,7 @@ export async function getRenderContext(
throw new Error('Failed to render. Could not find workspace');
}
const rootEnvironment = await models.environment.getOrCreateForParentId(
const rootEnvironment = baseEnvironment || await models.environment.getOrCreateForParentId(
workspace ? workspace._id : 'n/a',
);
const subEnvironmentId = environment ?
@ -466,6 +468,7 @@ export async function getRenderedRequestAndContext(
{
request,
environment,
baseEnvironment,
extraInfo,
purpose,
}: RenderRequestOptions,
@ -474,7 +477,7 @@ export async function getRenderedRequestAndContext(
const workspace = ancestors.find(isWorkspace);
const parentId = workspace ? workspace._id : 'n/a';
const cookieJar = await models.cookieJar.getOrCreateForParentId(parentId);
const renderContext = await getRenderContext({ request, environment, ancestors, purpose, extraInfo });
const renderContext = await getRenderContext({ request, environment, ancestors, purpose, extraInfo, baseEnvironment });
// HACK: Switch '#}' to '# }' to prevent Nunjucks from barfing
// https://github.com/kong/insomnia/issues/895

View File

@ -53,5 +53,6 @@ const runPreRequestScript = async (
return {
...context,
environment: mutatedContextObject.environment,
baseEnvironment: mutatedContextObject.baseEnvironment,
};
};

View File

@ -71,6 +71,10 @@ export async function createHiddenBrowserWindow(): Promise<ElectronBrowserWindow
const hiddenBrowserWindow = new BrowserWindow({
show: false,
title: 'HiddenBrowserWindow',
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
minHeight: MINIMUM_HEIGHT,
minWidth: MINIMUM_WIDTH,
webPreferences: {
contextIsolation: true,
nodeIntegration: true,

View File

@ -1,6 +1,7 @@
import type { CurlRequestOptions, CurlRequestOutput } from '../main/network/libcurl-promise';
import { Request } from '../models/request';
import { RequestContext } from '../sdk/objects/insomnia';
const cancelRequestFunctionMap = new Map<string, () => void>();
export async function cancelRequestById(requestId: string) {
const cancel = cancelRequestFunctionMap.get(requestId);
@ -30,6 +31,7 @@ export const cancellableRunPreRequestScript = async (options: { script: string;
return result as {
request: Request;
environment: object;
baseEnvironment: object;
};
} catch (err) {
if (err.name === 'AbortError') {
@ -62,6 +64,7 @@ export const cancellableCurlRequest = async (requestOptions: CurlRequestOptions)
return { statusMessage: 'Error', error: err.message || 'Something went wrong' };
}
};
const cancellablePromise = ({ signal, fn }: { signal: AbortSignal; fn: Promise<any> }) => {
if (signal?.aborted) {
return Promise.reject(new DOMException('Aborted', 'AbortError'));

View File

@ -73,20 +73,31 @@ export const fetchRequestData = async (requestId: string) => {
return { request, environment, settings, clientCertificates, caCert, activeEnvironmentId, timelinePath, responseId };
};
export const tryToExecutePreRequestScript = async (request: Request, environment: Environment, timelinePath: string, responseId: string) => {
export const tryToExecutePreRequestScript = async (
request: Request,
environment: Environment,
timelinePath: string,
responseId: string,
baseEnvironment: Environment,
) => {
if (!request.preRequestScript) {
return {
request,
environment: undefined,
baseEnvironment: undefined,
};
}
try {
const output = await cancellableRunPreRequestScript({
script: request.preRequestScript,
context: {
request,
timelinePath,
environment: environment?.data || {},
// it inputs empty environment data when active environment is the base environment
// this is more deterministic and avoids that script accidently manipulates baseEnvironment instead of environment
environment: environment._id === baseEnvironment._id ? {} : (environment?.data || {}),
baseEnvironment: baseEnvironment?.data || {},
},
});
console.log('[network] Pre-request script succeeded', output);
@ -98,10 +109,18 @@ export const tryToExecutePreRequestScript = async (request: Request, environment
);
environment.data = output.environment;
environment.dataPropertyOrder = envPropertyOrder.map;
const baseEnvPropertyOrder = orderedJSON.parse(
JSON.stringify(output.baseEnvironment),
JSON_ORDER_PREFIX,
JSON_ORDER_SEPARATOR,
);
baseEnvironment.data = output.baseEnvironment;
baseEnvironment.dataPropertyOrder = baseEnvPropertyOrder.map;
return {
request: output.request,
environment: environment,
environment,
baseEnvironment,
};
} catch (err) {
await fs.promises.appendFile(timelinePath, JSON.stringify({ value: err.message, name: 'Text', timestamp: Date.now() }) + '\n');
@ -126,12 +145,14 @@ export const tryToInterpolateRequest = async (
request: Request,
environment: string | Environment,
purpose?: RenderPurpose,
extraInfo?: ExtraRenderInfo
extraInfo?: ExtraRenderInfo,
baseEnvironment?: Environment,
) => {
try {
return await getRenderedRequestAndContext({
request: request,
environment,
baseEnvironment,
purpose,
extraInfo,
});

View File

@ -5,22 +5,29 @@ export interface RequestContext {
request: Request;
timelinePath: string;
environment?: object;
baseEnvironment?: object;
}
export class InsomniaObject {
public environment: Environment;
public collectionVariables: Environment;
public baseEnvironment: Environment;
constructor(
rawObj: {
environment: Environment;
baseEnvironment: Environment;
},
) {
this.environment = rawObj.environment;
this.baseEnvironment = rawObj.baseEnvironment;
this.collectionVariables = this.baseEnvironment; // collectionVariables is mapped to baseEnvironment
}
toObject = () => {
return {
environment: this.environment.toObject(),
baseEnvironment: this.baseEnvironment.toObject(),
};
};
}
@ -29,10 +36,12 @@ export function initInsomniaObject(
rawObj: RequestContext,
) {
const environment = new Environment(rawObj.environment);
const baseEnvironment = new Environment(rawObj.baseEnvironment);
return new InsomniaObject(
{
environment,
baseEnvironment,
},
);
};

View File

@ -369,15 +369,24 @@ export const sendAction: ActionFunction = async ({ request, params }) => {
timelinePath,
responseId,
} = await fetchRequestData(requestId);
const baseEnvironment = await models.environment.getOrCreateForParentId(workspaceId);
try {
const { shouldPromptForPathAfterResponse } = await request.json() as SendActionParams;
const mutatedContext = await tryToExecutePreRequestScript(req, environment, timelinePath, responseId);
const mutatedContext = await tryToExecutePreRequestScript(req, environment, timelinePath, responseId, baseEnvironment);
if (!mutatedContext?.request) {
// exiy early if there was a problem with the pre-request script
// TODO: improve error message?
return null;
}
const renderedResult = await tryToInterpolateRequest(mutatedContext.request, mutatedContext.environment || environment._id, RENDER_PURPOSE_SEND);
const renderedResult = await tryToInterpolateRequest(
mutatedContext.request,
mutatedContext.environment || environment._id,
RENDER_PURPOSE_SEND,
undefined,
mutatedContext.baseEnvironment,
);
const renderedRequest = await tryToTransformRequestWithPlugins(renderedResult);
// TODO: remove this temporary hack to support GraphQL variables in the request body properly