@usebruno/js 0.44.0 → 0.45.0
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/package.json +3 -7
- package/src/bru.js +30 -5
- package/src/bruno-request.js +58 -0
- package/src/bruno-response.js +14 -0
- package/src/runtime/assert-runtime.js +4 -1
- package/src/runtime/script-runtime.js +4 -3
- package/src/runtime/test-runtime.js +2 -1
- package/src/runtime/vars-runtime.js +2 -1
- package/src/sandbox/node-vm/cjs-loader.js +369 -0
- package/src/sandbox/node-vm/constants.js +99 -0
- package/src/sandbox/node-vm/index.js +46 -222
- package/src/sandbox/node-vm/index.spec.js +950 -4
- package/src/sandbox/node-vm/utils.js +42 -0
- package/src/sandbox/quickjs/shims/bru.js +6 -0
- package/src/sandbox/quickjs/shims/bruno-request.js +27 -0
|
@@ -106,6 +106,43 @@ describe('node-vm sandbox', () => {
|
|
|
106
106
|
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
107
107
|
).rejects.toThrow('Access to files outside of the allowed context roots is not allowed');
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
it('should block absolute paths outside allowed roots', async () => {
|
|
111
|
+
// Try to require an absolute path outside the collection
|
|
112
|
+
const script = `
|
|
113
|
+
const secret = require('/etc/passwd');
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
const context = { console: console };
|
|
117
|
+
|
|
118
|
+
await expect(
|
|
119
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
120
|
+
).rejects.toThrow('Access to files outside of the allowed context roots is not allowed');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should allow absolute paths within allowed roots', async () => {
|
|
124
|
+
// Create a module in the collection
|
|
125
|
+
fs.writeFileSync(
|
|
126
|
+
path.join(collectionPath, 'absolute-test.js'),
|
|
127
|
+
'module.exports = { loaded: true };'
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Use absolute path to require it
|
|
131
|
+
const absolutePath = path.join(collectionPath, 'absolute-test.js');
|
|
132
|
+
const script = `
|
|
133
|
+
const mod = require('${absolutePath.replace(/\\/g, '\\\\')}');
|
|
134
|
+
bru.setVar('result', mod.loaded);
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const context = {
|
|
138
|
+
bru: { setVar: jest.fn() },
|
|
139
|
+
console: console
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
143
|
+
|
|
144
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
145
|
+
});
|
|
109
146
|
});
|
|
110
147
|
|
|
111
148
|
describe('createCustomRequire - additionalContextRoots', () => {
|
|
@@ -225,28 +262,937 @@ describe('node-vm sandbox', () => {
|
|
|
225
262
|
|
|
226
263
|
describe('createCustomRequire - module caching', () => {
|
|
227
264
|
it('should cache loaded modules', async () => {
|
|
228
|
-
|
|
265
|
+
// Module increments a counter each time it's executed
|
|
266
|
+
// If caching works, counter should only be 1 after multiple requires
|
|
229
267
|
fs.writeFileSync(
|
|
230
268
|
path.join(collectionPath, 'cached.js'),
|
|
231
269
|
`
|
|
232
|
-
|
|
270
|
+
if (!global._cacheTestCount) global._cacheTestCount = 0;
|
|
271
|
+
global._cacheTestCount++;
|
|
272
|
+
module.exports = { id: Date.now() };
|
|
233
273
|
`
|
|
234
274
|
);
|
|
235
275
|
|
|
236
276
|
const script = `
|
|
237
277
|
const mod1 = require('./cached');
|
|
238
278
|
const mod2 = require('./cached');
|
|
239
|
-
|
|
279
|
+
const mod3 = require('./cached');
|
|
280
|
+
bru.setVar('sameInstance', mod1 === mod2 && mod2 === mod3);
|
|
281
|
+
bru.setVar('loadCount', global._cacheTestCount);
|
|
282
|
+
`;
|
|
283
|
+
|
|
284
|
+
const context = {
|
|
285
|
+
bru: { setVar: jest.fn() },
|
|
286
|
+
console: console
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
290
|
+
|
|
291
|
+
// All requires should return the same cached instance
|
|
292
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('sameInstance', true);
|
|
293
|
+
// Module should only be executed once
|
|
294
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('loadCount', 1);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should handle circular dependencies', async () => {
|
|
298
|
+
// Create two modules that require each other
|
|
299
|
+
fs.writeFileSync(
|
|
300
|
+
path.join(collectionPath, 'circularA.js'),
|
|
301
|
+
`
|
|
302
|
+
exports.name = 'A';
|
|
303
|
+
const B = require('./circularB');
|
|
304
|
+
exports.fromB = B.name;
|
|
305
|
+
`
|
|
306
|
+
);
|
|
307
|
+
fs.writeFileSync(
|
|
308
|
+
path.join(collectionPath, 'circularB.js'),
|
|
309
|
+
`
|
|
310
|
+
exports.name = 'B';
|
|
311
|
+
const A = require('./circularA');
|
|
312
|
+
exports.fromA = A.name;
|
|
313
|
+
`
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const script = `
|
|
317
|
+
const A = require('./circularA');
|
|
318
|
+
// A loads first, sets exports.name='A', then requires B
|
|
319
|
+
// B loads, sets exports.name='B', requires A (gets partial: {name:'A'})
|
|
320
|
+
// B finishes with {name:'B', fromA:'A'}
|
|
321
|
+
// A finishes with {name:'A', fromB:'B'}
|
|
322
|
+
bru.setVar('result', A.name + '-' + A.fromB);
|
|
323
|
+
`;
|
|
324
|
+
|
|
325
|
+
const context = {
|
|
326
|
+
bru: { setVar: jest.fn() },
|
|
327
|
+
console: console
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
331
|
+
|
|
332
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'A-B');
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe('createCustomRequire - Node.js builtin modules', () => {
|
|
337
|
+
it('should load builtin modules (crypto)', async () => {
|
|
338
|
+
const script = `
|
|
339
|
+
const crypto = require('crypto');
|
|
340
|
+
bru.setVar('result', typeof crypto.createHash);
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
const context = {
|
|
344
|
+
bru: { setVar: jest.fn() },
|
|
345
|
+
console: console
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
349
|
+
|
|
350
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function');
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('should support node: prefix syntax', async () => {
|
|
354
|
+
const script = `
|
|
355
|
+
const path = require('node:path');
|
|
356
|
+
bru.setVar('result', typeof path.join);
|
|
357
|
+
`;
|
|
358
|
+
|
|
359
|
+
const context = {
|
|
360
|
+
bru: { setVar: jest.fn() },
|
|
361
|
+
console: console
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
365
|
+
|
|
366
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('should allow all builtin modules including fs', async () => {
|
|
370
|
+
const script = `
|
|
371
|
+
const fs = require('fs');
|
|
372
|
+
bru.setVar('result', typeof fs.readFileSync);
|
|
373
|
+
`;
|
|
374
|
+
|
|
375
|
+
const context = {
|
|
376
|
+
bru: { setVar: jest.fn() },
|
|
377
|
+
console: console
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
381
|
+
|
|
382
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should load multiple builtins', async () => {
|
|
386
|
+
const script = `
|
|
387
|
+
const url = require('url');
|
|
388
|
+
const util = require('util');
|
|
389
|
+
const buffer = require('buffer');
|
|
390
|
+
const fs = require('fs');
|
|
391
|
+
bru.setVar('result', typeof url.parse + '-' + typeof util.format + '-' + typeof buffer.Buffer + '-' + typeof fs.readFileSync);
|
|
392
|
+
`;
|
|
393
|
+
|
|
394
|
+
const context = {
|
|
395
|
+
bru: { setVar: jest.fn() },
|
|
396
|
+
console: console
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
400
|
+
|
|
401
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function-function-function-function');
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('createCustomRequire - npm modules in vm context', () => {
|
|
406
|
+
it('should load npm modules from collection into vm context', async () => {
|
|
407
|
+
// Create a mock npm module in collection's node_modules
|
|
408
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'test-module');
|
|
409
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
410
|
+
fs.writeFileSync(
|
|
411
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
412
|
+
'module.exports = { name: "test-module", value: 123 };'
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
const script = `
|
|
416
|
+
const testMod = require('test-module');
|
|
417
|
+
bru.setVar('result', testMod.name + '-' + testMod.value);
|
|
418
|
+
`;
|
|
419
|
+
|
|
420
|
+
const context = {
|
|
421
|
+
bru: { setVar: jest.fn() },
|
|
422
|
+
console: console
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
426
|
+
|
|
427
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'test-module-123');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('should handle npm module with dependencies', async () => {
|
|
431
|
+
// Create a mock npm module with internal dependencies
|
|
432
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'parent-module');
|
|
433
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
434
|
+
fs.writeFileSync(
|
|
435
|
+
path.join(nodeModulesDir, 'helper.js'),
|
|
436
|
+
'module.exports = { helper: true };'
|
|
437
|
+
);
|
|
438
|
+
fs.writeFileSync(
|
|
439
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
440
|
+
'const helper = require("./helper"); module.exports = { hasHelper: helper.helper };'
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const script = `
|
|
444
|
+
const parentMod = require('parent-module');
|
|
445
|
+
bru.setVar('result', parentMod.hasHelper);
|
|
446
|
+
`;
|
|
447
|
+
|
|
448
|
+
const context = {
|
|
449
|
+
bru: { setVar: jest.fn() },
|
|
450
|
+
console: console
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
454
|
+
|
|
455
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should provide bru object to npm modules', async () => {
|
|
459
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'bru-access-module');
|
|
460
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
461
|
+
fs.writeFileSync(
|
|
462
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
463
|
+
`module.exports = {
|
|
464
|
+
getEnvVar: function(name) { return bru.getEnvVar(name); },
|
|
465
|
+
setVar: function(name, value) { bru.setVar(name, value); }
|
|
466
|
+
};`
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
const script = `
|
|
470
|
+
const bruModule = require('bru-access-module');
|
|
471
|
+
const envValue = bruModule.getEnvVar('TEST_VAR');
|
|
472
|
+
bruModule.setVar('result', envValue);
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
const getEnvVarMock = jest.fn().mockReturnValue('test-value');
|
|
476
|
+
const setVarMock = jest.fn();
|
|
477
|
+
const context = {
|
|
478
|
+
bru: {
|
|
479
|
+
getEnvVar: getEnvVarMock,
|
|
480
|
+
setVar: setVarMock
|
|
481
|
+
},
|
|
482
|
+
console: console
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
486
|
+
|
|
487
|
+
expect(getEnvVarMock).toHaveBeenCalledWith('TEST_VAR');
|
|
488
|
+
expect(setVarMock).toHaveBeenCalledWith('result', 'test-value');
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('should provide req object to npm modules', async () => {
|
|
492
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'req-access-module');
|
|
493
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
494
|
+
fs.writeFileSync(
|
|
495
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
496
|
+
`module.exports = {
|
|
497
|
+
getUrl: function() { return req.getUrl(); },
|
|
498
|
+
getMethod: function() { return req.getMethod(); },
|
|
499
|
+
setHeader: function(name, value) { req.setHeader(name, value); }
|
|
500
|
+
};`
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const script = `
|
|
504
|
+
const reqModule = require('req-access-module');
|
|
505
|
+
const url = reqModule.getUrl();
|
|
506
|
+
const method = reqModule.getMethod();
|
|
507
|
+
reqModule.setHeader('X-Custom', 'value');
|
|
508
|
+
bru.setVar('result', method + ':' + url);
|
|
509
|
+
`;
|
|
510
|
+
|
|
511
|
+
const setVarMock = jest.fn();
|
|
512
|
+
const getUrlMock = jest.fn().mockReturnValue('https://api.example.com');
|
|
513
|
+
const getMethodMock = jest.fn().mockReturnValue('POST');
|
|
514
|
+
const setHeaderMock = jest.fn();
|
|
515
|
+
const context = {
|
|
516
|
+
bru: { setVar: setVarMock },
|
|
517
|
+
req: {
|
|
518
|
+
getUrl: getUrlMock,
|
|
519
|
+
getMethod: getMethodMock,
|
|
520
|
+
setHeader: setHeaderMock
|
|
521
|
+
},
|
|
522
|
+
console: console
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
526
|
+
|
|
527
|
+
expect(getUrlMock).toHaveBeenCalled();
|
|
528
|
+
expect(getMethodMock).toHaveBeenCalled();
|
|
529
|
+
expect(setHeaderMock).toHaveBeenCalledWith('X-Custom', 'value');
|
|
530
|
+
expect(setVarMock).toHaveBeenCalledWith('result', 'POST:https://api.example.com');
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('should provide res object to npm modules', async () => {
|
|
534
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'res-access-module');
|
|
535
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
536
|
+
fs.writeFileSync(
|
|
537
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
538
|
+
`module.exports = {
|
|
539
|
+
getStatus: function() { return res.getStatus(); },
|
|
540
|
+
getBody: function() { return res.getBody(); },
|
|
541
|
+
getHeader: function(name) { return res.getHeader(name); }
|
|
542
|
+
};`
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
const script = `
|
|
546
|
+
const resModule = require('res-access-module');
|
|
547
|
+
const status = resModule.getStatus();
|
|
548
|
+
const body = resModule.getBody();
|
|
549
|
+
const contentType = resModule.getHeader('content-type');
|
|
550
|
+
bru.setVar('result', status + ':' + contentType + ':' + body.message);
|
|
551
|
+
`;
|
|
552
|
+
|
|
553
|
+
const context = {
|
|
554
|
+
bru: { setVar: jest.fn() },
|
|
555
|
+
res: {
|
|
556
|
+
getStatus: jest.fn().mockReturnValue(200),
|
|
557
|
+
getBody: jest.fn().mockReturnValue({ message: 'success' }),
|
|
558
|
+
getHeader: jest.fn().mockReturnValue('application/json')
|
|
559
|
+
},
|
|
560
|
+
console: console
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
564
|
+
|
|
565
|
+
expect(context.res.getStatus).toHaveBeenCalled();
|
|
566
|
+
expect(context.res.getBody).toHaveBeenCalled();
|
|
567
|
+
expect(context.res.getHeader).toHaveBeenCalledWith('content-type');
|
|
568
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', '200:application/json:success');
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should provide bru, req, res to nested npm module dependencies', async () => {
|
|
572
|
+
// Create parent module
|
|
573
|
+
const parentDir = path.join(collectionPath, 'node_modules', 'parent-ctx-module');
|
|
574
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
575
|
+
fs.writeFileSync(
|
|
576
|
+
path.join(parentDir, 'index.js'),
|
|
577
|
+
`const child = require('./child');
|
|
578
|
+
module.exports = { childResult: child.getData() };`
|
|
579
|
+
);
|
|
580
|
+
// Create child module that accesses context
|
|
581
|
+
fs.writeFileSync(
|
|
582
|
+
path.join(parentDir, 'child.js'),
|
|
583
|
+
`module.exports = {
|
|
584
|
+
getData: function() {
|
|
585
|
+
return {
|
|
586
|
+
envVar: bru.getEnvVar('NESTED_VAR'),
|
|
587
|
+
reqUrl: req.getUrl(),
|
|
588
|
+
resStatus: res.getStatus()
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};`
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
const script = `
|
|
595
|
+
const parent = require('parent-ctx-module');
|
|
596
|
+
const data = parent.childResult;
|
|
597
|
+
bru.setVar('result', data.envVar + '|' + data.reqUrl + '|' + data.resStatus);
|
|
598
|
+
`;
|
|
599
|
+
|
|
600
|
+
const getEnvVarMock = jest.fn().mockReturnValue('nested-value');
|
|
601
|
+
const setVarMock = jest.fn();
|
|
602
|
+
const getUrlMock = jest.fn().mockReturnValue('https://nested.example.com');
|
|
603
|
+
const getStatusMock = jest.fn().mockReturnValue(201);
|
|
604
|
+
const context = {
|
|
605
|
+
bru: {
|
|
606
|
+
getEnvVar: getEnvVarMock,
|
|
607
|
+
setVar: setVarMock
|
|
608
|
+
},
|
|
609
|
+
req: {
|
|
610
|
+
getUrl: getUrlMock
|
|
611
|
+
},
|
|
612
|
+
res: {
|
|
613
|
+
getStatus: getStatusMock
|
|
614
|
+
},
|
|
615
|
+
console: console
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
619
|
+
|
|
620
|
+
expect(getEnvVarMock).toHaveBeenCalledWith('NESTED_VAR');
|
|
621
|
+
expect(getUrlMock).toHaveBeenCalled();
|
|
622
|
+
expect(getStatusMock).toHaveBeenCalled();
|
|
623
|
+
expect(setVarMock).toHaveBeenCalledWith('result', 'nested-value|https://nested.example.com|201');
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
describe('CommonJS module patterns', () => {
|
|
627
|
+
it('should handle module.exports = object pattern', async () => {
|
|
628
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-object');
|
|
629
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
630
|
+
fs.writeFileSync(
|
|
631
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
632
|
+
'module.exports = { foo: "bar", num: 42 };'
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
const script = `
|
|
636
|
+
const mod = require('cjs-object');
|
|
637
|
+
bru.setVar('result', mod.foo + '-' + mod.num);
|
|
638
|
+
`;
|
|
639
|
+
|
|
640
|
+
const context = {
|
|
641
|
+
bru: { setVar: jest.fn() },
|
|
642
|
+
console: console
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
646
|
+
|
|
647
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'bar-42');
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it('should handle module.exports = function pattern', async () => {
|
|
651
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-function');
|
|
652
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
653
|
+
fs.writeFileSync(
|
|
654
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
655
|
+
'module.exports = function(x) { return x * 2; };'
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
const script = `
|
|
659
|
+
const double = require('cjs-function');
|
|
660
|
+
bru.setVar('result', double(21));
|
|
661
|
+
`;
|
|
662
|
+
|
|
663
|
+
const context = {
|
|
664
|
+
bru: { setVar: jest.fn() },
|
|
665
|
+
console: console
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
669
|
+
|
|
670
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 42);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('should handle module.exports = class pattern', async () => {
|
|
674
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-class');
|
|
675
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
676
|
+
fs.writeFileSync(
|
|
677
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
678
|
+
`class Calculator {
|
|
679
|
+
constructor(val) { this.val = val; }
|
|
680
|
+
add(x) { return this.val + x; }
|
|
681
|
+
}
|
|
682
|
+
module.exports = Calculator;`
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
const script = `
|
|
686
|
+
const Calculator = require('cjs-class');
|
|
687
|
+
const calc = new Calculator(10);
|
|
688
|
+
bru.setVar('result', calc.add(5));
|
|
689
|
+
`;
|
|
690
|
+
|
|
691
|
+
const context = {
|
|
692
|
+
bru: { setVar: jest.fn() },
|
|
693
|
+
console: console
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
697
|
+
|
|
698
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 15);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('should handle exports.property pattern', async () => {
|
|
702
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-exports');
|
|
703
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
704
|
+
fs.writeFileSync(
|
|
705
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
706
|
+
`exports.add = function(a, b) { return a + b; };
|
|
707
|
+
exports.multiply = function(a, b) { return a * b; };
|
|
708
|
+
exports.VERSION = '1.0.0';`
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
const script = `
|
|
712
|
+
const math = require('cjs-exports');
|
|
713
|
+
bru.setVar('result', math.add(2, 3) + '-' + math.multiply(4, 5) + '-' + math.VERSION);
|
|
714
|
+
`;
|
|
715
|
+
|
|
716
|
+
const context = {
|
|
717
|
+
bru: { setVar: jest.fn() },
|
|
718
|
+
console: console
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
722
|
+
|
|
723
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', '5-20-1.0.0');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it('should handle mixed module.exports and exports pattern', async () => {
|
|
727
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-mixed');
|
|
728
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
729
|
+
fs.writeFileSync(
|
|
730
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
731
|
+
`// module.exports takes precedence
|
|
732
|
+
exports.ignored = 'this will be ignored';
|
|
733
|
+
module.exports = { actual: 'value' };`
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
const script = `
|
|
737
|
+
const mod = require('cjs-mixed');
|
|
738
|
+
bru.setVar('result', mod.actual + '-' + (mod.ignored || 'undefined'));
|
|
739
|
+
`;
|
|
740
|
+
|
|
741
|
+
const context = {
|
|
742
|
+
bru: { setVar: jest.fn() },
|
|
743
|
+
console: console
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
747
|
+
|
|
748
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'value-undefined');
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
describe('File extension handling', () => {
|
|
753
|
+
it('should load .cjs files as CommonJS', async () => {
|
|
754
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-ext-module');
|
|
755
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
756
|
+
fs.writeFileSync(
|
|
757
|
+
path.join(nodeModulesDir, 'package.json'),
|
|
758
|
+
'{"name": "cjs-ext-module", "main": "index.cjs"}'
|
|
759
|
+
);
|
|
760
|
+
fs.writeFileSync(
|
|
761
|
+
path.join(nodeModulesDir, 'index.cjs'),
|
|
762
|
+
'module.exports = { format: "cjs", value: 100 };'
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
const script = `
|
|
766
|
+
const mod = require('cjs-ext-module');
|
|
767
|
+
bru.setVar('result', mod.format + '-' + mod.value);
|
|
768
|
+
`;
|
|
769
|
+
|
|
770
|
+
const context = {
|
|
771
|
+
bru: { setVar: jest.fn() },
|
|
772
|
+
console: console
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
776
|
+
|
|
777
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'cjs-100');
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should fail when loading .mjs files (ES modules)', async () => {
|
|
781
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'mjs-ext-module');
|
|
782
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
783
|
+
fs.writeFileSync(
|
|
784
|
+
path.join(nodeModulesDir, 'package.json'),
|
|
785
|
+
'{"name": "mjs-ext-module", "main": "index.mjs"}'
|
|
786
|
+
);
|
|
787
|
+
fs.writeFileSync(
|
|
788
|
+
path.join(nodeModulesDir, 'index.mjs'),
|
|
789
|
+
'export default { format: "esm" };'
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
const script = `
|
|
793
|
+
const mod = require('mjs-ext-module');
|
|
794
|
+
`;
|
|
795
|
+
|
|
796
|
+
const context = { console: console };
|
|
797
|
+
|
|
798
|
+
await expect(
|
|
799
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
800
|
+
).rejects.toThrow();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it('should load module with package.json main field', async () => {
|
|
804
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'custom-main');
|
|
805
|
+
fs.mkdirSync(path.join(nodeModulesDir, 'lib'), { recursive: true });
|
|
806
|
+
fs.writeFileSync(
|
|
807
|
+
path.join(nodeModulesDir, 'package.json'),
|
|
808
|
+
'{"name": "custom-main", "main": "lib/entry.js"}'
|
|
809
|
+
);
|
|
810
|
+
fs.writeFileSync(
|
|
811
|
+
path.join(nodeModulesDir, 'lib', 'entry.js'),
|
|
812
|
+
'module.exports = { entry: "custom-main-lib" };'
|
|
813
|
+
);
|
|
814
|
+
|
|
815
|
+
const script = `
|
|
816
|
+
const mod = require('custom-main');
|
|
817
|
+
bru.setVar('result', mod.entry);
|
|
818
|
+
`;
|
|
819
|
+
|
|
820
|
+
const context = {
|
|
821
|
+
bru: { setVar: jest.fn() },
|
|
822
|
+
console: console
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
826
|
+
|
|
827
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'custom-main-lib');
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it('should require relative .cjs files within npm module', async () => {
|
|
831
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'cjs-relative');
|
|
832
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
833
|
+
fs.writeFileSync(
|
|
834
|
+
path.join(nodeModulesDir, 'helper.cjs'),
|
|
835
|
+
'module.exports = { helperValue: "from-cjs" };'
|
|
836
|
+
);
|
|
837
|
+
fs.writeFileSync(
|
|
838
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
839
|
+
'const helper = require("./helper.cjs"); module.exports = helper;'
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
const script = `
|
|
843
|
+
const mod = require('cjs-relative');
|
|
844
|
+
bru.setVar('result', mod.helperValue);
|
|
845
|
+
`;
|
|
846
|
+
|
|
847
|
+
const context = {
|
|
848
|
+
bru: { setVar: jest.fn() },
|
|
849
|
+
console: console
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
853
|
+
|
|
854
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'from-cjs');
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it('should load .json files directly', async () => {
|
|
858
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'json-direct');
|
|
859
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
860
|
+
fs.writeFileSync(
|
|
861
|
+
path.join(nodeModulesDir, 'package.json'),
|
|
862
|
+
'{"name": "json-direct", "main": "data.json"}'
|
|
863
|
+
);
|
|
864
|
+
fs.writeFileSync(
|
|
865
|
+
path.join(nodeModulesDir, 'data.json'),
|
|
866
|
+
'{"type": "json-main", "count": 42}'
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
const script = `
|
|
870
|
+
const data = require('json-direct');
|
|
871
|
+
bru.setVar('result', data.type + '-' + data.count);
|
|
872
|
+
`;
|
|
873
|
+
|
|
874
|
+
const context = {
|
|
875
|
+
bru: { setVar: jest.fn() },
|
|
876
|
+
console: console
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
880
|
+
|
|
881
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'json-main-42');
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
describe('JSON file handling', () => {
|
|
886
|
+
it('should load JSON files from npm modules', async () => {
|
|
887
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'json-module');
|
|
888
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
889
|
+
fs.writeFileSync(
|
|
890
|
+
path.join(nodeModulesDir, 'config.json'),
|
|
891
|
+
'{"name": "test-config", "version": "1.0.0", "enabled": true}'
|
|
892
|
+
);
|
|
893
|
+
fs.writeFileSync(
|
|
894
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
895
|
+
'const config = require("./config.json"); module.exports = config;'
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
const script = `
|
|
899
|
+
const config = require('json-module');
|
|
900
|
+
bru.setVar('result', config.name + '-' + config.version + '-' + config.enabled);
|
|
901
|
+
`;
|
|
902
|
+
|
|
903
|
+
const context = {
|
|
904
|
+
bru: { setVar: jest.fn() },
|
|
905
|
+
console: console
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
909
|
+
|
|
910
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'test-config-1.0.0-true');
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it('should handle nested JSON requires', async () => {
|
|
914
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'nested-json');
|
|
915
|
+
fs.mkdirSync(path.join(nodeModulesDir, 'data'), { recursive: true });
|
|
916
|
+
fs.writeFileSync(
|
|
917
|
+
path.join(nodeModulesDir, 'data', 'schema.json'),
|
|
918
|
+
'{"type": "object", "properties": {"id": {"type": "number"}}}'
|
|
919
|
+
);
|
|
920
|
+
fs.writeFileSync(
|
|
921
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
922
|
+
'const schema = require("./data/schema.json"); module.exports = { schema };'
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const script = `
|
|
926
|
+
const mod = require('nested-json');
|
|
927
|
+
bru.setVar('result', mod.schema.type + '-' + mod.schema.properties.id.type);
|
|
928
|
+
`;
|
|
929
|
+
|
|
930
|
+
const context = {
|
|
931
|
+
bru: { setVar: jest.fn() },
|
|
932
|
+
console: console
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
936
|
+
|
|
937
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'object-number');
|
|
938
|
+
});
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
describe('Node.js globals in npm modules', () => {
|
|
942
|
+
it('should have access to Buffer', async () => {
|
|
943
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'buffer-module');
|
|
944
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
945
|
+
fs.writeFileSync(
|
|
946
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
947
|
+
`module.exports = {
|
|
948
|
+
encode: function(str) { return Buffer.from(str).toString('base64'); },
|
|
949
|
+
decode: function(b64) { return Buffer.from(b64, 'base64').toString('utf8'); }
|
|
950
|
+
};`
|
|
951
|
+
);
|
|
952
|
+
|
|
953
|
+
const script = `
|
|
954
|
+
const bufMod = require('buffer-module');
|
|
955
|
+
const encoded = bufMod.encode('hello');
|
|
956
|
+
const decoded = bufMod.decode(encoded);
|
|
957
|
+
bru.setVar('result', encoded + '-' + decoded);
|
|
958
|
+
`;
|
|
959
|
+
|
|
960
|
+
const context = {
|
|
961
|
+
bru: { setVar: jest.fn() },
|
|
962
|
+
console: console
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
966
|
+
|
|
967
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'aGVsbG8=-hello');
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it('should have access to URL and URLSearchParams', async () => {
|
|
971
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'url-module');
|
|
972
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
973
|
+
fs.writeFileSync(
|
|
974
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
975
|
+
`module.exports = {
|
|
976
|
+
parseUrl: function(urlStr) {
|
|
977
|
+
const url = new URL(urlStr);
|
|
978
|
+
return url.hostname;
|
|
979
|
+
},
|
|
980
|
+
buildQuery: function(params) {
|
|
981
|
+
const search = new URLSearchParams(params);
|
|
982
|
+
return search.toString();
|
|
983
|
+
}
|
|
984
|
+
};`
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
const script = `
|
|
988
|
+
const urlMod = require('url-module');
|
|
989
|
+
bru.setVar('result', urlMod.parseUrl('https://example.com/path'));
|
|
990
|
+
`;
|
|
991
|
+
|
|
992
|
+
const context = {
|
|
993
|
+
bru: { setVar: jest.fn() },
|
|
994
|
+
console: console
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
998
|
+
|
|
999
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'example.com');
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
it('should have access to setTimeout/clearTimeout', async () => {
|
|
1003
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'timer-module');
|
|
1004
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
1005
|
+
fs.writeFileSync(
|
|
1006
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
1007
|
+
`module.exports = {
|
|
1008
|
+
hasTimers: function() {
|
|
1009
|
+
return typeof setTimeout === 'function' && typeof clearTimeout === 'function';
|
|
1010
|
+
}
|
|
1011
|
+
};`
|
|
1012
|
+
);
|
|
1013
|
+
|
|
1014
|
+
const script = `
|
|
1015
|
+
const timerMod = require('timer-module');
|
|
1016
|
+
bru.setVar('result', timerMod.hasTimers());
|
|
1017
|
+
`;
|
|
1018
|
+
|
|
1019
|
+
const context = {
|
|
1020
|
+
bru: { setVar: jest.fn() },
|
|
1021
|
+
console: console
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1025
|
+
|
|
1026
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
describe('Error handling', () => {
|
|
1031
|
+
it('should throw error for non-existent module', async () => {
|
|
1032
|
+
const script = `
|
|
1033
|
+
const mod = require('non-existent-module-xyz');
|
|
1034
|
+
`;
|
|
1035
|
+
|
|
1036
|
+
const context = { console: console };
|
|
1037
|
+
|
|
1038
|
+
await expect(
|
|
1039
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
1040
|
+
).rejects.toThrow('Could not resolve module');
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
it('should throw error for module with syntax error', async () => {
|
|
1044
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'syntax-error-module');
|
|
1045
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
1046
|
+
fs.writeFileSync(
|
|
1047
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
1048
|
+
'module.exports = { invalid syntax here'
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
const script = `
|
|
1052
|
+
const mod = require('syntax-error-module');
|
|
1053
|
+
`;
|
|
1054
|
+
|
|
1055
|
+
const context = { console: console };
|
|
1056
|
+
|
|
1057
|
+
await expect(
|
|
1058
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
1059
|
+
).rejects.toThrow();
|
|
1060
|
+
});
|
|
1061
|
+
|
|
1062
|
+
it('should throw error for module with runtime error', async () => {
|
|
1063
|
+
const nodeModulesDir = path.join(collectionPath, 'node_modules', 'runtime-error-module');
|
|
1064
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
1065
|
+
fs.writeFileSync(
|
|
1066
|
+
path.join(nodeModulesDir, 'index.js'),
|
|
1067
|
+
'throw new Error("Module initialization failed");'
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
const script = `
|
|
1071
|
+
const mod = require('runtime-error-module');
|
|
1072
|
+
`;
|
|
1073
|
+
|
|
1074
|
+
const context = { console: console };
|
|
1075
|
+
|
|
1076
|
+
await expect(
|
|
1077
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
1078
|
+
).rejects.toThrow('Module initialization failed');
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
describe('context isolation', () => {
|
|
1084
|
+
it('should have global pointing to isolated context (not host)', async () => {
|
|
1085
|
+
const context = {
|
|
1086
|
+
bru: { setVar: jest.fn() },
|
|
1087
|
+
console: console
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
// global exists but points to isolated context, so global.bru should exist
|
|
1091
|
+
// process is a sanitized object in the isolated context
|
|
1092
|
+
const script = `bru.setVar('result', typeof global.bru === 'object' && typeof global.process === 'object')`;
|
|
1093
|
+
|
|
1094
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1095
|
+
|
|
1096
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it('should not have access to host fs module via globalThis', async () => {
|
|
1100
|
+
const context = {
|
|
1101
|
+
bru: { setVar: jest.fn() },
|
|
1102
|
+
console: console
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
const script = `bru.setVar('result', typeof globalThis.fs)`;
|
|
1106
|
+
|
|
1107
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1108
|
+
|
|
1109
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'undefined');
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
it('should throw ReferenceError for undeclared variables', async () => {
|
|
1113
|
+
const context = { console: console };
|
|
1114
|
+
|
|
1115
|
+
const script = `const x = someUndeclaredVar`;
|
|
1116
|
+
|
|
1117
|
+
await expect(
|
|
1118
|
+
runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
|
|
1119
|
+
).rejects.toThrow('someUndeclaredVar is not defined');
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
it('should have access to context objects via globalThis', async () => {
|
|
1123
|
+
const context = {
|
|
1124
|
+
bru: { setVar: jest.fn() },
|
|
1125
|
+
req: { url: 'http://test.com' },
|
|
1126
|
+
console: console
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
const script = `bru.setVar('result', typeof globalThis.req)`;
|
|
1130
|
+
|
|
1131
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1132
|
+
|
|
1133
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'object');
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it('should have access to allowed globals like Buffer', async () => {
|
|
1137
|
+
const context = {
|
|
1138
|
+
bru: { setVar: jest.fn() },
|
|
1139
|
+
console: console
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
const script = `bru.setVar('result', typeof globalThis.Buffer)`;
|
|
1143
|
+
|
|
1144
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1145
|
+
|
|
1146
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function');
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
it('should have access to process object with nextTick', async () => {
|
|
1150
|
+
const context = {
|
|
1151
|
+
bru: { setVar: jest.fn() },
|
|
1152
|
+
console: console
|
|
1153
|
+
};
|
|
1154
|
+
|
|
1155
|
+
const script = `
|
|
1156
|
+
const hasSafeProps = typeof process.version === 'string' && typeof process.platform === 'string';
|
|
1157
|
+
const hasNextTick = typeof process.nextTick === 'function';
|
|
1158
|
+
bru.setVar('result', hasSafeProps && hasNextTick);
|
|
1159
|
+
`;
|
|
1160
|
+
|
|
1161
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1162
|
+
|
|
1163
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
it('should work with Array.isArray across context boundaries', async () => {
|
|
1167
|
+
const context = {
|
|
1168
|
+
bru: { setVar: jest.fn() },
|
|
1169
|
+
console: console
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
const script = `
|
|
1173
|
+
const arr = [1, 2, 3];
|
|
1174
|
+
bru.setVar('result', Array.isArray(arr));
|
|
240
1175
|
`;
|
|
241
1176
|
|
|
1177
|
+
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
1178
|
+
|
|
1179
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
it('should have working Object methods', async () => {
|
|
242
1183
|
const context = {
|
|
243
1184
|
bru: { setVar: jest.fn() },
|
|
244
1185
|
console: console
|
|
245
1186
|
};
|
|
246
1187
|
|
|
1188
|
+
const script = `
|
|
1189
|
+
const obj = { a: 1, b: 2 };
|
|
1190
|
+
bru.setVar('result', Object.keys(obj).join(','));
|
|
1191
|
+
`;
|
|
1192
|
+
|
|
247
1193
|
await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
|
|
248
1194
|
|
|
249
|
-
expect(context.bru.setVar).toHaveBeenCalledWith('
|
|
1195
|
+
expect(context.bru.setVar).toHaveBeenCalledWith('result', 'a,b');
|
|
250
1196
|
});
|
|
251
1197
|
});
|
|
252
1198
|
});
|