neuron-inspector 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/assertions.js +11 -0
- package/dist/collections/assertions.js.map +1 -1
- package/dist/collections/dynamic-vars-core.d.ts +79 -0
- package/dist/collections/dynamic-vars-core.js +214 -0
- package/dist/collections/dynamic-vars-core.js.map +1 -0
- package/dist/collections/dynamic-vars.d.ts +2 -61
- package/dist/collections/dynamic-vars.js +6 -146
- package/dist/collections/dynamic-vars.js.map +1 -1
- package/dist/collections/encoding.d.ts +3 -0
- package/dist/collections/encoding.js +20 -0
- package/dist/collections/encoding.js.map +1 -0
- package/dist/collections/executor.d.ts +128 -5
- package/dist/collections/executor.js +330 -93
- package/dist/collections/executor.js.map +1 -1
- package/dist/collections/index.d.ts +11 -3
- package/dist/collections/index.js +7 -1
- package/dist/collections/index.js.map +1 -1
- package/dist/collections/masking.d.ts +42 -0
- package/dist/collections/masking.js +188 -0
- package/dist/collections/masking.js.map +1 -0
- package/dist/collections/resolver-core.d.ts +74 -0
- package/dist/collections/resolver-core.js +309 -0
- package/dist/collections/resolver-core.js.map +1 -0
- package/dist/collections/resolver.d.ts +9 -62
- package/dist/collections/resolver.js +21 -281
- package/dist/collections/resolver.js.map +1 -1
- package/dist/collections/scenario-run.d.ts +86 -0
- package/dist/collections/scenario-run.js +169 -0
- package/dist/collections/scenario-run.js.map +1 -0
- package/dist/collections/script-api.d.ts +18 -0
- package/dist/collections/script-api.js +208 -0
- package/dist/collections/script-api.js.map +1 -0
- package/dist/collections/scripts.d.ts +1 -8
- package/dist/collections/scripts.js +6 -196
- package/dist/collections/scripts.js.map +1 -1
- package/dist/collections/slug.d.ts +2 -0
- package/dist/collections/slug.js +8 -0
- package/dist/collections/slug.js.map +1 -0
- package/dist/collections/store.d.ts +5 -2
- package/dist/collections/store.js +6 -6
- package/dist/collections/store.js.map +1 -1
- package/dist/collections/tools.d.ts +8 -1
- package/dist/collections/tools.js +313 -89
- package/dist/collections/tools.js.map +1 -1
- package/dist/collections/types.d.ts +42 -3
- package/dist/correlation.js +10 -0
- package/dist/correlation.js.map +1 -1
- package/dist/extension-slot.d.ts +45 -0
- package/dist/extension-slot.js +81 -0
- package/dist/extension-slot.js.map +1 -0
- package/dist/monitor.d.ts +5 -0
- package/dist/monitor.js +7 -2
- package/dist/monitor.js.map +1 -1
- package/dist/server.js +113 -25
- package/dist/server.js.map +1 -1
- package/dist/tools.js +1 -1
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
- package/dist/collections/__tests__/assertions.test.d.ts +0 -1
- package/dist/collections/__tests__/assertions.test.js +0 -203
- package/dist/collections/__tests__/assertions.test.js.map +0 -1
- package/dist/collections/__tests__/dynamic-vars.test.d.ts +0 -1
- package/dist/collections/__tests__/dynamic-vars.test.js +0 -169
- package/dist/collections/__tests__/dynamic-vars.test.js.map +0 -1
- package/dist/collections/__tests__/execution.test.d.ts +0 -1
- package/dist/collections/__tests__/execution.test.js +0 -271
- package/dist/collections/__tests__/execution.test.js.map +0 -1
- package/dist/collections/__tests__/integration.test.d.ts +0 -1
- package/dist/collections/__tests__/integration.test.js +0 -478
- package/dist/collections/__tests__/integration.test.js.map +0 -1
- package/dist/collections/__tests__/merge.test.d.ts +0 -1
- package/dist/collections/__tests__/merge.test.js +0 -411
- package/dist/collections/__tests__/merge.test.js.map +0 -1
- package/dist/collections/__tests__/resolver.test.d.ts +0 -1
- package/dist/collections/__tests__/resolver.test.js +0 -282
- package/dist/collections/__tests__/resolver.test.js.map +0 -1
- package/dist/collections/__tests__/scenario.test.d.ts +0 -1
- package/dist/collections/__tests__/scenario.test.js +0 -367
- package/dist/collections/__tests__/scenario.test.js.map +0 -1
- package/dist/collections/__tests__/scripts.test.d.ts +0 -1
- package/dist/collections/__tests__/scripts.test.js +0 -245
- package/dist/collections/__tests__/scripts.test.js.map +0 -1
- package/dist/collections/__tests__/store.test.d.ts +0 -1
- package/dist/collections/__tests__/store.test.js +0 -313
- package/dist/collections/__tests__/store.test.js.map +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Running one scenario, independent of where it runs. The bridge's
|
|
2
|
+
// neuron_api_run_scenario and the extension's browser engine both call
|
|
3
|
+
// runScenario, so environment choice, required inputs and secret masking are
|
|
4
|
+
// decided in exactly one place.
|
|
5
|
+
import { executeScenario } from "./executor.js";
|
|
6
|
+
import { mergeInputDeclarations, prepareInputs, describeInputs } from "./masking.js";
|
|
7
|
+
import { slugify } from "./slug.js";
|
|
8
|
+
export function pickEnvironment(collection, id, requested) {
|
|
9
|
+
const envName = requested ?? collection.active_environment;
|
|
10
|
+
if (!envName) {
|
|
11
|
+
return { error: "No environment specified and no active_environment set on the collection" };
|
|
12
|
+
}
|
|
13
|
+
const envs = collection.environments ?? {};
|
|
14
|
+
const key = envs[envName] ? envName : slugify(envName);
|
|
15
|
+
const environment = envs[key];
|
|
16
|
+
if (!environment) {
|
|
17
|
+
return { error: `Environment '${envName}' not found in collection '${id}'` };
|
|
18
|
+
}
|
|
19
|
+
return { key, environment };
|
|
20
|
+
}
|
|
21
|
+
/** Declared inputs for a run (environment's merged with the scenario's). Never values. */
|
|
22
|
+
export function runInputsFor(collection, id, scenarioKey, environment) {
|
|
23
|
+
const picked = pickEnvironment(collection, id, environment);
|
|
24
|
+
if ("error" in picked)
|
|
25
|
+
return picked;
|
|
26
|
+
let scenarioInputs;
|
|
27
|
+
if (scenarioKey !== undefined) {
|
|
28
|
+
const scenario = collection.scenarios?.[scenarioKey];
|
|
29
|
+
if (!scenario)
|
|
30
|
+
return { error: `Scenario '${scenarioKey}' not found in collection '${id}'` };
|
|
31
|
+
scenarioInputs = scenario.inputs;
|
|
32
|
+
}
|
|
33
|
+
return { environment: picked.key, inputs: describeInputs(mergeInputDeclarations(picked.environment.inputs, scenarioInputs)) };
|
|
34
|
+
}
|
|
35
|
+
export async function runScenario(opts) {
|
|
36
|
+
const { collection, collectionId: id } = opts;
|
|
37
|
+
const picked = pickEnvironment(collection, id, opts.environment);
|
|
38
|
+
if ("error" in picked)
|
|
39
|
+
return picked;
|
|
40
|
+
const { key: envKey, environment } = picked;
|
|
41
|
+
const scenario = collection.scenarios?.[opts.scenario];
|
|
42
|
+
if (!scenario) {
|
|
43
|
+
return { error: `Scenario '${opts.scenario}' not found in collection '${id}'` };
|
|
44
|
+
}
|
|
45
|
+
const declared = mergeInputDeclarations(environment.inputs, scenario.inputs);
|
|
46
|
+
const prepared = prepareInputs(declared, opts.inputs);
|
|
47
|
+
if (prepared.missing.length) {
|
|
48
|
+
return { error: `Missing required input(s): ${prepared.missing.join(", ")}`, inputs: describeInputs(declared) };
|
|
49
|
+
}
|
|
50
|
+
opts.onPreparedInputs?.(prepared.values, prepared.secrets);
|
|
51
|
+
const steps = scenario.steps ?? [];
|
|
52
|
+
if (opts.startStep !== undefined && (opts.startStep < 0 || opts.startStep >= steps.length)) {
|
|
53
|
+
return { error: `Step ${opts.startStep + 1} doesn't exist in '${opts.scenario}' (it has ${steps.length})` };
|
|
54
|
+
}
|
|
55
|
+
return executeScenario({
|
|
56
|
+
scenarioKey: opts.scenario,
|
|
57
|
+
steps,
|
|
58
|
+
collectionId: id,
|
|
59
|
+
collection,
|
|
60
|
+
environment,
|
|
61
|
+
environmentName: envKey,
|
|
62
|
+
httpExecutor: opts.httpExecutor,
|
|
63
|
+
pipeline: opts.pipeline,
|
|
64
|
+
assertionEvaluator: opts.assertionEvaluator,
|
|
65
|
+
scriptRunner: opts.scriptRunner,
|
|
66
|
+
inputs: prepared.values,
|
|
67
|
+
secrets: prepared.secrets,
|
|
68
|
+
isCancelled: opts.isCancelled,
|
|
69
|
+
onEvent: opts.onEvent,
|
|
70
|
+
sleep: opts.sleep,
|
|
71
|
+
startStep: opts.startStep,
|
|
72
|
+
endStep: opts.endStep,
|
|
73
|
+
initialExtracted: opts.initialExtracted,
|
|
74
|
+
maskValues: opts.maskValues,
|
|
75
|
+
onStepContext: opts.onStepContext,
|
|
76
|
+
onMaskValue: opts.onMaskValue,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
export class RerunContexts {
|
|
80
|
+
ttlMs;
|
|
81
|
+
max;
|
|
82
|
+
now;
|
|
83
|
+
map = new Map();
|
|
84
|
+
constructor(ttlMs = 30 * 60_000, max = 20, now = Date.now) {
|
|
85
|
+
this.ttlMs = ttlMs;
|
|
86
|
+
this.max = max;
|
|
87
|
+
this.now = now;
|
|
88
|
+
}
|
|
89
|
+
prune() {
|
|
90
|
+
const cutoff = this.now() - this.ttlMs;
|
|
91
|
+
for (const [id, c] of this.map)
|
|
92
|
+
if (c.touchedAt < cutoff)
|
|
93
|
+
this.map.delete(id);
|
|
94
|
+
while (this.map.size > this.max)
|
|
95
|
+
this.map.delete(this.map.keys().next().value);
|
|
96
|
+
}
|
|
97
|
+
get(runId) {
|
|
98
|
+
this.prune();
|
|
99
|
+
return this.map.get(runId);
|
|
100
|
+
}
|
|
101
|
+
has(runId) {
|
|
102
|
+
return !!this.get(runId);
|
|
103
|
+
}
|
|
104
|
+
/** Hooks for runScenario that fill (or keep filling) the context for `runId`. */
|
|
105
|
+
recorder(runId, base) {
|
|
106
|
+
this.prune();
|
|
107
|
+
let ctx = this.map.get(runId);
|
|
108
|
+
if (!ctx) {
|
|
109
|
+
ctx = { ...base, inputs: {}, secrets: [], maskValues: [], before: {}, touchedAt: this.now() };
|
|
110
|
+
this.map.set(runId, ctx);
|
|
111
|
+
}
|
|
112
|
+
const c = ctx;
|
|
113
|
+
return {
|
|
114
|
+
onPreparedInputs: (values, secrets) => {
|
|
115
|
+
c.inputs = { ...values };
|
|
116
|
+
c.secrets = [...secrets];
|
|
117
|
+
c.touchedAt = this.now();
|
|
118
|
+
},
|
|
119
|
+
onStepContext: (step, extractedBefore) => {
|
|
120
|
+
c.before[step] = extractedBefore;
|
|
121
|
+
c.touchedAt = this.now();
|
|
122
|
+
},
|
|
123
|
+
onMaskValue: (value) => {
|
|
124
|
+
if (!c.maskValues.includes(value))
|
|
125
|
+
c.maskValues.push(value);
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
forget(runId) {
|
|
130
|
+
this.map.delete(runId);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Re-run one step ("step") or a step and everything after it ("from"), using
|
|
135
|
+
* the context recorded for the original run. The collection passed in is the
|
|
136
|
+
* current one, so a step fixed in the editor can be re-run as-is.
|
|
137
|
+
*/
|
|
138
|
+
export async function rerunScenario(opts) {
|
|
139
|
+
const ctx = opts.contexts.get(opts.runId);
|
|
140
|
+
if (!ctx) {
|
|
141
|
+
return { error: "That run can't be re-run any more: its context is kept in memory for 30 minutes and only for runs started in this session. Run the scenario again." };
|
|
142
|
+
}
|
|
143
|
+
const before = ctx.before[opts.step];
|
|
144
|
+
if (!before) {
|
|
145
|
+
return { error: `Step ${opts.step + 1} didn't run in the original run, so there's nothing to re-run from. Re-run an earlier step with "from here".` };
|
|
146
|
+
}
|
|
147
|
+
const rec = opts.contexts.recorder(opts.runId, { scenario: ctx.scenario, environment: ctx.environment });
|
|
148
|
+
return runScenario({
|
|
149
|
+
...opts,
|
|
150
|
+
scenario: ctx.scenario,
|
|
151
|
+
environment: ctx.environment,
|
|
152
|
+
inputs: ctx.inputs,
|
|
153
|
+
startStep: opts.step,
|
|
154
|
+
endStep: opts.mode === "step" ? opts.step : undefined,
|
|
155
|
+
initialExtracted: before,
|
|
156
|
+
maskValues: [...ctx.secrets, ...ctx.maskValues],
|
|
157
|
+
onStepContext: (i, b) => {
|
|
158
|
+
// Re-running only one step leaves the later steps' recorded context alone.
|
|
159
|
+
if (opts.mode === "from" || i === opts.step)
|
|
160
|
+
rec.onStepContext(i, b);
|
|
161
|
+
opts.onStepContext?.(i, b);
|
|
162
|
+
},
|
|
163
|
+
onMaskValue: (v) => {
|
|
164
|
+
rec.onMaskValue(v);
|
|
165
|
+
opts.onMaskValue?.(v);
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=scenario-run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-run.js","sourceRoot":"","sources":["../../src/collections/scenario-run.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,uEAAuE;AACvE,6EAA6E;AAC7E,gCAAgC;AAGhC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,eAAe,CAC7B,UAAsB,EACtB,EAAU,EACV,SAAkB;IAElB,MAAM,OAAO,GAAI,SAAoB,IAAI,UAAU,CAAC,kBAAkB,CAAC;IACvE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,KAAK,EAAE,0EAA0E,EAAE,CAAC;IAC/F,CAAC;IACD,MAAM,IAAI,GAAG,UAAU,CAAC,YAAY,IAAI,EAAE,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,EAAE,KAAK,EAAE,gBAAgB,OAAO,8BAA8B,EAAE,GAAG,EAAE,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC;AAC9B,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAC1B,UAAsB,EACtB,EAAU,EACV,WAA+B,EAC/B,WAAoB;IAEpB,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,EAAE,EAAE,WAAW,CAAC,CAAC;IAC5D,IAAI,OAAO,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IACrC,IAAI,cAAsC,CAAC;IAC3C,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC,WAAW,CAAC,CAAC;QACrD,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,KAAK,EAAE,aAAa,WAAW,8BAA8B,EAAE,GAAG,EAAE,CAAC;QAC7F,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IACnC,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,CAAC,sBAAsB,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;AAChI,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAuBjC;IACC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;IAC9C,MAAM,MAAM,GAAG,eAAe,CAAC,UAAU,EAAE,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACjE,IAAI,OAAO,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IACrC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAE5C,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,EAAE,KAAK,EAAE,aAAa,IAAI,CAAC,QAAQ,8BAA8B,EAAE,GAAG,EAAE,CAAC;IAClF,CAAC;IAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,8BAA8B,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;IAClH,CAAC;IACD,IAAI,CAAC,gBAAgB,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;IACnC,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3F,OAAO,EAAE,KAAK,EAAE,QAAQ,IAAI,CAAC,SAAS,GAAG,CAAC,sBAAsB,IAAI,CAAC,QAAQ,aAAa,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;IAC9G,CAAC;IAED,OAAO,eAAe,CAAC;QACrB,WAAW,EAAE,IAAI,CAAC,QAAQ;QAC1B,KAAK;QACL,YAAY,EAAE,EAAE;QAChB,UAAU;QACV,WAAW;QACX,eAAe,EAAE,MAAM;QACvB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;QAC3C,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;QACvC,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,WAAW,EAAE,IAAI,CAAC,WAAW;KAC9B,CAAC,CAAC;AACL,CAAC;AAqBD,MAAM,OAAO,aAAa;IAEJ;IAA6B;IAAkB;IAD3D,GAAG,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC9C,YAAoB,QAAQ,EAAE,GAAG,MAAM,EAAU,MAAM,EAAE,EAAU,MAAoB,IAAI,CAAC,GAAG;QAA3E,UAAK,GAAL,KAAK,CAAc;QAAU,QAAG,GAAH,GAAG,CAAK;QAAU,QAAG,GAAH,GAAG,CAAyB;IAAG,CAAC;IAE3F,KAAK;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC;QACvC,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,CAAC,SAAS,GAAG,MAAM;gBAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAC,CAAC;IAC3F,CAAC;IAED,GAAG,CAAC,KAAa;QACf,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,KAAa;QACf,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,iFAAiF;IACjF,QAAQ,CAAC,KAAa,EAAE,IAAgD;QACtE,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC9F,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3B,CAAC;QACD,MAAM,CAAC,GAAG,GAAG,CAAC;QACd,OAAO;YACL,gBAAgB,EAAE,CAAC,MAA8B,EAAE,OAAiB,EAAE,EAAE;gBACtE,CAAC,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;gBACzB,CAAC,CAAC,OAAO,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;gBACzB,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,CAAC;YACD,aAAa,EAAE,CAAC,IAAY,EAAE,eAAuC,EAAE,EAAE;gBACvE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC;gBACjC,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,CAAC;YACD,WAAW,EAAE,CAAC,KAAa,EAAE,EAAE;gBAC7B,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;oBAAE,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC9D,CAAC;SACF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,KAAa;QAClB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAKnC;IACC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,KAAK,EAAE,oJAAoJ,EAAE,CAAC;IACzK,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,KAAK,EAAE,QAAQ,IAAI,CAAC,IAAI,GAAG,CAAC,8GAA8G,EAAE,CAAC;IACxJ,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IACzG,OAAO,WAAW,CAAC;QACjB,GAAG,IAAI;QACP,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,SAAS,EAAE,IAAI,CAAC,IAAI;QACpB,OAAO,EAAE,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACrD,gBAAgB,EAAE,MAAM;QACxB,UAAU,EAAE,CAAC,GAAG,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,UAAU,CAAC;QAC/C,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACtB,2EAA2E;YAC3E,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;gBAAE,GAAG,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACrE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE;YACjB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Script, ScriptContext, ScriptResult, ScriptRunner, AssertionTarget } from "./types.js";
|
|
2
|
+
export interface ScriptScope {
|
|
3
|
+
/** Names a script can use: nr, console, and the safe builtins. */
|
|
4
|
+
globals: Record<string, unknown>;
|
|
5
|
+
/** Record an error thrown by the script. */
|
|
6
|
+
fail(message: string): void;
|
|
7
|
+
/** Variables, mutations, tests and logs after the script ran. */
|
|
8
|
+
finish(): ScriptResult;
|
|
9
|
+
}
|
|
10
|
+
export declare function createScriptScope(context: ScriptContext, phase: "pre" | "post", response?: AssertionTarget): ScriptScope;
|
|
11
|
+
/**
|
|
12
|
+
* Declarative script runner.
|
|
13
|
+
* Handles pre_request.set and post_response.extract/set without executing arbitrary code.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DeclarativeScriptRunner implements ScriptRunner {
|
|
16
|
+
runPreRequest(script: Script, context: ScriptContext): ScriptResult;
|
|
17
|
+
runPostResponse(script: Script, response: AssertionTarget, context: ScriptContext): ScriptResult;
|
|
18
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// The `nr` script API, independent of how the code is executed. The bridge
|
|
2
|
+
// runs scripts in node:vm; the extension's browser engine runs them in a
|
|
3
|
+
// sandboxed extension page. Both build their scope here so scripts behave
|
|
4
|
+
// identically.
|
|
5
|
+
import { toBase64, fromBase64 } from "./encoding.js";
|
|
6
|
+
import { dotGet } from "./assertions.js";
|
|
7
|
+
export function createScriptScope(context, phase, response) {
|
|
8
|
+
const variables = { ...context.variables };
|
|
9
|
+
const mutations = {};
|
|
10
|
+
const tests = [];
|
|
11
|
+
const logs = [];
|
|
12
|
+
// Build mutable request state from context
|
|
13
|
+
const reqState = {
|
|
14
|
+
method: context.request?.method ?? "GET",
|
|
15
|
+
url: context.request?.path ?? "/",
|
|
16
|
+
headers: { ...(context.request?.headers ?? {}) },
|
|
17
|
+
body: context.request?.body,
|
|
18
|
+
cookies: { ...(context.request?.cookies ?? {}) },
|
|
19
|
+
};
|
|
20
|
+
// ── nr.variables ──
|
|
21
|
+
const nrVariables = {
|
|
22
|
+
get(key) { return variables[key]; },
|
|
23
|
+
set(key, value) { variables[key] = String(value); },
|
|
24
|
+
};
|
|
25
|
+
// ── nr.request ──
|
|
26
|
+
const nrRequestHeaders = {
|
|
27
|
+
get(key) { return reqState.headers[key] ?? reqState.headers[key.toLowerCase()]; },
|
|
28
|
+
set(key, value) {
|
|
29
|
+
reqState.headers[key] = value;
|
|
30
|
+
if (!mutations.headers)
|
|
31
|
+
mutations.headers = {};
|
|
32
|
+
mutations.headers[key] = value;
|
|
33
|
+
},
|
|
34
|
+
remove(key) {
|
|
35
|
+
delete reqState.headers[key];
|
|
36
|
+
if (!mutations.headers)
|
|
37
|
+
mutations.headers = {};
|
|
38
|
+
mutations.headers[key] = "";
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const nrRequest = {
|
|
42
|
+
get method() { return reqState.method; },
|
|
43
|
+
set method(v) { reqState.method = v; mutations.method = v; },
|
|
44
|
+
get url() { return reqState.url; },
|
|
45
|
+
set url(v) { reqState.url = v; mutations.path = v; },
|
|
46
|
+
headers: nrRequestHeaders,
|
|
47
|
+
get body() { return reqState.body; },
|
|
48
|
+
set body(v) { reqState.body = v; mutations.body = v; },
|
|
49
|
+
};
|
|
50
|
+
// ── nr.cookies ──
|
|
51
|
+
const nrCookies = {
|
|
52
|
+
set(name, value) {
|
|
53
|
+
reqState.cookies[name] = value;
|
|
54
|
+
if (!mutations.cookies)
|
|
55
|
+
mutations.cookies = {};
|
|
56
|
+
mutations.cookies[name] = value;
|
|
57
|
+
},
|
|
58
|
+
get(name) { return reqState.cookies[name]; },
|
|
59
|
+
};
|
|
60
|
+
// ── nr.environment (read-only) ──
|
|
61
|
+
const nrEnvironment = {
|
|
62
|
+
get(key) { return context.environment?.variables?.[key]; },
|
|
63
|
+
get name() { return context.environment?.name ?? ""; },
|
|
64
|
+
get base_url() { return context.environment?.base_url ?? ""; },
|
|
65
|
+
};
|
|
66
|
+
// ── nr.response (post-response only) ──
|
|
67
|
+
let nrResponse;
|
|
68
|
+
if (phase === "post" && response) {
|
|
69
|
+
const rawBody = response.body;
|
|
70
|
+
const textBody = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
71
|
+
nrResponse = {
|
|
72
|
+
status: response.status,
|
|
73
|
+
headers: {
|
|
74
|
+
get(key) {
|
|
75
|
+
return response.headers[key] ?? response.headers[key.toLowerCase()];
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
body: rawBody,
|
|
79
|
+
json() { return rawBody; },
|
|
80
|
+
text() { return textBody; },
|
|
81
|
+
time: response.timing.total_ms,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ── nr.test ──
|
|
85
|
+
function nrTest(name, fn) {
|
|
86
|
+
try {
|
|
87
|
+
fn();
|
|
88
|
+
tests.push({ name, passed: true });
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
tests.push({
|
|
92
|
+
name,
|
|
93
|
+
passed: false,
|
|
94
|
+
error: err instanceof Error ? err.message : String(err),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── nr.log ──
|
|
99
|
+
function nrLog(...args) {
|
|
100
|
+
logs.push(args.map(String).join(" "));
|
|
101
|
+
}
|
|
102
|
+
// Assemble the nr object
|
|
103
|
+
const nr = {
|
|
104
|
+
variables: nrVariables,
|
|
105
|
+
request: nrRequest,
|
|
106
|
+
cookies: nrCookies,
|
|
107
|
+
environment: nrEnvironment,
|
|
108
|
+
test: nrTest,
|
|
109
|
+
log: nrLog,
|
|
110
|
+
};
|
|
111
|
+
if (nrResponse)
|
|
112
|
+
nr.response = nrResponse;
|
|
113
|
+
const globals = {
|
|
114
|
+
nr,
|
|
115
|
+
console: { log: nrLog, warn: nrLog, error: nrLog },
|
|
116
|
+
JSON,
|
|
117
|
+
parseInt,
|
|
118
|
+
parseFloat,
|
|
119
|
+
Math,
|
|
120
|
+
Date,
|
|
121
|
+
Array,
|
|
122
|
+
Object,
|
|
123
|
+
String,
|
|
124
|
+
Number,
|
|
125
|
+
Boolean,
|
|
126
|
+
RegExp,
|
|
127
|
+
Map,
|
|
128
|
+
Set,
|
|
129
|
+
encodeURIComponent,
|
|
130
|
+
decodeURIComponent,
|
|
131
|
+
encodeURI,
|
|
132
|
+
decodeURI,
|
|
133
|
+
btoa: (s) => toBase64(String(s)),
|
|
134
|
+
atob: (s) => fromBase64(String(s)),
|
|
135
|
+
isNaN,
|
|
136
|
+
isFinite,
|
|
137
|
+
undefined,
|
|
138
|
+
NaN,
|
|
139
|
+
Infinity,
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
globals,
|
|
143
|
+
fail(message) {
|
|
144
|
+
logs.push(`[script error] ${message}`);
|
|
145
|
+
},
|
|
146
|
+
finish() {
|
|
147
|
+
const result = { variables };
|
|
148
|
+
const hasMutations = mutations.headers || mutations.body !== undefined ||
|
|
149
|
+
mutations.cookies || mutations.path || mutations.method;
|
|
150
|
+
if (hasMutations)
|
|
151
|
+
result.mutations = mutations;
|
|
152
|
+
if (tests.length)
|
|
153
|
+
result.tests = tests;
|
|
154
|
+
if (logs.length)
|
|
155
|
+
result.logs = logs;
|
|
156
|
+
return result;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Declarative script runner.
|
|
162
|
+
* Handles pre_request.set and post_response.extract/set without executing arbitrary code.
|
|
163
|
+
*/
|
|
164
|
+
export class DeclarativeScriptRunner {
|
|
165
|
+
runPreRequest(script, context) {
|
|
166
|
+
const variables = { ...context.variables };
|
|
167
|
+
if (script.set) {
|
|
168
|
+
for (const [key, value] of Object.entries(script.set)) {
|
|
169
|
+
variables[key] = value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { variables };
|
|
173
|
+
}
|
|
174
|
+
runPostResponse(script, response, context) {
|
|
175
|
+
const variables = { ...context.variables };
|
|
176
|
+
if (script.extract) {
|
|
177
|
+
for (const [varName, path] of Object.entries(script.extract)) {
|
|
178
|
+
const value = extractFromResponse(response, path);
|
|
179
|
+
if (value !== undefined && value !== null) {
|
|
180
|
+
variables[varName] = String(value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (script.set) {
|
|
185
|
+
for (const [key, value] of Object.entries(script.set)) {
|
|
186
|
+
variables[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { variables };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Extract a value from a response using dot notation with body.* prefix handling. */
|
|
193
|
+
function extractFromResponse(response, path) {
|
|
194
|
+
if (path === "status")
|
|
195
|
+
return response.status;
|
|
196
|
+
if (path === "body")
|
|
197
|
+
return response.body;
|
|
198
|
+
if (path.startsWith("body."))
|
|
199
|
+
return dotGet(response.body, path.slice(5));
|
|
200
|
+
if (path.startsWith("header."))
|
|
201
|
+
return response.headers[path.slice(7).toLowerCase()];
|
|
202
|
+
if (path === "timing.total")
|
|
203
|
+
return response.timing.total_ms;
|
|
204
|
+
if (path === "timing.ttfb")
|
|
205
|
+
return response.timing.ttfb_ms;
|
|
206
|
+
return dotGet(response.body, path);
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=script-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"script-api.js","sourceRoot":"","sources":["../../src/collections/script-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { ScriptRunner, Script, ScriptContext, ScriptResult, AssertionTarget } from "./types.js";
|
|
2
|
-
|
|
3
|
-
* Declarative script runner.
|
|
4
|
-
* Handles pre_request.set and post_response.extract/set without executing arbitrary code.
|
|
5
|
-
*/
|
|
6
|
-
export declare class DeclarativeScriptRunner implements ScriptRunner {
|
|
7
|
-
runPreRequest(script: Script, context: ScriptContext): ScriptResult;
|
|
8
|
-
runPostResponse(script: Script, response: AssertionTarget, context: ScriptContext): ScriptResult;
|
|
9
|
-
}
|
|
2
|
+
export { DeclarativeScriptRunner } from "./script-api.js";
|
|
10
3
|
/**
|
|
11
4
|
* JavaScript script runner.
|
|
12
5
|
* If a script has `code`, runs it in a sandboxed node:vm context
|
|
@@ -1,53 +1,6 @@
|
|
|
1
1
|
import { createContext, runInContext } from "node:vm";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
* Declarative script runner.
|
|
5
|
-
* Handles pre_request.set and post_response.extract/set without executing arbitrary code.
|
|
6
|
-
*/
|
|
7
|
-
export class DeclarativeScriptRunner {
|
|
8
|
-
runPreRequest(script, context) {
|
|
9
|
-
const variables = { ...context.variables };
|
|
10
|
-
if (script.set) {
|
|
11
|
-
for (const [key, value] of Object.entries(script.set)) {
|
|
12
|
-
variables[key] = value;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return { variables };
|
|
16
|
-
}
|
|
17
|
-
runPostResponse(script, response, context) {
|
|
18
|
-
const variables = { ...context.variables };
|
|
19
|
-
if (script.extract) {
|
|
20
|
-
for (const [varName, path] of Object.entries(script.extract)) {
|
|
21
|
-
const value = extractFromResponse(response, path);
|
|
22
|
-
if (value !== undefined && value !== null) {
|
|
23
|
-
variables[varName] = String(value);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
if (script.set) {
|
|
28
|
-
for (const [key, value] of Object.entries(script.set)) {
|
|
29
|
-
variables[key] = value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return { variables };
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
/** Extract a value from a response using dot notation with body.* prefix handling. */
|
|
36
|
-
function extractFromResponse(response, path) {
|
|
37
|
-
if (path === "status")
|
|
38
|
-
return response.status;
|
|
39
|
-
if (path === "body")
|
|
40
|
-
return response.body;
|
|
41
|
-
if (path.startsWith("body."))
|
|
42
|
-
return dotGet(response.body, path.slice(5));
|
|
43
|
-
if (path.startsWith("header."))
|
|
44
|
-
return response.headers[path.slice(7).toLowerCase()];
|
|
45
|
-
if (path === "timing.total")
|
|
46
|
-
return response.timing.total_ms;
|
|
47
|
-
if (path === "timing.ttfb")
|
|
48
|
-
return response.timing.ttfb_ms;
|
|
49
|
-
return dotGet(response.body, path);
|
|
50
|
-
}
|
|
2
|
+
import { createScriptScope, DeclarativeScriptRunner } from "./script-api.js";
|
|
3
|
+
export { DeclarativeScriptRunner } from "./script-api.js";
|
|
51
4
|
// ── JS script runner ────────────────────────────────────────────
|
|
52
5
|
const declarativeFallback = new DeclarativeScriptRunner();
|
|
53
6
|
/**
|
|
@@ -68,158 +21,15 @@ export class JsScriptRunner {
|
|
|
68
21
|
return declarativeFallback.runPostResponse(script, response, context);
|
|
69
22
|
}
|
|
70
23
|
executeCode(code, context, phase, response) {
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
const tests = [];
|
|
74
|
-
const logs = [];
|
|
75
|
-
// Build mutable request state from context
|
|
76
|
-
const reqState = {
|
|
77
|
-
method: context.request?.method ?? "GET",
|
|
78
|
-
url: context.request?.path ?? "/",
|
|
79
|
-
headers: { ...(context.request?.headers ?? {}) },
|
|
80
|
-
body: context.request?.body,
|
|
81
|
-
cookies: { ...(context.request?.cookies ?? {}) },
|
|
82
|
-
};
|
|
83
|
-
// ── nr.variables ──
|
|
84
|
-
const nrVariables = {
|
|
85
|
-
get(key) { return variables[key]; },
|
|
86
|
-
set(key, value) { variables[key] = String(value); },
|
|
87
|
-
};
|
|
88
|
-
// ── nr.request ──
|
|
89
|
-
const nrRequestHeaders = {
|
|
90
|
-
get(key) { return reqState.headers[key] ?? reqState.headers[key.toLowerCase()]; },
|
|
91
|
-
set(key, value) {
|
|
92
|
-
reqState.headers[key] = value;
|
|
93
|
-
if (!mutations.headers)
|
|
94
|
-
mutations.headers = {};
|
|
95
|
-
mutations.headers[key] = value;
|
|
96
|
-
},
|
|
97
|
-
remove(key) {
|
|
98
|
-
delete reqState.headers[key];
|
|
99
|
-
if (!mutations.headers)
|
|
100
|
-
mutations.headers = {};
|
|
101
|
-
mutations.headers[key] = "";
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
const nrRequest = {
|
|
105
|
-
get method() { return reqState.method; },
|
|
106
|
-
set method(v) { reqState.method = v; mutations.method = v; },
|
|
107
|
-
get url() { return reqState.url; },
|
|
108
|
-
set url(v) { reqState.url = v; mutations.path = v; },
|
|
109
|
-
headers: nrRequestHeaders,
|
|
110
|
-
get body() { return reqState.body; },
|
|
111
|
-
set body(v) { reqState.body = v; mutations.body = v; },
|
|
112
|
-
};
|
|
113
|
-
// ── nr.cookies ──
|
|
114
|
-
const nrCookies = {
|
|
115
|
-
set(name, value) {
|
|
116
|
-
reqState.cookies[name] = value;
|
|
117
|
-
if (!mutations.cookies)
|
|
118
|
-
mutations.cookies = {};
|
|
119
|
-
mutations.cookies[name] = value;
|
|
120
|
-
},
|
|
121
|
-
get(name) { return reqState.cookies[name]; },
|
|
122
|
-
};
|
|
123
|
-
// ── nr.environment (read-only) ──
|
|
124
|
-
const nrEnvironment = {
|
|
125
|
-
get(key) { return context.environment?.variables?.[key]; },
|
|
126
|
-
get name() { return context.environment?.name ?? ""; },
|
|
127
|
-
get base_url() { return context.environment?.base_url ?? ""; },
|
|
128
|
-
};
|
|
129
|
-
// ── nr.response (post-response only) ──
|
|
130
|
-
let nrResponse;
|
|
131
|
-
if (phase === "post" && response) {
|
|
132
|
-
const rawBody = response.body;
|
|
133
|
-
const textBody = typeof rawBody === "string" ? rawBody : JSON.stringify(rawBody);
|
|
134
|
-
nrResponse = {
|
|
135
|
-
status: response.status,
|
|
136
|
-
headers: {
|
|
137
|
-
get(key) {
|
|
138
|
-
return response.headers[key] ?? response.headers[key.toLowerCase()];
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
body: rawBody,
|
|
142
|
-
json() { return rawBody; },
|
|
143
|
-
text() { return textBody; },
|
|
144
|
-
time: response.timing.total_ms,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
// ── nr.test ──
|
|
148
|
-
function nrTest(name, fn) {
|
|
149
|
-
try {
|
|
150
|
-
fn();
|
|
151
|
-
tests.push({ name, passed: true });
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
tests.push({
|
|
155
|
-
name,
|
|
156
|
-
passed: false,
|
|
157
|
-
error: err instanceof Error ? err.message : String(err),
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// ── nr.log ──
|
|
162
|
-
function nrLog(...args) {
|
|
163
|
-
logs.push(args.map(String).join(" "));
|
|
164
|
-
}
|
|
165
|
-
// Assemble the nr object
|
|
166
|
-
const nr = {
|
|
167
|
-
variables: nrVariables,
|
|
168
|
-
request: nrRequest,
|
|
169
|
-
cookies: nrCookies,
|
|
170
|
-
environment: nrEnvironment,
|
|
171
|
-
test: nrTest,
|
|
172
|
-
log: nrLog,
|
|
173
|
-
};
|
|
174
|
-
if (nrResponse)
|
|
175
|
-
nr.response = nrResponse;
|
|
176
|
-
// ── Sandbox ──
|
|
177
|
-
const sandbox = createContext({
|
|
178
|
-
nr,
|
|
179
|
-
console: { log: nrLog, warn: nrLog, error: nrLog },
|
|
180
|
-
JSON,
|
|
181
|
-
parseInt,
|
|
182
|
-
parseFloat,
|
|
183
|
-
Math,
|
|
184
|
-
Date,
|
|
185
|
-
Array,
|
|
186
|
-
Object,
|
|
187
|
-
String,
|
|
188
|
-
Number,
|
|
189
|
-
Boolean,
|
|
190
|
-
RegExp,
|
|
191
|
-
Map,
|
|
192
|
-
Set,
|
|
193
|
-
encodeURIComponent,
|
|
194
|
-
decodeURIComponent,
|
|
195
|
-
encodeURI,
|
|
196
|
-
decodeURI,
|
|
197
|
-
btoa: (s) => Buffer.from(s).toString("base64"),
|
|
198
|
-
atob: (s) => Buffer.from(s, "base64").toString("utf-8"),
|
|
199
|
-
isNaN,
|
|
200
|
-
isFinite,
|
|
201
|
-
undefined,
|
|
202
|
-
NaN,
|
|
203
|
-
Infinity,
|
|
204
|
-
});
|
|
24
|
+
const scope = createScriptScope(context, phase, response);
|
|
25
|
+
const sandbox = createContext(scope.globals);
|
|
205
26
|
try {
|
|
206
27
|
runInContext(code, sandbox, { timeout: 5000, filename: `script-${phase}.js` });
|
|
207
28
|
}
|
|
208
29
|
catch (err) {
|
|
209
|
-
|
|
210
|
-
logs.push(`[script error] ${msg}`);
|
|
30
|
+
scope.fail(err instanceof Error ? err.message : String(err));
|
|
211
31
|
}
|
|
212
|
-
|
|
213
|
-
const result = { variables };
|
|
214
|
-
const hasMutations = mutations.headers || mutations.body !== undefined ||
|
|
215
|
-
mutations.cookies || mutations.path || mutations.method;
|
|
216
|
-
if (hasMutations)
|
|
217
|
-
result.mutations = mutations;
|
|
218
|
-
if (tests.length)
|
|
219
|
-
result.tests = tests;
|
|
220
|
-
if (logs.length)
|
|
221
|
-
result.logs = logs;
|
|
222
|
-
return result;
|
|
32
|
+
return scope.finish();
|
|
223
33
|
}
|
|
224
34
|
}
|
|
225
35
|
//# sourceMappingURL=scripts.js.map
|