├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── dependabot.yml └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .jsdoc-config-type-def-sandbox.json ├── .jsdoc-config-type-def.json ├── .jsdoc-config.json ├── .npmignore ├── .nycrc.js ├── CHANGELOG.yaml ├── LICENSE.md ├── README.md ├── codecov.yml ├── index.js ├── lib ├── bootcode.js ├── bundle │ ├── bundling-options.js │ ├── index.browser.js │ └── index.js ├── environment.js ├── index.js ├── postman-sandbox-fleet.js ├── postman-sandbox.js ├── sandbox │ ├── console.js │ ├── cookie-jar.js │ ├── cookie-store.js │ ├── execute-context.js │ ├── execute.js │ ├── execution.js │ ├── index.js │ ├── non-legacy-codemarkers.js │ ├── ping.js │ ├── pm-require.js │ ├── pmapi-setup-runner.js │ ├── pmapi.js │ ├── postman-legacy-interface.js │ ├── purse.js │ ├── timers.js │ ├── vault.js │ └── xml2Json.js └── vendor │ ├── atob.js │ ├── btoa.js │ ├── buffer │ ├── buffer.js │ ├── index.browser.js │ ├── index.js │ └── specific-buffer.js │ ├── sugar.js │ └── uuid.js ├── npm ├── build-sandbox-types.js ├── build-types.js ├── cache.js ├── create-release.js ├── prepublish.js ├── publish-coverage.js ├── test-browser.js ├── test-integration.js ├── test-lint.js ├── test-system.js ├── test-unit.js ├── test-vm.js └── utils │ ├── jsdoc-custom-tags-plugin.js │ └── templates.js ├── package-lock.json ├── package.json ├── test ├── .eslintrc ├── integration │ └── _bootstrap.js ├── karma.conf.js ├── system │ ├── bootcode-dependencies.test.js │ ├── bootcode-size.test.js │ ├── editorconfig.test.js │ ├── jsdoc-config.test.js │ ├── npm-publish.test.js │ └── repository.test.js ├── unit │ ├── _bootstrap.js │ ├── execution.test.js │ ├── pm-variables-tracking.test.js │ ├── pm-variables.test.js │ ├── sandbox-assertion-events.test.js │ ├── sandbox-assertions.test.js │ ├── sandbox-codemarkers.test.js │ ├── sandbox-console.test.js │ ├── sandbox-dispose.test.js │ ├── sandbox-error-events.test.js │ ├── sandbox-libraries │ │ ├── ajv.test.js │ │ ├── chai-postman.test.js │ │ ├── cheerio.test.js │ │ ├── crypto.test.js │ │ ├── csv-parse.test.js │ │ ├── deprecation.test.js │ │ ├── legacy.test.js │ │ ├── liquid-json.test.js │ │ ├── lodash3.test.js │ │ ├── moment-min.test.js │ │ ├── pm-require.test.js │ │ ├── pm.test.js │ │ ├── postman-collection.test.js │ │ ├── postman.test.js │ │ ├── sugar.test.js │ │ ├── tv4.test.js │ │ ├── uuid-vendor.test.js │ │ └── xml2Json.test.js │ ├── sandbox-promises.test.js │ ├── sandbox-sanity.test.js │ ├── sandbox-shenanigans.test.js │ ├── sandbox-timeout.test.js │ ├── sandbox-timers.test.js │ ├── timers.test.js │ └── vendors │ │ ├── atob.test.js │ │ ├── btoa.test.js │ │ └── buffer.test.js └── vm │ ├── _bootstrap.js │ └── postman-collection.test.js └── types ├── index.d.ts └── sandbox ├── prerequest.d.ts └── test.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | max_length = 120 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{json, yml, html, hbs}] 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior 2 | * text=auto 3 | 4 | # JavaScript files will always have LF line endings 5 | *.js text eol=lf 6 | 7 | # JSON files always have LF line endings 8 | *.json text eol=lf 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: "saturday" 8 | time: "03:14" 9 | timezone: Asia/Calcutta 10 | rebase-strategy: "disabled" 11 | open-pull-requests-limit: 10 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | pull_request: 9 | branches: [$default-branch] 10 | schedule: 11 | - cron: '0 12 * * 0' 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | lint: 19 | name: Lint 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v3 25 | 26 | - name: Use Node.js 18.x 27 | uses: actions/setup-node@v3 28 | with: 29 | node-version: 18.x 30 | cache: 'npm' 31 | 32 | - name: Install 33 | run: npm ci 34 | 35 | - name: Run lint tests 36 | run: npm run test-lint 37 | 38 | browser-tests: 39 | name: Browser Tests 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v3 45 | 46 | - name: Use Node.js 18.x 47 | uses: actions/setup-node@v3 48 | with: 49 | node-version: 18.x 50 | cache: 'npm' 51 | 52 | - name: Install 53 | run: npm ci 54 | 55 | - name: Pretest 56 | run: npm run pretest 57 | 58 | - name: Run browser tests 59 | run: npm run test-browser 60 | 61 | tests: 62 | name: Tests 63 | runs-on: ${{ matrix.os }} 64 | strategy: 65 | fail-fast: false 66 | matrix: 67 | node-version: [18, 20] 68 | os: [ubuntu-latest, windows-latest] 69 | include: 70 | - coverage: true 71 | node-version: latest 72 | os: ubuntu-latest 73 | 74 | steps: 75 | - name: Checkout repository 76 | uses: actions/checkout@v3 77 | 78 | - name: Use Node.js ${{ matrix.node-version }} 79 | uses: actions/setup-node@v3 80 | with: 81 | node-version: ${{ matrix.node-version }} 82 | cache: 'npm' 83 | 84 | - name: Install 85 | run: npm ci 86 | 87 | - name: Pretest 88 | run: npm run pretest 89 | 90 | - name: Run system tests 91 | run: npm run test-system 92 | 93 | - name: Run unit tests 94 | run: npm run test-unit 95 | 96 | - if: ${{ matrix.coverage }} 97 | name: Upload coverage 98 | run: npm run codecov -- -c -Z -f .coverage/coverage-final.json -F unit -t ${{ secrets.CODECOV_TOKEN }} 99 | 100 | - name: Run vm tests 101 | run: npm run test-vm 102 | 103 | - name: Run integration tests 104 | run: npm run test-integration 105 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ develop, main ] 6 | pull_request: 7 | # The branches below must be a subset of the branches above 8 | branches: [ develop ] 9 | schedule: 10 | - cron: '0 12 * * 0' 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | language: [ 'javascript' ] 21 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 22 | # Learn more: 23 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v2 28 | 29 | # Initializes the CodeQL tools for scanning. 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v1 32 | with: 33 | languages: ${{ matrix.language }} 34 | # If you wish to specify custom queries, you can do so here or in a config file. 35 | # By default, queries listed here will override any specified in a config file. 36 | # Prefix the list here with "+" to use these queries and those in the config file. 37 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 38 | 39 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 40 | # If this step fails, then you should remove it and run the build manually (see below) 41 | - name: Autobuild 42 | uses: github/codeql-action/autobuild@v1 43 | 44 | # ℹ️ Command-line programs to run using the OS shell. 45 | # 📚 https://git.io/JvXDl 46 | 47 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 48 | # and modify them (or add more) to build your code if your project 49 | # uses a compiled language 50 | 51 | #- run: | 52 | # make bootstrap 53 | # make release 54 | 55 | - name: Perform CodeQL Analysis 56 | uses: github/codeql-action/analyze@v1 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # PLATFORM 2 | # ======== 3 | # All exclusions that are specific to the NPM, GIT, IDE and Operating Systems. 4 | 5 | # - Do not allow installed node modules to be committed. Doing `npm install -d` will bring them in root or other places. 6 | node_modules 7 | 8 | # - Do not commit any log file from anywhere 9 | *.log 10 | *.log.* 11 | 12 | # - Prevent addition of OS specific file explorer files 13 | Thumbs.db 14 | .DS_Store 15 | 16 | # Prevent IDE stuff 17 | .idea 18 | .vscode 19 | 20 | # PROJECT 21 | # ======= 22 | # Configuration pertaining to project specific repository structure. 23 | 24 | # - Prevent Sublime text IDE files from being committed to repository 25 | *.sublime-* 26 | 27 | # - Allow sublime text project file to be committed in the development directory. 28 | !/develop/*.sublime-project 29 | 30 | # - Prevent CI output files from being Added 31 | /out/ 32 | 33 | # - Prevent diff backups from SourceTree from showing as commit. 34 | *.BACKUP.* 35 | *.BASE.* 36 | *.LOCAL.* 37 | *.REMOTE.* 38 | *.orig 39 | 40 | # - Prevent unit test coverage reports from being committed to the repository 41 | .coverage 42 | .nyc_output 43 | 44 | # - do not commit sandbox .cache (but do not add it in npm ignore) 45 | .cache 46 | -------------------------------------------------------------------------------- /.jsdoc-config-type-def-sandbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc", 6 | "closure" 7 | ] 8 | }, 9 | "source": { 10 | "include": [ 11 | "lib/sandbox/pmapi.js", 12 | "lib/sandbox/execute-context.js", 13 | "lib/sandbox/cookie-jar.js" 14 | ], 15 | "includePattern": ".+\\.js(doc)?$", 16 | "excludePattern": "(^|\\/|\\\\)_" 17 | }, 18 | "plugins": [ 19 | "tsd-jsdoc/dist/plugin", 20 | "./npm/utils/jsdoc-custom-tags-plugin.js" 21 | ], 22 | "templates": { 23 | "cleverLinks": true, 24 | "default": { 25 | "outputSourceFiles": false 26 | } 27 | }, 28 | "opts": { 29 | "destination": "./types/sandbox/", 30 | "template": "tsd-jsdoc/dist", 31 | "outFile": "index.d.ts", 32 | "recurse": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.jsdoc-config-type-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": [ 5 | "jsdoc", 6 | "closure" 7 | ] 8 | }, 9 | "source": { 10 | "include": [ 11 | "lib/sandbox" 12 | ], 13 | "includePattern": ".+\\.js(doc)?$", 14 | "excludePattern": "(^|\\/|\\\\)_" 15 | }, 16 | "plugins": [ 17 | "tsd-jsdoc/dist/plugin" 18 | ], 19 | "templates": { 20 | "cleverLinks": true, 21 | "default": { 22 | "outputSourceFiles": false 23 | } 24 | }, 25 | "opts": { 26 | "destination": "./types", 27 | "template": "tsd-jsdoc/dist", 28 | "outFile": "index.d.ts", 29 | "recurse": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.jsdoc-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true, 4 | "dictionaries": ["jsdoc", "closure"] 5 | }, 6 | "source": { 7 | "include": [ ], 8 | "includePattern": ".+\\.js(doc)?$", 9 | "excludePattern": "(^|\\/|\\\\)_" 10 | }, 11 | 12 | "plugins": [ 13 | "plugins/markdown" 14 | ], 15 | 16 | "templates": { 17 | "cleverLinks": false, 18 | "monospaceLinks": false, 19 | "highlightTutorialCode" : true 20 | }, 21 | 22 | "opts": { 23 | "template": "./node_modules/postman-jsdoc-theme", 24 | "encoding": "utf8", 25 | "destination": "./out/docs", 26 | "recurse": true, 27 | "readme": "README.md" 28 | }, 29 | 30 | "markdown": { 31 | "parser": "gfm", 32 | "hardwrap": false 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # PLATFORM 2 | # ======== 3 | # All exclusions that are specific to the NPM, GIT, IDE and Operating Systems. 4 | 5 | # - Do not allow installed node modules to be committed. Doing `npm install -d` will bring them in root or other places. 6 | node_modules 7 | 8 | # - Do not commit any log file from anywhere 9 | *.log 10 | *.log.* 11 | 12 | # - Prevent addition of OS specific file explorer files 13 | Thumbs.db 14 | .DS_Store 15 | 16 | # Prevent IDE stuff 17 | .idea 18 | .vscode 19 | 20 | # PROJECT 21 | # ======= 22 | # Configuration pertaining to project specific repository structure. 23 | 24 | # - Prevent Sublime text IDE files from being committed to repository 25 | *.sublime-* 26 | 27 | # - Allow sublime text project file to be committed in the development directory. 28 | !/develop/*.sublime-project 29 | 30 | # - Prevent CI output files from being added 31 | /out/ 32 | 33 | # - Prevent diff backups from SourceTree from showing as commit. 34 | *.BACKUP.* 35 | *.BASE.* 36 | *.LOCAL.* 37 | *.REMOTE.* 38 | *.orig 39 | 40 | # - Prevent code coverage reports from being added 41 | .coverage 42 | .nyc_output 43 | 44 | # - Prevent config and test files from being added 45 | .git* 46 | npm/ 47 | test/ 48 | .eslintrc 49 | .nycrc.js 50 | codecov.yml 51 | .editorconfig 52 | .jsdoc-config.json 53 | .jsdoc-config-type-def.json 54 | .jsdoc-config-type-def-sandbox.json 55 | -------------------------------------------------------------------------------- /.nycrc.js: -------------------------------------------------------------------------------- 1 | const TEST_TYPE = ((argv) => { 2 | let match = argv[argv.length - 1].match(/npm\/test-(\w+).js/); 3 | 4 | return match && match[1] || ''; 5 | })(process.argv); 6 | 7 | function configOverrides(testType) { 8 | switch (testType) { 9 | case 'unit': 10 | return { 11 | statements: 45, 12 | branches: 30, 13 | functions: 30, 14 | lines: 45 15 | }; 16 | default: 17 | return {} 18 | } 19 | } 20 | 21 | module.exports = { 22 | // @todo cover `all` files by writing unit test for bundled lib/sandbox files 23 | // all: true, 24 | 'check-coverage': true, 25 | 'report-dir': '.coverage', 26 | 'temp-dir': '.nyc_output', 27 | include: ['lib/**/*.js'], 28 | reporter: ['lcov', 'json', 'text', 'text-summary'], 29 | ...configOverrides(TEST_TYPE), 30 | }; 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Postman Sandbox [![Build Status](https://github.com/postmanlabs/postman-sandbox/actions/workflows/ci.yml/badge.svg)](https://github.com/postmanlabs/postman-sandbox/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/postmanlabs/postman-sandbox/branch/develop/graph/badge.svg)](https://codecov.io/gh/postmanlabs/postman-sandbox) 2 | 3 | Module that unifies execution of third-party JavaScript within Node.js and Browser. 4 | 5 | > This module is part of Postman Runtime Core and is not intended for independent use. 6 | > 7 | > If you are looking to execute collections, you should bee using [Newman](https://github.com/postmanlabs/newman) 8 | 9 | ## Usage 10 | ```js 11 | var Sandbox = require('postman-sandbox'), 12 | context; 13 | 14 | Sandbox.createContext(function (err, ctx) { 15 | if (err) { 16 | return console.error(err); 17 | } 18 | 19 | ctx.execute(`// code here`, {}, {}, function (err) { 20 | if (err) { 21 | return console.error(err); 22 | } 23 | console.log('executed') 24 | }); 25 | }); 26 | ``` 27 | 28 | ## Sandbox Environment 29 | 30 | The following section outlines the API available inside sandbox scripts 31 | 32 | ### pm 33 | 34 | - pm.test 35 | - pm.info 36 | - pm.vault 37 | - pm.globals 38 | - pm.cookies 39 | - pm.execution 40 | - pm.variables 41 | - pm.visualizer 42 | - pm.sendRequest 43 | - pm.environment 44 | - pm.iterationData 45 | - pm.collectionVariables 46 | 47 | #### pre-request script specials 48 | 49 | - pm.request 50 | 51 | #### test script specials 52 | 53 | - pm.request 54 | - pm.response 55 | 56 | ## Events fired from Sandbox 57 | - pong 58 | - error 59 | - console 60 | - execution 61 | - execution.error 62 | - execution.error.* 63 | - execution.request.* 64 | - execution.result.* 65 | - execution.cookies.* 66 | - execution.skipRequest.* 67 | 68 | ## Events responded to 69 | - ping 70 | - execute 71 | - execution.abort.* 72 | - execution.response.* 73 | - execution.cookies.* 74 | 75 | ## Contributing 76 | 77 | ### Debug in browser 78 | 79 | To debug tests in Chrome's DevTools, start tests using `npm run test-browser -- --debug` and click `DEBUG`. 80 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 70..100 # green if 100+, red if 70- 3 | 4 | status: 5 | patch: 6 | # coverage status for pull request diff 7 | default: 8 | target: 100 # any patch should be 100% covered 9 | threshold: 1% # allow a little drop 10 | 11 | project: 12 | # coverage status for whole project 13 | default: 14 | target: auto # use coverage of base commit as target 15 | threshold: 1% # allow a little drop 16 | 17 | # coverage status for unit tests 18 | unit: 19 | target: 40 20 | flags: 21 | - unit 22 | 23 | parsers: 24 | javascript: 25 | enable_partials: yes # use partial line coverage 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @license Copyright 2016 Postdot Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 11 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and limitations under the License. 13 | */ 14 | module.exports = require('./lib'); 15 | -------------------------------------------------------------------------------- /lib/bootcode.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | env = require('./environment'); 3 | 4 | let cache, 5 | bundler, 6 | cacher; 7 | 8 | // we first try and load the pre-bundled file from file-cache. file cache might be absent during development phase and 9 | // as such, we fall back to live bundling. 10 | try { 11 | bundler = require('../.cache/bootcode'); 12 | } 13 | catch (e) { 14 | console && console.info('sandbox: ' + e.message + '\n' + 15 | 'bootcode is being live compiled. use `npm run cache` to use cached variant.'); 16 | } 17 | 18 | // in case bundler is not a valid function, we create a bundler that uses the environment to compile sandbox bootstrap 19 | // code 20 | !_.isFunction(bundler) && (bundler = function (done) { 21 | require('./bundle').load(env).compile(done); 22 | }); 23 | 24 | cacher = function (done) { 25 | // in case the cache is already populated, we simply forward the cached string to the caller 26 | if (cache) { 27 | return done(null, cache); 28 | } 29 | 30 | // since the code is not cached, we fetch the code from the bundler (it could be file cached or live compiled) and 31 | // then cache it before forwarding it to caller. 32 | bundler(function (err, code) { 33 | if (err) { return done(err); } 34 | 35 | // ensure buffer is stringified before being cached 36 | (code && !_.isString(code)) && (code = code.toString()); 37 | if (code && _.isString(code)) { // before caching we check the code as string once more 38 | cache = code; 39 | cacher.cached = true; // a flag to aid debugging 40 | } 41 | 42 | return done(null, cache); 43 | }); 44 | }; 45 | 46 | module.exports = cacher; 47 | -------------------------------------------------------------------------------- /lib/bundle/bundling-options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | insertGlobalVars: false, 3 | detectGlobals: true, 4 | browserField: false, 5 | bare: true, 6 | builtins: false, 7 | commondir: true, 8 | 9 | // This is to prevent bundling errors for modules that 10 | // are not in node_modules but are instead imported from a 11 | // vendor and should be exposed via `require` inside the bundle. 12 | ignoreMissing: true 13 | }; 14 | -------------------------------------------------------------------------------- /lib/bundle/index.browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Error message to trigger if bundling is accidentally triggered inside a browser 3 | * 4 | * @constant 5 | * @private 6 | * @type {String} 7 | */ 8 | const ERROR_MESSAGE = 'sandbox: code bundling is not supported in browser. use cached templates.'; 9 | 10 | function StubBundle () { 11 | throw new Error(ERROR_MESSAGE); 12 | } 13 | 14 | StubBundle.load = function () { 15 | throw new Error(ERROR_MESSAGE); 16 | }; 17 | 18 | module.exports = StubBundle; 19 | -------------------------------------------------------------------------------- /lib/bundle/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | { minify } = require('terser'), 3 | 4 | bundlingOptions = require('./bundling-options'), 5 | 6 | PREFER_BUILTIN = 'preferBuiltin', 7 | 8 | /** 9 | * To unite components of path in holy matrimony! 10 | * 11 | * @return {String} 12 | */ 13 | pathJoin = function () { 14 | return Array.prototype.join.call(arguments, '/').replace(/\/{1,}/g, '/'); 15 | }; 16 | 17 | let browserify, // loaded inside try-catch 18 | browserifyBuiltins; // loaded inside try-catch 19 | 20 | // The modules for browserification should only be required during development of this module and as such a production 21 | // installation should not even trigger require of this module. But in case it does, let's make the error message a bit 22 | // more consumable. 23 | try { 24 | browserify = require('browserify'); 25 | browserifyBuiltins = require('browserify/lib/builtins'); 26 | } 27 | catch (e) { 28 | console && console.error('sandbox: bundling triggered in production module installation mode'); 29 | throw e; 30 | } 31 | 32 | class Bundle { 33 | /** 34 | * Create a bundle from an options template 35 | * 36 | * @param {Object} options - 37 | * @param {Object} options.files - 38 | * @param {Object.} options.require - 39 | * @param {Boolean} options.require.global - 40 | * @param {Boolean} options.require.preferBuiltin - 41 | * @param {String} options.require.resolve - 42 | * @param {String} options.require.expose - 43 | * @param {Boolean|Object} options.compress - 44 | * @param {Array.} options.ignore - 45 | * @param {Object=} [options.bundler] - 46 | */ 47 | constructor (options) { 48 | /** 49 | * @private 50 | * @type {Browserify} 51 | */ 52 | this.bundler = browserify({ ...bundlingOptions, ...options.bundler }); // merge with user options 53 | 54 | /** 55 | * @private 56 | * @type {Boolean} 57 | */ 58 | this.compress = options.compress; 59 | 60 | this.preferBrowserResolver = options.preferBrowserResolver; 61 | 62 | // process any list of modules externally required and also accommodate the use of built-ins if needed 63 | _.forEach(options.require, (options, resolve) => { 64 | // allow resolution override where the required module is resolved 65 | // from a different name than the one provided in options 66 | if (this.preferBrowserResolver && options.resolveBrowser) { 67 | resolve = options.resolveBrowser; 68 | } 69 | else if (options.resolve) { 70 | resolve = options.resolve; 71 | } 72 | 73 | // set the name using which the module is exported to the one marked as the module name (only in case when 74 | // one is not explicitly provided in options.) 75 | !options.expose && (options.expose = resolve); 76 | 77 | if (_.get(options, PREFER_BUILTIN) && _.has(browserifyBuiltins, resolve)) { // @todo: add tests 78 | this.bundler.require(browserifyBuiltins[resolve], options); 79 | } 80 | else { 81 | this.bundler.require(require.resolve(resolve), options); // @todo: add tests for resolve failures 82 | } 83 | }); 84 | 85 | // ignore the items mentioned in ignore list 86 | _.forEach(options.ignore, this.bundler.ignore.bind(this.bundler)); 87 | 88 | // add files that are needed 89 | _.forEach(options.files, (options, file) => { 90 | this.bundler.add(pathJoin(__dirname, file), options); 91 | }); 92 | } 93 | 94 | compile (done) { 95 | this.bundler.bundle((err, bundle) => { 96 | if (err) { return done(err); } 97 | 98 | // Expose only the required node built-ins to be used in vendor modules 99 | // These will be cleared out by sandbox when the bundle is loaded 100 | const safeExposeBuiltIns = ` 101 | if (typeof require === 'function') { 102 | globalThis._nodeRequires = { 103 | buffer: require('buffer') 104 | }; 105 | 106 | // prevent access to node's require function 107 | require = null; 108 | } 109 | `, 110 | 111 | bundleString = safeExposeBuiltIns + bundle.toString(); 112 | 113 | // bail out if compression is disabled 114 | if (!this.compress) { return done(null, bundleString); } 115 | 116 | minify(bundleString, { 117 | compress: { 118 | drop_console: true // discard calls to console.* functions 119 | }, 120 | mangle: true, // Mangle names 121 | safari10: true, // Work around the Safari 10/11 await bug (bugs.webkit.org/show_bug.cgi?id=176685) 122 | keep_fnames: /Postman.*/, // Prevent discarding or mangling of function names like "Postman*" 123 | keep_classnames: /Postman.*/, // Prevent discarding or mangling of class names like "Postman*" 124 | format: { 125 | comments: false // Omit comments in the output 126 | // @note ascii_only is disabled since postman-sandbox v4 127 | // ascii_only: true, // Unicode characters in strings and regexps 128 | } 129 | }).then(({ code }) => { 130 | done(null, code); 131 | }).catch(done); 132 | }); 133 | } 134 | 135 | /** 136 | * Allows one to fetch a list of dependencies required by the bundle 137 | * 138 | * @param {Function} done - receives err, dependencies:Array 139 | */ 140 | listDependencies (done) { 141 | const dependencies = [], 142 | addPackageToDependencies = function (pkg) { 143 | dependencies.push(pkg.name); 144 | }; 145 | 146 | this.bundler.on('package', addPackageToDependencies); 147 | 148 | this.compile((err) => { 149 | this.bundler.removeListener('package', addPackageToDependencies); 150 | 151 | return done(err, _.uniq(dependencies).sort()); 152 | }); 153 | } 154 | 155 | static load (options) { 156 | return new Bundle(options); 157 | } 158 | } 159 | 160 | module.exports = Bundle; 161 | -------------------------------------------------------------------------------- /lib/environment.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bundler: { 3 | noParse: ['jquery'] 4 | }, 5 | require: { 6 | // builtins required by the libraries exposed for sandbox users 7 | events: { preferBuiltin: true, glob: true }, 8 | path: { preferBuiltin: true, glob: true }, 9 | timers: { preferBuiltin: true }, 10 | _process: { preferBuiltin: true, glob: true }, 11 | util: { preferBuiltin: true, glob: true }, 12 | stream: { preferBuiltin: true, glob: true }, 13 | string_decoder: { preferBuiltin: true, glob: true }, 14 | buffer: { 15 | resolve: '../vendor/buffer/index.js', 16 | resolveBrowser: '../vendor/buffer/index.browser.js', 17 | expose: 'buffer', 18 | glob: true 19 | }, 20 | url: { preferBuiltin: true, glob: true }, 21 | punycode: { preferBuiltin: true, glob: true }, 22 | querystring: { preferBuiltin: true, glob: true }, 23 | fs: { preferBuiltin: true }, 24 | os: { preferBuiltin: true }, 25 | 'liquid-json': { expose: 'json', glob: true }, 26 | 'crypto-js': { glob: true }, 27 | atob: { resolve: '../vendor/atob.js', expose: 'atob', glob: true }, 28 | btoa: { resolve: '../vendor/btoa.js', expose: 'btoa', glob: true }, 29 | ajv: { glob: true }, 30 | tv4: { glob: true }, 31 | xml2js: { glob: true }, 32 | backbone: { glob: true }, 33 | cheerio: { glob: true }, 34 | assert: { resolve: 'assert/build/assert.js', expose: 'assert', glob: true }, 35 | // expose has been set like this to make it easier to accommodate the async API later 36 | 'csv-parse': { resolve: 'csv-parse/lib/sync', expose: 'csv-parse/lib/sync', glob: true }, 37 | 'postman-collection': { expose: 'postman-collection', glob: true }, 38 | uuid: { resolve: '../vendor/uuid', expose: 'uuid', glob: true }, 39 | chai: { glob: true }, 40 | moment: { resolve: 'moment/min/moment.min', expose: 'moment', glob: true }, 41 | lodash: { glob: true } 42 | }, 43 | ignore: ['aws4', 'hawk', 'node-oauth1'], 44 | files: { 45 | '../vendor/sugar': true, // sugar is tricky as module. hence included as vendor. 46 | '../sandbox': true 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | bootcode = require('./bootcode'), 3 | PostmanSandbox = require('./postman-sandbox'), 4 | PostmanSandboxFleet = require('./postman-sandbox-fleet'); 5 | 6 | module.exports = { 7 | /** 8 | * Creates a new instance of sandbox from the options that have been provided 9 | * 10 | * @param {Object=} [options] - 11 | * @param {Function} callback - 12 | */ 13 | createContext (options, callback) { 14 | if (_.isFunction(options) && !callback) { 15 | callback = options; 16 | options = {}; 17 | } 18 | 19 | options = _.clone(options); 20 | bootcode((err, code) => { 21 | if (err) { return callback(err); } 22 | if (!code) { return callback(new Error('sandbox: bootcode missing!')); } 23 | 24 | options.bootCode = code; // assign the code in options 25 | 26 | new PostmanSandbox().initialize({}, options, callback); 27 | }); 28 | }, 29 | 30 | /** 31 | * Creates a new instance of sandbox fleet based on the give templates 32 | * 33 | * @param {PostmanSandboxFleet.templateRegistry} registry - 34 | * @param {PostmanSandboxFleet.initOptions} [initOptions] - 35 | * @param {PostmanSandboxFleet.connectOptions} [connectOptions] - 36 | * @param {Function} callback - 37 | */ 38 | createContextFleet (registry, initOptions, connectOptions, callback) { 39 | if (typeof initOptions === 'function') { 40 | callback = initOptions; 41 | initOptions = null; 42 | connectOptions = null; 43 | } 44 | else if (typeof connectOptions === 'function') { 45 | callback = connectOptions; 46 | connectOptions = null; 47 | } 48 | 49 | if (typeof callback !== 'function') { 50 | return callback(new TypeError('sandbox: callback must be a function')); 51 | } 52 | 53 | if (!_.isObject(initOptions)) { 54 | initOptions = {}; 55 | } 56 | 57 | if (!_.isObject(connectOptions)) { 58 | connectOptions = {}; 59 | } 60 | 61 | connectOptions = _.clone(connectOptions); 62 | 63 | bootcode((err, code) => { 64 | if (err) { return callback(err); } 65 | if (!code) { return callback(new Error('sandbox: bootcode missing!')); } 66 | 67 | connectOptions.bootCode = code; 68 | 69 | try { 70 | const sandboxFleet = new PostmanSandboxFleet(registry, initOptions, connectOptions); 71 | 72 | return callback(null, sandboxFleet); 73 | } 74 | catch (err) { 75 | return callback(err); 76 | } 77 | }); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /lib/postman-sandbox-fleet.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | PostmanSandbox = require('./postman-sandbox'); 3 | 4 | /** 5 | * @typedef {Object} SDK 6 | * 7 | * @property {Class} request 8 | * @property {Class} response 9 | */ 10 | 11 | /** 12 | * Returns the SDK classes for `request` and `response` 13 | * 14 | * @typedef {Function} initializeExecution 15 | * 16 | * @param {String} - Execution [event's]{@link http://www.postmanlabs.com/postman-collection/Event} `listen` property 17 | * @param {Object} - Current execution context 18 | * @return {SDK} - Request and response instances based on context. 19 | */ 20 | 21 | /** 22 | * ChaiJS plugin function to be passed as an argument to `chai.use` 23 | * 24 | * @see {@link https://www.chaijs.com/guide/plugins/} for details 25 | * 26 | * @typedef {Function} chaiJSPlugin 27 | */ 28 | 29 | /** 30 | * Template to govern the behavior of individual 31 | * sandbox instances in the fleet. It should be a 32 | * valid node.js code which on execution should 33 | * return two function using `module.exports`: 34 | * - {@link initializeExecution} 35 | * - {@link chaiJSPlugin} 36 | * 37 | * @typedef {String} template 38 | */ 39 | 40 | /** 41 | * Registry of templates. 42 | * Each template should be mapped to unique name 43 | * which then could be used as identifier to retrieve 44 | * the sandbox instance initialized by that template. 45 | * 46 | * @typedef {Object.} templateRegistry 47 | */ 48 | 49 | /** 50 | * Options to configure PostmanSandboxFleet initialization 51 | * 52 | * @typedef initOptions 53 | * 54 | * @property {Boolean} [disableLegacyAPIs=true] - Disable legacy pm interface APIs 55 | * @property {Array.} [disabledAPIs] - List of pm APIs to disable 56 | */ 57 | 58 | /** 59 | * Options to configure UniversalVM connection 60 | * for each individual PostmanSandbox instance 61 | * 62 | * @typedef connectOptions 63 | * 64 | * @property {String} [bootCode] Code to be executed inside a UVM on boot 65 | * @property {Number} [timeout] - 66 | * @property {Boolean} [debug] - Enable console logs inside UVM 67 | */ 68 | 69 | /** 70 | * Class representing a fleet of PostmanSandboxes, 71 | * allowing orchestration of different variants 72 | * of sandboxes governed by templates. 73 | */ 74 | class PostmanSandboxFleet { 75 | /** 76 | * Create a fleet of sandboxes 77 | * 78 | * @param {templateRegistry} registry - 79 | * @param {initOptions} initOptions - 80 | * @param {connectOptions} connectOptions - 81 | */ 82 | constructor (registry, initOptions, connectOptions) { 83 | this.fleet = new Map(); 84 | 85 | if (!_.isObject(registry)) { 86 | throw new TypeError('sandbox-fleet: template registry must be an object'); 87 | } 88 | 89 | this.templateRegistry = registry; 90 | 91 | this.initOptions = initOptions; 92 | this.connectOptions = connectOptions; 93 | } 94 | 95 | /** 96 | * Check if a template is registered with a given name 97 | * 98 | * @param {string} templateName - 99 | * @returns {boolean} 100 | */ 101 | isRegistered (templateName) { 102 | return _.has(this.templateRegistry, templateName); 103 | } 104 | 105 | /** 106 | * Register a new template 107 | * 108 | * @param {string} templateName - 109 | * @param {template} template - 110 | */ 111 | register (templateName, template) { 112 | if (typeof templateName !== 'string') { 113 | throw new TypeError('sandbox-fleet: template name must be a string'); 114 | } 115 | 116 | if (typeof template !== 'string') { 117 | throw new TypeError('sandbox-fleet: template must be a string'); 118 | } 119 | 120 | if (this.isRegistered(templateName)) { 121 | throw new Error(`sandbox-fleet: template already registered for name ${templateName}`); 122 | } 123 | 124 | this.templateRegistry[templateName] = template; 125 | } 126 | 127 | /** 128 | * Returns sandbox instance for the given template name 129 | * 130 | * @param {String} templateName - 131 | * @param {Function} callback - 132 | * @returns {PostmanSandbox} 133 | */ 134 | getContext (templateName, callback) { 135 | if (typeof callback !== 'function') { 136 | return callback(new TypeError('sandbox-fleet: callback must be a function')); 137 | } 138 | 139 | if (typeof templateName !== 'string') { 140 | return callback(new TypeError('sandbox-fleet: template name must be a string')); 141 | } 142 | 143 | if (this.fleet.has(templateName)) { 144 | return callback(null, this.fleet.get(templateName)); 145 | } 146 | 147 | if (!this.isRegistered(templateName)) { 148 | return callback(new Error(`sandbox-fleet: template not found for name ${templateName}`)); 149 | } 150 | 151 | const template = this.templateRegistry[templateName]; 152 | 153 | if (typeof template !== 'string') { 154 | return callback(new Error(`sandbox-fleet: invalid template for name ${templateName}`)); 155 | } 156 | 157 | new PostmanSandbox().initialize({ 158 | disableLegacyAPIs: true, 159 | ...this.initOptions, 160 | template: template 161 | }, this.connectOptions, (err, context) => { 162 | if (err) { 163 | return callback(err); 164 | } 165 | 166 | // Trapping call to `context.dispose` to do the required cleanup in the fleet 167 | const proxiedContext = new Proxy(context, { 168 | get: (target, prop, receiver) => { 169 | if (prop === 'dispose') { 170 | return (...args) => { 171 | for (const context of this.fleet.values()) { 172 | if (proxiedContext === context) { 173 | this.fleet.delete(templateName); 174 | 175 | return target[prop](...args); 176 | } 177 | } 178 | }; 179 | } 180 | 181 | return Reflect.get(target, prop, receiver); 182 | } 183 | }); 184 | 185 | this.fleet.set(templateName, proxiedContext); 186 | callback(null, proxiedContext); 187 | }); 188 | } 189 | 190 | /** 191 | * Dispose off all initialized sandbox instances from the fleet 192 | * 193 | * @returns {void} 194 | */ 195 | disposeAll () { 196 | this.fleet.forEach((context, templateName) => { 197 | context.dispose(); 198 | 199 | this.fleet.delete(templateName); 200 | }); 201 | } 202 | } 203 | 204 | module.exports = PostmanSandboxFleet; 205 | -------------------------------------------------------------------------------- /lib/sandbox/console.js: -------------------------------------------------------------------------------- 1 | var teleportJS = require('teleport-javascript'), 2 | 3 | arrayProtoSlice = Array.prototype.slice, 4 | 5 | /** 6 | * @constant 7 | * @type {String} 8 | */ 9 | CONSOLE_EVENT = 'execution.console', 10 | 11 | /** 12 | * List of functions that we expect and create for console 13 | * 14 | * @constant 15 | * @type {String[]} 16 | */ 17 | logLevels = ['log', 'warn', 'debug', 'info', 'error', 'clear']; 18 | 19 | /** 20 | * Replacer to be used with teleport-javascript to handle cases which are not 21 | * handled by it. 22 | * 23 | * @param {String} key - Key of the property to replace 24 | * @param {Any} value - Value of property to replace 25 | * @return {Any} Replaced value 26 | */ 27 | function replacer (key, value) { 28 | if (typeof value === 'function') { 29 | const fnType = (value.constructor && value.constructor.name) ? 30 | value.constructor.name : 'Function'; 31 | 32 | return value.name ? `[${fnType}: ${value.name}]` : `[${fnType}]`; 33 | } 34 | 35 | if (value instanceof WeakMap) { 36 | return '[WeakMap]'; 37 | } 38 | else if (value instanceof WeakSet) { 39 | return '[WeakSet]'; 40 | } 41 | else if (value instanceof ArrayBuffer) { 42 | return `[ArrayBuffer { byteLength: ${value.byteLength} }]`; 43 | } 44 | 45 | return value; 46 | } 47 | 48 | function PostmanConsole (emitter, cursor, originalConsole) { 49 | const dispatch = function (level) { // create a dispatch function that emits events 50 | const args = arrayProtoSlice.call(arguments, 1); 51 | 52 | if (originalConsole) { 53 | // eslint-disable-next-line prefer-spread 54 | originalConsole[level].apply(originalConsole, args); 55 | } 56 | 57 | emitter.dispatch(CONSOLE_EVENT, cursor, level, teleportJS.stringify(args, replacer)); 58 | }; 59 | 60 | // setup variants of the logger based on log levels 61 | logLevels.forEach((name) => { 62 | this[name] = dispatch.bind(emitter, name); 63 | }); 64 | } 65 | 66 | module.exports = PostmanConsole; 67 | -------------------------------------------------------------------------------- /lib/sandbox/cookie-store.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | 3 | Store = require('@postman/tough-cookie').Store, 4 | Cookie = require('@postman/tough-cookie').Cookie, 5 | 6 | EXECUTION_EVENT_BASE = 'execution.cookies.', 7 | EVENT_STORE_ACTION = 'store', 8 | STORE_METHODS = [ 9 | 'findCookie', 'findCookies', 'putCookie', 'updateCookie', 10 | 'removeCookie', 'removeCookies', 'removeAllCookies', 'getAllCookies' 11 | ], 12 | FUNCTION = 'function', 13 | 14 | arrayProtoSlice = Array.prototype.slice; 15 | 16 | class PostmanCookieStore extends Store { 17 | constructor (id, emitter, timers) { 18 | super(); 19 | 20 | this.id = id; // execution identifier 21 | this.emitter = emitter; 22 | this.timers = timers; 23 | } 24 | } 25 | 26 | // Disable CookieJar's *Sync APIs 27 | PostmanCookieStore.prototype.synchronous = false; 28 | 29 | // attach a common handler to all store methods 30 | STORE_METHODS.forEach(function (method) { 31 | PostmanCookieStore.prototype[method] = function () { 32 | const eventName = EXECUTION_EVENT_BASE + this.id; 33 | let args, 34 | eventId, 35 | callback; 36 | 37 | // fetch all the arguments passed to the method 38 | args = arrayProtoSlice.call(arguments); 39 | 40 | // adjust arguments length based on Store's prototype method 41 | // eslint-disable-next-line lodash/path-style 42 | args.length = _.get(Store.prototype, [method, 'length'], 0); 43 | 44 | // move callback/last argument out of arguments 45 | // this will be called when timer clears the event 46 | callback = args.pop(); 47 | 48 | // set event for the callback 49 | eventId = this.timers.setEvent(function (err, cookies) { 50 | if (typeof callback !== FUNCTION) { 51 | throw new TypeError('callback is not a function'); 52 | } 53 | 54 | // methods: putCookie, updateCookie, removeCookie, removeCookies, 55 | // removeAllCookies 56 | // or, onError 57 | if (err || !cookies) { 58 | return callback(err); 59 | } 60 | 61 | // methods: findCookies, getAllCookies 62 | if (Array.isArray(cookies)) { 63 | return callback(err, cookies.map(function (cookie) { 64 | return Cookie.fromJSON(cookie); // serialize cookie object 65 | })); 66 | } 67 | 68 | // method: findCookie 69 | callback(err, Cookie.fromJSON(cookies)); 70 | }); 71 | 72 | // @note: artificial timeout is added to fix the timers bug when sandbox 73 | // is executed in the same process (Node.js VM) and the event is 74 | // processed synchronously by the in-memory cookie store or cache. 75 | // This timeout ensures that the event is processed asynchronously 76 | // without blocking the rest of the script execution. 77 | // Refer: https://github.com/postmanlabs/postman-app-support/issues/11064 78 | this.timers.wrapped.setTimeout(() => { 79 | // finally, dispatch event over the bridge 80 | this.emitter.dispatch(eventName, eventId, EVENT_STORE_ACTION, method, args); 81 | }); 82 | }; 83 | }); 84 | 85 | module.exports = PostmanCookieStore; 86 | -------------------------------------------------------------------------------- /lib/sandbox/execute-context.js: -------------------------------------------------------------------------------- 1 | const { isNonLegacySandbox } = require('./non-legacy-codemarkers'); 2 | const _ = require('lodash'), 3 | legacy = require('./postman-legacy-interface'); 4 | 5 | module.exports = function (scope, code, execution, console, timers, pmapi, onAssertion, options) { 6 | // if there is no code, then no point bubbling anything up 7 | if (!(code && _.isString(code))) { 8 | return timers.terminate(); 9 | } 10 | 11 | // start by resetting the scope 12 | scope.reset(); 13 | 14 | if (isNonLegacySandbox(code) || options.disableLegacyAPIs) { 15 | // ensure any previously added global variables from legacy are torn down. side-effect is that if user 16 | // explicitly created global variables with same name as legacy ones, they will be torn down too! 17 | // for that reason, the setup function tags the scope and avoids tearing down an scope that was never setup 18 | legacy.teardown(scope); 19 | } 20 | else { 21 | // prepare legacy environment, which adds a tonne of global variables 22 | legacy.setup(scope, execution, console); 23 | } 24 | 25 | // prepare the scope's environment variables 26 | scope.import({ 27 | Buffer: require('buffer').Buffer, 28 | // TODO: Remove this once it is added to Uniscope (node>=v20) 29 | File: require('buffer').File, 30 | // forward console 31 | console: console, 32 | // forward pm-api instance 33 | /** 34 | * The pm object encloses all information pertaining to the script being executed and 35 | * allows one to access a copy of the request being sent or the response received. 36 | * It also allows one to get and set environment and global variables. 37 | * 38 | * @type {Postman} 39 | */ 40 | pm: pmapi, 41 | // import the timers 42 | setTimeout: timers.setTimeout, 43 | setInterval: timers.setInterval, 44 | setImmediate: timers.setImmediate, 45 | clearTimeout: timers.clearTimeout, 46 | clearInterval: timers.clearInterval, 47 | clearImmediate: timers.clearImmediate 48 | }); 49 | 50 | scope.exec(code, { async: true }, function (err) { 51 | // we check if the execution went async by determining the timer queue length at this time 52 | execution.return.async = (timers.queueLength() > 0); 53 | 54 | // call this hook to perform any post script execution tasks 55 | legacy.finish(scope, pmapi, onAssertion); 56 | 57 | function complete () { 58 | // if timers are running, we do not need to proceed with any logic of completing execution. instead we wait 59 | // for timer completion callback to fire 60 | if (execution.return.async) { 61 | return err && timers.error(err); // but if we had error, we pass it to async error handler 62 | } 63 | 64 | // at this stage, the script is a synchronous script, we simply forward whatever has come our way 65 | timers.terminate(err); 66 | } 67 | 68 | timers.wrapped.setImmediate(complete); 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /lib/sandbox/execution.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'), 2 | sdk = require('postman-collection'), 3 | 4 | PROPERTY = { 5 | REQUEST: 'request', 6 | SCRIPT: 'script', 7 | DATA: 'data', 8 | COOKIES: 'cookies', 9 | RESPONSE: 'response', 10 | MESSAGE: 'message' 11 | }, 12 | 13 | TARGETS_WITH_REQUEST = { 14 | test: true, 15 | prerequest: true 16 | }, 17 | 18 | TARGETS_WITH_RESPONSE = { 19 | test: true 20 | }, 21 | 22 | CONTEXT_VARIABLE_SCOPES = ['_variables', 'environment', 'collectionVariables', 'globals'], 23 | 24 | trackingOptions = { autoCompact: true }; 25 | 26 | class Execution { 27 | constructor (id, event, context, options) { 28 | this.id = id; 29 | this.target = event.listen || PROPERTY.SCRIPT; 30 | this.legacy = options.legacy || {}; 31 | this.cursor = _.isObject(options.cursor) ? options.cursor : {}; 32 | 33 | this.data = _.get(context, PROPERTY.DATA, {}); 34 | this.cookies = new sdk.CookieList(null, context.cookies); 35 | 36 | CONTEXT_VARIABLE_SCOPES.forEach((variableScope) => { 37 | // normalize variable scope instances 38 | this[variableScope] = sdk.VariableScope.isVariableScope(context[variableScope]) ? 39 | context[variableScope] : new sdk.VariableScope(context[variableScope]); 40 | 41 | // enable change tracking 42 | this[variableScope].enableTracking(trackingOptions); 43 | }); 44 | 45 | if (options.initializeExecution) { 46 | const { request, response, message } = options.initializeExecution(this.target, context) || {}; 47 | 48 | this.request = request; 49 | this.response = response; 50 | this.message = message; 51 | } 52 | else { 53 | if (TARGETS_WITH_REQUEST[this.target] || _.has(context, PROPERTY.REQUEST)) { 54 | /** 55 | * @note: 56 | * this reference is passed on as `pm.request`, pm api adds helper functions like `to` to `pm.request` 57 | * sandbox overrides collection Request.prototype.toJSON to remove helpers before toJSON, see `purse.js` 58 | */ 59 | this.request = sdk.Request.isRequest(context.request) ? 60 | context.request : new sdk.Request(context.request); 61 | } 62 | 63 | if (TARGETS_WITH_RESPONSE[this.target] || _.has(context, PROPERTY.RESPONSE)) { 64 | /** 65 | * @note: 66 | * this reference is passed on as `pm.response`, pm api adds helper functions like `to` to `pm.response` 67 | * sandbox overrides collection Response.prototype.toJSON to remove helpers before toJSON, 68 | * see `purse.js` 69 | */ 70 | this.response = sdk.Response.isResponse(context.response) ? 71 | context.response : new sdk.Response(context.response); 72 | } 73 | } 74 | 75 | /** 76 | * @typedef {Object} Return 77 | * 78 | * @property {Boolean} async - true if the executed script was async, false otherwise 79 | * @property {Visualizer} visualizer - visualizer data 80 | * @property {*} nextRequest - next request to send 81 | */ 82 | this.return = {}; 83 | } 84 | 85 | toJSON () { 86 | return _.mapValues(this, function (value) { 87 | // if there is no specific json serialiser, return the raw value 88 | if (!_.isFunction(value && value.toJSON)) { 89 | return value; 90 | } 91 | 92 | return value.toJSON(); 93 | }); 94 | } 95 | } 96 | 97 | module.exports = Execution; 98 | -------------------------------------------------------------------------------- /lib/sandbox/index.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * @license Copyright 2016 Postdot Technologies, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 11 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and limitations under the License. 13 | * 14 | * This file is the Postman scripting sandbox's bootstrap code and would during module usage be exported as part of npm 15 | * cache and deployed for ease of use and performance improvements. 16 | * 17 | * @note 18 | * This file runs within Node and browser sandboxes and standard node aspects may not 100% apply 19 | */ 20 | /* global bridge */ 21 | 22 | // Setup Timerz before we delete the global timers 23 | require('./timers'); 24 | 25 | // Require buffer to make sure it's available in the sandbox 26 | // Browserify statically analyses the usage of buffers and only 27 | // injects Buffer into the scope if it's used. `Buffer` injected 28 | // by browserify is part of the functional scope and does not get 29 | // deleted when we mutate the global scope below. 30 | require('buffer'); 31 | 32 | // Although we execute the user code in a well-defined scope using the uniscope 33 | // module but still to cutoff the reference to the globally available properties 34 | // we sanitize the global scope by deleting the forbidden properties in this UVM 35 | // and create a secure sandboxed environment. 36 | // @note this is executed at the very beginning of the sandbox code to make sure 37 | // non of the dependency can keep a reference to a global property. 38 | // @note since this mutates the global scope, it's possible to mess-up as we 39 | // update our dependencies. 40 | (function recreatingTheUniverse () { 41 | var contextObject = this, 42 | // 1. allow all the uniscope allowed globals 43 | allowedGlobals = require('uniscope/lib/allowed-globals').concat([ 44 | // 2. allow properties which can be controlled/ignored using uniscope 45 | 'require', 'eval', 'console', 46 | // 3. allow uvm internals because these will be cleared by uvm itself at the end. 47 | // make sure any new property added in uvm firmware is allowed here as well. 48 | 'bridge', '__uvm_emit', '__uvm_setTimeout' 49 | ]), 50 | deleteProperty = function (key) { 51 | // directly delete the property without setting it to `null` or `undefined` 52 | // because a few properties in browser context breaks the sandbox. 53 | // @note non-configurable keys are not deleted. 54 | // eslint-disable-next-line lodash/prefer-includes 55 | allowedGlobals.indexOf(key) === -1 && delete contextObject[key]; 56 | }; 57 | 58 | do { 59 | // delete all forbidden properties (including non-enumerable) 60 | Object.getOwnPropertyNames(contextObject).forEach(deleteProperty); 61 | // keep looking through the prototype chain until we reach the Object prototype 62 | // @note this deletes the constructor as well to make sure one can't recreate the same scope 63 | contextObject = Object.getPrototypeOf(contextObject); 64 | } while (Object.getPrototypeOf(contextObject) !== null); 65 | 66 | // define custom Error.prepareStackTrace 67 | Object.defineProperty(Error, 'prepareStackTrace', { 68 | value: function (error, structuredStackTrace) { 69 | let errorString = `Error: ${error && error.message}`; 70 | 71 | for (let i = 0; i < (structuredStackTrace && structuredStackTrace.length); i++) { 72 | errorString += `\n at ${structuredStackTrace[i]}`; 73 | } 74 | 75 | return errorString; 76 | }, 77 | configurable: false, 78 | enumerable: false, 79 | writable: false 80 | }); 81 | }()); 82 | 83 | // do include json purse 84 | require('./purse'); 85 | 86 | // setup the ping-pong and execute routines 87 | bridge.on('ping', require('./ping').listener('pong')); 88 | 89 | // initialize execution 90 | require('./execute')(bridge, { 91 | console: (typeof console !== 'undefined' ? console : null), 92 | window: (typeof window !== 'undefined' ? window : null) 93 | }); 94 | 95 | // We don't need direct access to the global bridge once it's part of execution closure. 96 | // eslint-disable-next-line no-global-assign, no-implicit-globals, no-delete-var 97 | bridge = undefined; delete bridge; 98 | -------------------------------------------------------------------------------- /lib/sandbox/non-legacy-codemarkers.js: -------------------------------------------------------------------------------- 1 | const NONLEGACY_SANDBOX_MARKERS = { 2 | '"use sandbox2";': true, 3 | '\'use sandbox2\';': true 4 | }; 5 | 6 | module.exports = { 7 | isNonLegacySandbox (code) { 8 | return NONLEGACY_SANDBOX_MARKERS[code.substr(0, 15)]; 9 | }, 10 | 11 | getNonLegacyCodeMarker () { 12 | return Object.keys(NONLEGACY_SANDBOX_MARKERS)[0]; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /lib/sandbox/ping.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | listener (pong) { 3 | return function (payload) { 4 | this.dispatch(pong, payload); 5 | }; 6 | } 7 | }; 8 | 9 | -------------------------------------------------------------------------------- /lib/sandbox/pm-require.js: -------------------------------------------------------------------------------- 1 | const { LEGACY_GLOBS } = require('./postman-legacy-interface'), 2 | 3 | MODULE_KEY = '__module_obj', // why not use `module`? 4 | MODULE_WRAPPER = [ 5 | '((exports, module) => {\n', 6 | `\n})(${MODULE_KEY}.exports, ${MODULE_KEY});` 7 | ], 8 | PACKAGE_TYPE_REGEX = /^[^:]+:[^:]+$/, 9 | 10 | // This is used to determine if the package is external or internal. 11 | // External packages are those that are not part of the Postman's 12 | // Package Library and follow the format `registry:package`. 13 | isExternalPackage = (name) => { return PACKAGE_TYPE_REGEX.test(name); }; 14 | 15 | /** 16 | * Cache of all files that are available to be required. 17 | * 18 | * @typedef {Object.} FileCache 19 | */ 20 | 21 | class PostmanRequireStore { 22 | /** 23 | * @param {FileCache} fileCache - fileCache 24 | */ 25 | constructor (fileCache) { 26 | if (!fileCache) { 27 | throw new Error('File cache is required'); 28 | } 29 | 30 | this.fileCache = fileCache; 31 | } 32 | 33 | /** 34 | * Check if the file is available in the cache. 35 | * 36 | * @param {string} path - path 37 | * @returns {boolean} 38 | */ 39 | hasFile (path) { 40 | return Boolean(this.getFile(path)); 41 | } 42 | 43 | /** 44 | * Get the file from the cache. 45 | * 46 | * @param {string} path - path 47 | * @returns {Object|undefined} - file 48 | */ 49 | getFile (path) { 50 | return this.fileCache[path]; 51 | } 52 | 53 | /** 54 | * Get the resolved path for the file. 55 | * 56 | * @param {string} path - path 57 | * @returns {string|undefined} - resolved path 58 | */ 59 | getResolvedPath (path) { 60 | if (this.hasFile(path)) { 61 | return path; 62 | } 63 | } 64 | 65 | /** 66 | * Get the file data. 67 | * 68 | * @param {string} path - path 69 | * @returns {string|undefined} 70 | */ 71 | getFileData (path) { 72 | return this.hasFile(path) && this.getFile(path).data; 73 | } 74 | 75 | /** 76 | * Check if the file has an error. 77 | * 78 | * @param {string} path - path 79 | * @returns {boolean} 80 | */ 81 | hasError (path) { 82 | return this.hasFile(path) && Boolean(this.getFile(path).error); 83 | } 84 | 85 | /** 86 | * Get the file error. 87 | * 88 | * @param {string} path - path 89 | * @returns {string|undefined} 90 | */ 91 | getFileError (path) { 92 | return this.hasError(path) && this.getFile(path).error; 93 | } 94 | } 95 | 96 | /** 97 | * @param {FileCache} fileCache - fileCache 98 | * @param {Object} scope - scope 99 | * @returns {Function} - postmanRequire 100 | * @example 101 | * const fileCache = { 102 | * 'path/to/file.js': { 103 | * data: 'module.exports = { foo: "bar" };' 104 | * } 105 | * }; 106 | * 107 | * const postmanRequire = createPostmanRequire(fileCache, scope); 108 | * 109 | * const module = postmanRequire('path/to/file.js'); 110 | * console.log(module.foo); // bar 111 | */ 112 | function createPostmanRequire (fileCache, scope) { 113 | const store = new PostmanRequireStore(fileCache || {}), 114 | cache = {}; 115 | 116 | /** 117 | * @param {string} name - name 118 | * @returns {any} - module 119 | */ 120 | function postmanRequire (name) { 121 | const path = store.getResolvedPath(name); 122 | 123 | if (!path) { 124 | // Error should contain the name exactly as the user specified, 125 | // and not the resolved path. 126 | throw new Error(`Cannot find package '${name}'`); 127 | } 128 | 129 | if (store.hasError(path)) { 130 | throw new Error(`Error while loading package '${name}': ${store.getFileError(path)}`); 131 | } 132 | 133 | // Any module should not be evaluated twice, so we use it from the 134 | // cache. If there's a circular dependency, the partially evaluated 135 | // module will be returned from the cache. 136 | if (cache[path]) { 137 | // Always use the resolved path as the ID of the module. This 138 | // ensures that relative paths are handled correctly. 139 | return cache[path].exports; 140 | } 141 | 142 | /* eslint-disable-next-line one-var */ 143 | const file = store.getFileData(path), 144 | moduleObj = { 145 | id: path, 146 | exports: {} 147 | }; 148 | 149 | // Add to cache before executing. This ensures that any dependency 150 | // that tries to import it's parent/ancestor gets the cached 151 | // version and not end up in infinite loop. 152 | cache[moduleObj.id] = moduleObj; 153 | 154 | /* eslint-disable-next-line one-var */ 155 | const wrappedModule = MODULE_WRAPPER[0] + file + MODULE_WRAPPER[1]; 156 | 157 | scope.import({ 158 | [MODULE_KEY]: moduleObj 159 | }); 160 | 161 | // Note: We're executing the code in the same scope as the one 162 | // which called the `pm.require` function. This is because we want 163 | // to share the global scope across all the required modules. Any 164 | // locals are available inside the required modules and any locals 165 | // created inside the required modules are available to the parent. 166 | // 167 | // Why `async` = true? 168 | // - We want to allow execution of async code like setTimeout etc. 169 | scope.exec(wrappedModule, { 170 | async: true, 171 | block: isExternalPackage(name) ? 172 | LEGACY_GLOBS.concat('pm') : 173 | LEGACY_GLOBS 174 | }, (err) => { 175 | // Bubble up the error to be caught as execution error 176 | if (err) { 177 | throw new Error(`Error in package '${name}': ${err.message ? err.message : err}`); 178 | } 179 | }); 180 | 181 | scope.unset(MODULE_KEY); 182 | 183 | return moduleObj.exports; 184 | } 185 | 186 | return postmanRequire; 187 | } 188 | 189 | module.exports = createPostmanRequire; 190 | -------------------------------------------------------------------------------- /lib/sandbox/pmapi-setup-runner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * This module externally sets up the test runner on pm api. Essentially, it does not know the insides of pm-api and 5 | * does the job completely from outside with minimal external dependency 6 | */ 7 | const FUNCTION = 'function'; 8 | 9 | /** 10 | * @module {PMAPI~setupTestRunner} 11 | * @private 12 | * 13 | * @param {PMAPI} pm - an instance of PM API that it needs 14 | * @param {Function} onAssertionComplete - is the trigger function that is called every time a test is executed and it 15 | * receives the AssertionInfo object outlining details of the assertion 16 | */ 17 | module.exports = function (pm, onAssertionComplete) { 18 | var assertionIndex = 0, 19 | 20 | /** 21 | * Returns an object that represents data coming out of an assertion. 22 | * 23 | * @note This is put in a function since this needs to be done from a number of place and having a single 24 | * function reduces the chance of bugs 25 | * 26 | * @param {String} name - 27 | * @param {Boolean} skipped - 28 | * 29 | * @returns {PMAPI~AssertionInfo} 30 | */ 31 | getAssertionObject = function (name, skipped) { 32 | /** 33 | * @typeDef {AssertionInfo} 34 | * @private 35 | */ 36 | return { 37 | name: String(name), 38 | async: false, 39 | skipped: Boolean(skipped), 40 | passed: true, 41 | error: null, 42 | index: assertionIndex++ // increment the assertion counter (do it before asserting) 43 | }; 44 | }, 45 | 46 | /** 47 | * Simple function to mark an assertion as failed 48 | * 49 | * @private 50 | * 51 | * @note This is put in a function since this needs to be done from a number of place and having a single 52 | * function reduces the chance of bugs 53 | * 54 | * @param {Object} assertionData - 55 | * @param {*} err - 56 | */ 57 | markAssertionAsFailure = function (assertionData, err) { 58 | assertionData.error = err; 59 | assertionData.passed = false; 60 | }; 61 | 62 | /** 63 | * @param {String} name - 64 | * @param {Function} assert - 65 | * @chainable 66 | */ 67 | pm.test = function (name, assert) { 68 | var assertionData = getAssertionObject(name, false); 69 | 70 | // if there is no assertion function, we simply move on 71 | if (typeof assert !== FUNCTION) { 72 | onAssertionComplete(assertionData); 73 | 74 | return pm; 75 | } 76 | 77 | // if a callback function was sent, then we know that the test is asynchronous 78 | if (assert.length) { 79 | try { 80 | assertionData.async = true; // flag that this was an async test (would be useful later) 81 | 82 | // we execute assertion, but pass it a completion function, which, in turn, raises the completion 83 | // event. we do not need to worry about timers here since we are assuming that some timer within the 84 | // sandbox had actually been the source of async calls and would take care of this 85 | assert(function (err) { 86 | // at first we double check that no synchronous error has happened from the catch block below 87 | if (assertionData.error && assertionData.passed === false) { 88 | return; 89 | } 90 | 91 | // user triggered a failure of the assertion, so we mark it the same 92 | if (err) { 93 | markAssertionAsFailure(assertionData, err); 94 | } 95 | 96 | onAssertionComplete(assertionData); 97 | }); 98 | } 99 | // in case a synchronous error occurs in the the async assertion, we still bail out. 100 | catch (e) { 101 | markAssertionAsFailure(assertionData, e); 102 | onAssertionComplete(assertionData); 103 | } 104 | } 105 | // if the assertion function does not expect a callback, we synchronously execute the same 106 | else { 107 | try { assert(); } 108 | catch (e) { 109 | markAssertionAsFailure(assertionData, e); 110 | } 111 | 112 | onAssertionComplete(assertionData); 113 | } 114 | 115 | return pm; // make it chainable 116 | }; 117 | 118 | /** 119 | * @param {String} name - 120 | * @chainable 121 | */ 122 | pm.test.skip = function (name) { 123 | // trigger the assertion events with skips 124 | onAssertionComplete(getAssertionObject(name, true)); 125 | 126 | return pm; // chainable 127 | }; 128 | 129 | /** 130 | * @returns {Number} 131 | */ 132 | pm.test.index = function () { 133 | return assertionIndex; 134 | }; 135 | }; 136 | -------------------------------------------------------------------------------- /lib/sandbox/purse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module adds `.toJSON` to prototypes of objects that does not behave well with JSON.stringify() This aides in 3 | * accurate transport of information between IPC 4 | * 5 | */ 6 | try { 7 | Error && (Error.prototype.toJSON = function () { // eslint-disable-line no-extend-native 8 | return { 9 | type: 'Error', 10 | name: this.name, 11 | message: this.message 12 | }; 13 | }); 14 | } 15 | catch (e) {} // eslint-disable-line no-empty 16 | 17 | const { Request, Response } = require('postman-collection'); 18 | 19 | /** 20 | * We override toJSON to not export additional helpers that sandbox adds to pm.request and pm.response. 21 | */ 22 | try { 23 | Request.prototype.toJSON = (function (superToJSON) { // eslint-disable-line no-extend-native 24 | return function () { 25 | var tmp = this.to, 26 | json; 27 | 28 | // remove properties added by sandbox before doing a toJSON 29 | delete this.to; 30 | json = superToJSON.apply(this, arguments); 31 | 32 | this.to = tmp; 33 | 34 | return json; 35 | }; 36 | }(Request.prototype.toJSON)); 37 | 38 | Response.prototype.toJSON = (function (superToJSON) { // eslint-disable-line no-extend-native 39 | return function () { 40 | var tmp = this.to, 41 | json; 42 | 43 | // remove properties added by sandbox before doing a toJSON 44 | delete this.to; 45 | json = superToJSON.apply(this, arguments); 46 | 47 | this.to = tmp; 48 | 49 | return json; 50 | }; 51 | }(Response.prototype.toJSON)); 52 | } 53 | catch (e) {} // eslint-disable-line no-empty 54 | -------------------------------------------------------------------------------- /lib/sandbox/vault.js: -------------------------------------------------------------------------------- 1 | class Vault { 2 | constructor (id, bridge, timers) { 3 | this._bridge = bridge; 4 | this._timers = timers; 5 | this._event = `execution.vault.${id}`; 6 | 7 | this._handler = (eventId, ...args) => { 8 | this._timers.clearEvent(eventId, ...args); 9 | }; 10 | 11 | this._bridge.on(this._event, this._handler); 12 | } 13 | 14 | exec (...args) { 15 | return new Promise((resolve, reject) => { 16 | const eventId = this._timers.setEvent((err, ...args) => { 17 | if (err) { 18 | return reject(err instanceof Error ? err : new Error(err.message || err)); 19 | } 20 | 21 | resolve(...args); 22 | }); 23 | 24 | this._bridge.dispatch(this._event, eventId, ...args); 25 | }); 26 | } 27 | 28 | dispose () { 29 | this._bridge.off(this._event, this._handler); 30 | } 31 | } 32 | 33 | const getVaultInterface = (vault) => { 34 | return { 35 | get: (key) => { 36 | return vault('get', key); 37 | }, 38 | 39 | set: (key, value) => { 40 | return vault('set', key, value); 41 | }, 42 | 43 | unset: (key) => { 44 | return vault('unset', key); 45 | } 46 | }; 47 | }; 48 | 49 | module.exports = { 50 | Vault, 51 | getVaultInterface 52 | }; 53 | -------------------------------------------------------------------------------- /lib/sandbox/xml2Json.js: -------------------------------------------------------------------------------- 1 | const xml2js = require('xml2js'), 2 | 3 | /** 4 | * @constant 5 | * @type {Object} 6 | */ 7 | xml2jsOptions = { 8 | explicitArray: false, 9 | // this ensures that it works in the sync sandbox we currently have in the app 10 | async: false, 11 | trim: true, 12 | mergeAttrs: false 13 | }; 14 | 15 | module.exports = function (string) { 16 | var JSON = {}; 17 | 18 | xml2js.parseString(string, xml2jsOptions, function (_, result) { // @todo - see who swallows the error 19 | JSON = result; 20 | }); 21 | 22 | return JSON; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/vendor/atob.js: -------------------------------------------------------------------------------- 1 | module.exports = require('buffer').atob; 2 | -------------------------------------------------------------------------------- /lib/vendor/btoa.js: -------------------------------------------------------------------------------- 1 | module.exports = require('buffer').btoa; 2 | -------------------------------------------------------------------------------- /lib/vendor/buffer/buffer.js: -------------------------------------------------------------------------------- 1 | const NOT_IMPLEMENTED = function () { 2 | throw new Error('Not implemented'); 3 | }; 4 | 5 | function getBufferModule (buffer) { 6 | return { 7 | Buffer: buffer.Buffer, 8 | SlowBuffer: buffer.SlowBuffer, 9 | INSPECT_MAX_BYTES: buffer.INSPECT_MAX_BYTES, 10 | kMaxLength: buffer.kMaxLength, 11 | kStringMaxLength: buffer.kStringMaxLength, 12 | constants: buffer.constants, 13 | File: buffer.File, 14 | Blob: buffer.Blob, 15 | atob: buffer.atob, 16 | btoa: buffer.btoa, 17 | isAscii: NOT_IMPLEMENTED, 18 | isUtf8: NOT_IMPLEMENTED, 19 | resolveObjectURL: NOT_IMPLEMENTED, 20 | transcode: NOT_IMPLEMENTED 21 | } 22 | } 23 | 24 | module.exports = getBufferModule; 25 | -------------------------------------------------------------------------------- /lib/vendor/buffer/index.browser.js: -------------------------------------------------------------------------------- 1 | const getBufferModule = require('./buffer'); 2 | const SpecificBuffer = require('./specific-buffer'); 3 | const buffer = require('buffer/'); 4 | 5 | // Using 32-bit implementation value from Node 6 | // https://github.com/nodejs/node/blob/main/deps/v8/include/v8-primitive.h#L126 7 | const K_STRING_MAX_LENGTH = (1 << 28) - 16; 8 | 9 | module.exports = getBufferModule({ 10 | ...buffer, 11 | Buffer: SpecificBuffer(buffer.Buffer), 12 | kStringMaxLength: K_STRING_MAX_LENGTH, 13 | constants: { 14 | MAX_LENGTH: buffer.kMaxLength, 15 | MAX_STRING_LENGTH: K_STRING_MAX_LENGTH 16 | }, 17 | File: File, 18 | Blob: Blob, 19 | atob: atob, 20 | btoa: btoa 21 | }); 22 | -------------------------------------------------------------------------------- /lib/vendor/buffer/index.js: -------------------------------------------------------------------------------- 1 | const getBufferModule = require('./buffer'); 2 | const SpecificBuffer = require('./specific-buffer'); 3 | const buffer = globalThis._nodeRequires.buffer; 4 | 5 | module.exports = getBufferModule({ 6 | ...buffer, 7 | Buffer: SpecificBuffer(buffer.Buffer), 8 | }); 9 | -------------------------------------------------------------------------------- /lib/vendor/buffer/specific-buffer.js: -------------------------------------------------------------------------------- 1 | const NOT_IMPLEMENTED = function () { 2 | throw new Error('Not implemented'); 3 | }; 4 | 5 | function SpecificBuffer (_Buffer) { 6 | function Buffer () { 7 | if (typeof arguments[0] === 'number') { 8 | return _Buffer.alloc(...arguments); 9 | } 10 | 11 | return _Buffer.from(...arguments); 12 | } 13 | 14 | // Add the static properties from the original Buffer 15 | Object.setPrototypeOf(Buffer, _Buffer); 16 | 17 | Buffer.poolSize = _Buffer.poolSize; 18 | 19 | Object.defineProperty(Buffer, Symbol.hasInstance, { 20 | value: function (instance) { 21 | return instance instanceof _Buffer; 22 | } 23 | }); 24 | 25 | Buffer.from = function from () { 26 | return _Buffer.from(...arguments); 27 | }; 28 | 29 | Buffer.copyBytesFrom = NOT_IMPLEMENTED; 30 | 31 | Buffer.of = NOT_IMPLEMENTED; 32 | 33 | Buffer.alloc = function alloc () { 34 | return _Buffer.alloc(...arguments); 35 | }; 36 | 37 | Buffer.allocUnsafe = function allocUnsafe () { 38 | return _Buffer.allocUnsafe(...arguments); 39 | }; 40 | 41 | Buffer.allocUnsafeSlow = function allocUnsafeSlow () { 42 | return _Buffer.allocUnsafeSlow(...arguments); 43 | }; 44 | 45 | Buffer.isBuffer = function isBuffer () { 46 | return _Buffer.isBuffer(...arguments); 47 | }; 48 | 49 | Buffer.compare = function compare () { 50 | return _Buffer.compare(...arguments); 51 | }; 52 | 53 | Buffer.isEncoding = function isEncoding () { 54 | return _Buffer.isEncoding(...arguments); 55 | }; 56 | 57 | Buffer.concat = function concat () { 58 | return _Buffer.concat(...arguments); 59 | }; 60 | 61 | Buffer.byteLength = function byteLength () { 62 | return _Buffer.byteLength(...arguments); 63 | }; 64 | 65 | return Buffer; 66 | } 67 | 68 | module.exports = SpecificBuffer; 69 | -------------------------------------------------------------------------------- /lib/vendor/uuid.js: -------------------------------------------------------------------------------- 1 | var plc = 'x', 2 | pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', 3 | bit = /[xy]/g, 4 | replacer = function(c) { 5 | var r = Math.random()*16|0, v = c == plc ? r : (r&0x3|0x8); 6 | return v.toString(16); 7 | }; 8 | 9 | module.exports = function () { 10 | return pattern.replace(bit, replacer); 11 | }; 12 | 13 | module.exports.v4 = function () { 14 | return pattern.replace(bit, replacer); 15 | }; 16 | -------------------------------------------------------------------------------- /npm/build-types.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to generate type-definition for this module. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const path = require('path'), 7 | fs = require('fs'), 8 | chalk = require('chalk'), 9 | { test, exec, rm } = require('shelljs'), 10 | 11 | templates = require('./utils/templates'), 12 | 13 | IS_WINDOWS = (/^win/).test(process.platform), 14 | TARGET_DIR = path.join('types'); 15 | 16 | module.exports = function (exit) { 17 | console.info(chalk.yellow.bold('Generating type-definitions...')); 18 | 19 | try { 20 | // clean directory 21 | test('-d', TARGET_DIR) && rm('-rf', TARGET_DIR); 22 | } 23 | catch (e) { 24 | console.error(e.stack || e); 25 | 26 | return exit(e ? 1 : 0); 27 | } 28 | 29 | exec(`${IS_WINDOWS ? '' : 'node'} ${path.join('node_modules', '.bin', 'jsdoc')}${IS_WINDOWS ? '.cmd' : ''}` + 30 | ' -c .jsdoc-config-type-def.json -p', function (code) { 31 | if (!code) { 32 | fs.readFile(`${TARGET_DIR}/index.d.ts`, function (err, contents) { 33 | if (err) { 34 | console.info(chalk.red.bold('unable to read the type-definition file')); 35 | exit(1); 36 | } 37 | 38 | var source = contents.toString(); 39 | 40 | source = source 41 | // replacing Integer with number as 'Integer' is not a valid data-type in Typescript 42 | .replace(/Integer/gm, 'number') 43 | // replacing String[] with string[] as 'String' is not a valid data-type in Typescript 44 | .replace(/String\[]/gm, 'string[]') 45 | // replacing Boolean[] with boolean[] as 'Boolean' is not a valid data-type in Typescript 46 | .replace(/Boolean\[]/gm, 'boolean[]') 47 | // removing all occurrences html, as the these tags are not supported in Type-definitions 48 | .replace(/<[^>]*>/gm, '') 49 | // replacing @link tags with the object namepath to which it was linked, 50 | // as these link tags are not navigable in type-definitions. 51 | .replace(/\{@link (\w*)[#.]+(\w*)\}/gm, '$1.$2') 52 | .replace(/\{@link (\S+)\}/gm, '$1'); // remove @link tags 53 | 54 | source = `${templates.heading}\n${source}`; 55 | 56 | fs.writeFile(`${TARGET_DIR}/index.d.ts`, source, function (err) { 57 | if (err) { 58 | console.info(chalk.red.bold('unable to write the type-definition file')); 59 | exit(1); 60 | } 61 | console.info(chalk.green.bold(`type-definition file saved successfully at "${TARGET_DIR}"`)); 62 | exit(0); 63 | }); 64 | }); 65 | } 66 | else { 67 | // output status 68 | console.info(chalk.red.bold('unable to generate type-definition')); 69 | exit(code); 70 | } 71 | }); 72 | }; 73 | 74 | // ensure we run this script exports if this is a direct stdin.tty run 75 | !module.parent && module.exports(process.exit); 76 | -------------------------------------------------------------------------------- /npm/cache.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to generate boot code in ".cache" directory. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const _ = require('lodash'), 7 | async = require('async'), 8 | fs = require('fs'), 9 | chalk = require('chalk'), 10 | { mkdir, rm } = require('shelljs'), 11 | 12 | Bundle = require('../lib/bundle'); 13 | 14 | function createBundle (options, file, done) { 15 | async.waterfall([ 16 | function (next) { 17 | Bundle.load(options).compile(next); 18 | }, 19 | 20 | function (codeString, next) { 21 | fs.writeFile(file, `module.exports=c=>c(null,${JSON.stringify(codeString)})`, next); 22 | }, 23 | 24 | function (next) { 25 | console.info(` - ${file}`); 26 | next(); 27 | } 28 | ], done); 29 | } 30 | 31 | module.exports = function (exit) { 32 | mkdir('-p', '.cache'); // create a cache directory in any case 33 | 34 | if (_.get(process, 'argv[2]') === 'clear') { 35 | rm('-rf', '.cache'); 36 | 37 | console.info('cache cleared - ".cache/*"'); 38 | exit(); 39 | } 40 | 41 | console.info(chalk.yellow.bold('Generating bootcode in ".cache" directory...')); 42 | 43 | const options = require('../lib/environment'); 44 | 45 | async.parallel([ 46 | async.apply(createBundle, _.merge({ 47 | compress: true, 48 | preferBrowserResolver: false, 49 | bundler: { browserField: false } 50 | }, options), './.cache/bootcode.js'), 51 | async.apply(createBundle, _.merge({ 52 | compress: true, 53 | preferBrowserResolver: true, 54 | bundler: { browserField: true } 55 | }, options), './.cache/bootcode.browser.js') 56 | ], function (err) { 57 | if (err) { 58 | console.error(err); 59 | } 60 | else { 61 | console.info(chalk.green('bootcode ready for use!')); 62 | } 63 | exit(err ? 1 : 0); 64 | }); 65 | }; 66 | 67 | // ensure we run this script exports if this is a direct stdin.tty run 68 | !module.parent && module.exports(process.exit); 69 | -------------------------------------------------------------------------------- /npm/create-release.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to automate the versioning and changelog generation process for a release. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const shipit = require('@postman/shipit'), 7 | 8 | // npm run release [true] [beta] 9 | [pushToOrigin, preReleaseSuffix] = process.argv.splice(2); 10 | 11 | // only support `beta` suffix 12 | if (preReleaseSuffix && preReleaseSuffix !== 'beta') { 13 | throw new Error(`Can't prerelease with \`${preReleaseSuffix}\` suffix.`); 14 | } 15 | 16 | // 🚢 Just Ship It! 17 | shipit({ 18 | mainBranch: 'main', 19 | // don't push to origin unless explicitly set 20 | pushToOrigin: pushToOrigin === 'true', 21 | // prerelease suffix, if any 22 | preReleaseSuffix: preReleaseSuffix, 23 | // make sure that following dependencies are up to date 24 | dependencyList: [ 25 | '@postman/tough-cookie', 'chai-postman', 'liquid-json', 'lodash3', 26 | 'postman-collection', 'teleport-javascript', 'uniscope', 'uvm' 27 | ] 28 | }).then((version) => { 29 | console.info('🚀', version); 30 | }).catch((err) => { 31 | console.error('🔥', err); 32 | process.exit(1); 33 | }); 34 | -------------------------------------------------------------------------------- /npm/prepublish.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all required checks prior to publishing the module. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | // eslint-disable-next-line security/detect-child-process 7 | const { mkdir, rm } = require('shelljs'), 8 | cache = require('./cache'), 9 | systemTests = require('./test-system'); 10 | 11 | 12 | // trigger cache generation after clearing it 13 | rm('-rf', '.cache'); 14 | mkdir('-p', '.cache'); 15 | 16 | cache((exitCode) => { 17 | exitCode && process.exit(exitCode); 18 | 19 | systemTests(process.exit); 20 | }); 21 | -------------------------------------------------------------------------------- /npm/publish-coverage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to publish coverage reports to Codecov. 4 | // 5 | // It verifies the Codecov's bash uploader checksum before execution. 6 | // Usage: npm run codecov -- 7 | // Refer: https://about.codecov.io/security-update/ 8 | // --------------------------------------------------------------------------------------------------------------------- 9 | 10 | const https = require('https'), 11 | crypto = require('crypto'), 12 | promisify = require('util').promisify, 13 | 14 | // eslint-disable-next-line security/detect-child-process 15 | exec = promisify(require('child_process').exec), 16 | writeFile = promisify(require('fs').writeFile), 17 | 18 | chalk = require('chalk'), 19 | 20 | CODECOV_PATH = '.coverage/codecov.sh', 21 | BASH_UPLOADER_URL = 'https://codecov.io/bash', 22 | BASH_UPLOADER_BASE = 'https://raw.githubusercontent.com/codecov/codecov-bash'; 23 | 24 | function wget (url) { 25 | return new Promise((resolve, reject) => { 26 | const req = https.request(url, (res) => { 27 | if (res.statusCode !== 200) { 28 | return reject(new Error('non-200 response')); 29 | } 30 | 31 | let data = ''; 32 | 33 | res.on('data', (chunk) => { 34 | data += chunk; 35 | }); 36 | 37 | res.on('end', () => { 38 | resolve(data); 39 | }); 40 | }); 41 | 42 | req.on('error', (err) => { 43 | reject(err); 44 | }); 45 | 46 | req.end(); 47 | }); 48 | } 49 | 50 | function getVersion (script) { 51 | const match = script.match(/VERSION="([0-9.]*)"/); 52 | 53 | return match ? match[1] : null; 54 | } 55 | 56 | async function getPublicChecksum (version, encryption) { 57 | const url = `${BASH_UPLOADER_BASE}/${version}/SHA${encryption}SUM`, 58 | checksumResponse = await wget(url); 59 | 60 | // return codecov checksum only 61 | return checksumResponse.split('\n')[0]; 62 | } 63 | 64 | function calculateChecksum (script, encryption) { 65 | const shasum = crypto.createHash(`sha${encryption}`); 66 | 67 | shasum.update(script); 68 | 69 | return `${shasum.digest('hex')} codecov`; 70 | } 71 | 72 | async function validateScript (script) { 73 | const version = getVersion(script); 74 | 75 | if (!version) { 76 | throw new Error('Missing bash uploader version'); 77 | } 78 | 79 | for (const encryption of [1, 256, 512]) { 80 | // eslint-disable-next-line no-await-in-loop 81 | const publicChecksum = await getPublicChecksum(version, encryption), 82 | uploaderChecksum = calculateChecksum(script, encryption); 83 | 84 | if (uploaderChecksum !== publicChecksum) { 85 | throw new Error(`SHA${encryption} checksum mismatch`); 86 | } 87 | } 88 | } 89 | 90 | module.exports = async function () { 91 | // banner line 92 | console.info(chalk.yellow.bold('Publishing coverage reports...')); 93 | 94 | const args = process.argv.slice(2), 95 | script = await wget(BASH_UPLOADER_URL); 96 | 97 | await validateScript(script); 98 | await writeFile(CODECOV_PATH, script); 99 | 100 | return exec(`bash ${CODECOV_PATH} ${args.join(' ')}`); 101 | }; 102 | 103 | // ensure we run this script exports if this is a direct stdin.tty run 104 | if (!module.parent) { 105 | module.exports() 106 | .then(({ stdout, stderr }) => { 107 | console.info(stdout); 108 | console.info(stderr); 109 | }) 110 | .catch(({ message, stack, stdout, stderr }) => { 111 | console.error(stack || message); 112 | stdout && console.info(stdout); 113 | stderr && console.info(stderr); 114 | 115 | process.exit(1); 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /npm/test-browser.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all unit tests in the Chrome Browser. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const path = require('path'), 7 | 8 | chalk = require('chalk'), 9 | KarmaServer = require('karma').Server, 10 | 11 | KARMA_CONFIG_PATH = path.join(__dirname, '..', 'test', 'karma.conf'); 12 | 13 | module.exports = function (exit) { 14 | console.info(chalk.yellow.bold('Running unit tests within browser...')); 15 | 16 | (new KarmaServer({ // eslint-disable no-new 17 | cmd: 'start', 18 | configFile: KARMA_CONFIG_PATH 19 | }, exit)).start(); 20 | }; 21 | 22 | // ensure we run this script exports if this is a direct stdin.tty run 23 | !module.parent && module.exports(process.exit); 24 | -------------------------------------------------------------------------------- /npm/test-integration.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all integration tests. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | // set directories and files for test and coverage report 7 | const path = require('path'), 8 | 9 | chalk = require('chalk'), 10 | Mocha = require('mocha'), 11 | recursive = require('recursive-readdir'), 12 | 13 | SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'integration'); 14 | 15 | module.exports = function (exit) { 16 | // banner line 17 | console.info(chalk.yellow.bold('Running integration tests using mocha on node...')); 18 | 19 | // add all spec files to mocha 20 | recursive(SPEC_SOURCE_DIR, function (err, files) { 21 | if (err) { 22 | console.error(err); 23 | 24 | return exit(1); 25 | } 26 | 27 | const mocha = new Mocha({ timeout: 1000 * 60 }); 28 | 29 | // specially load bootstrap file 30 | mocha.addFile(path.join(SPEC_SOURCE_DIR, '_bootstrap.js')); 31 | 32 | files.filter(function (file) { // extract all test files 33 | return (file.substr(-8) === '.test.js'); 34 | }).forEach(mocha.addFile.bind(mocha)); 35 | 36 | mocha.run(function (err) { 37 | err && console.error(err.stack || err); 38 | exit(err ? 1 : 0); 39 | }); 40 | }); 41 | }; 42 | 43 | // ensure we run this script exports if this is a direct stdin.tty run 44 | !module.parent && module.exports(process.exit); 45 | -------------------------------------------------------------------------------- /npm/test-lint.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to contain all actions pertaining to code style checking, linting and normalization. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const chalk = require('chalk'), 7 | { ESLint } = require('eslint'), 8 | 9 | LINT_SOURCE_DIRS = [ 10 | './test/**/*.js', 11 | './index.js', 12 | './lib/**/*.js', 13 | './npm/**/*.js' 14 | ]; 15 | 16 | module.exports = async function (exit) { 17 | // banner line 18 | console.info(chalk.yellow.bold('\nLinting files using eslint...')); 19 | 20 | const eslint = new ESLint(), 21 | results = await eslint.lintFiles(LINT_SOURCE_DIRS), 22 | errorReport = ESLint.getErrorResults(results), 23 | formatter = await eslint.loadFormatter(); 24 | 25 | // log the result to CLI 26 | console.info(formatter.format(results)); 27 | 28 | (errorReport && !errorReport.length) && console.info(chalk.green('eslint ok!')); 29 | 30 | exit(Number(errorReport && errorReport.length) || 0); 31 | }; 32 | 33 | // ensure we run this script exports if this is a direct stdin.tty run 34 | !module.parent && module.exports(process.exit); 35 | -------------------------------------------------------------------------------- /npm/test-system.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all system tests. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const path = require('path'), 7 | 8 | Mocha = require('mocha'), 9 | chalk = require('chalk'), 10 | packity = require('packity'), 11 | recursive = require('recursive-readdir'), 12 | 13 | SPEC_SOURCE_DIR = path.join(__dirname, '..', 'test', 'system'); 14 | 15 | module.exports = function (exit) { 16 | // banner line 17 | console.info(chalk.yellow.bold('\nRunning system tests using mocha...')); 18 | 19 | // add all spec files to mocha 20 | recursive(SPEC_SOURCE_DIR, (err, files) => { 21 | if (err) { 22 | console.error(err); 23 | 24 | return exit(1); 25 | } 26 | 27 | const mocha = new Mocha({ timeout: 1000 * 60 }); 28 | 29 | files.filter((file) => { // extract all test files 30 | return (file.substr(-8) === '.test.js'); 31 | }).forEach(mocha.addFile.bind(mocha)); 32 | 33 | // start the mocha run 34 | mocha.run((runError) => { 35 | if (runError) { 36 | console.error(runError.stack || runError); 37 | 38 | return exit(1); 39 | } 40 | 41 | // packity 42 | const options = { 43 | path: './', 44 | dev: true 45 | }; 46 | 47 | packity(options, (err, results) => { 48 | packity.cliReporter(options)(err, results); 49 | 50 | exit(err ? 1 : 0); 51 | }); 52 | }); 53 | }); 54 | }; 55 | 56 | // ensure we run this script exports if this is a direct stdin.tty run 57 | !module.parent && module.exports(process.exit); 58 | -------------------------------------------------------------------------------- /npm/test-unit.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all unit tests. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | const path = require('path'), 7 | 8 | chalk = require('chalk'), 9 | Mocha = require('mocha'), 10 | recursive = require('recursive-readdir'), 11 | 12 | SPEC_SOURCE_DIR = path.join('test', 'unit'); 13 | 14 | module.exports = function (exit) { 15 | // banner line 16 | console.info(chalk.yellow.bold('Running unit tests using mocha on node...')); 17 | 18 | // add all spec files to mocha 19 | recursive(SPEC_SOURCE_DIR, (err, files) => { 20 | if (err) { 21 | console.error(err); 22 | 23 | return exit(1); 24 | } 25 | 26 | const mocha = new Mocha({ timeout: 1000 * 60 }); 27 | 28 | // specially load bootstrap file 29 | mocha.addFile(path.join(SPEC_SOURCE_DIR, '_bootstrap.js')); 30 | 31 | files.filter((file) => { // extract all test files 32 | return (file.substr(-8) === '.test.js'); 33 | }).forEach(mocha.addFile.bind(mocha)); 34 | 35 | // start the mocha run 36 | mocha.run((runError) => { 37 | runError && console.error(runError.stack || runError); 38 | 39 | exit(runError || process.exitCode ? 1 : 0); 40 | }); 41 | }); 42 | }; 43 | 44 | // ensure we run this script exports if this is a direct stdin.tty run 45 | !module.parent && module.exports(process.exit); 46 | -------------------------------------------------------------------------------- /npm/test-vm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // --------------------------------------------------------------------------------------------------------------------- 3 | // This script is intended to execute all unit tests in the Node VM: https://nodejs.org/api/vm.html. 4 | // --------------------------------------------------------------------------------------------------------------------- 5 | 6 | // set directories and files for test and coverage report 7 | const vm = require('vm'), 8 | 9 | chalk = require('chalk'), 10 | async = require('async'), 11 | browserify = require('browserify'), 12 | recursive = require('recursive-readdir'); 13 | 14 | module.exports = function (exit) { 15 | console.info(chalk.yellow.bold('Loading and running the sandbox bundle tests in the Node VM')); 16 | 17 | async.waterfall([ 18 | // Enlist all unit test files 19 | async.apply(recursive, 'test/vm'), 20 | 21 | // Bundle the unit test suite 22 | function (files, next) { 23 | var specs, 24 | bundler = browserify('test/vm/_bootstrap.js'); 25 | 26 | // workaround to avoid "getRandomValues() not supported" 27 | // ref: https://github.com/uuidjs/uuid#getrandomvalues-not-supported 28 | bundler.require(require.resolve('../lib/vendor/uuid'), { expose: 'uuid' }); 29 | 30 | (specs = files.filter(function (file) { // extract all test files 31 | return (file.substr(-8) === '.test.js'); 32 | })).forEach(function (file) { 33 | // @hack to allow mocha.addFile to work correctly in the Node VM 34 | bundler.require('./' + file, { expose: file }); 35 | }); 36 | 37 | bundler.bundle(function (err, bundle) { 38 | next(err, specs, bundle); 39 | }); 40 | }, 41 | 42 | // Run the tests in the VM 43 | function (__specs, bundle, __next) { 44 | var context = vm.createContext({ console, setTimeout, clearTimeout, __next, __specs }); 45 | 46 | context.global = context; // @hack to make the context work correctly 47 | 48 | vm.runInContext(bundle.toString(), context, { displayErrors: true }); 49 | } 50 | ], exit); 51 | }; 52 | 53 | // ensure we run this script exports if this is a direct stdin.tty run 54 | !module.parent && module.exports(process.exit); 55 | -------------------------------------------------------------------------------- /npm/utils/jsdoc-custom-tags-plugin.js: -------------------------------------------------------------------------------- 1 | function _onTagged (doclet, tag) { 2 | doclet.description = `${doclet.description}\n@${tag.originalTitle}`; 3 | } 4 | 5 | function defineTags (dictionary) { 6 | dictionary.defineTag('excludeFromPrerequestScript', { 7 | onTagged: _onTagged 8 | }); 9 | 10 | dictionary.defineTag('excludeFromTestScript', { 11 | onTagged: _onTagged 12 | }); 13 | } 14 | 15 | module.exports = { defineTags }; 16 | -------------------------------------------------------------------------------- /npm/utils/templates.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../../package.json'), 2 | 3 | heading = 4 | `// Type definitions for postman-sandbox ${pkg.version} 5 | // Project: https://github.com/postmanlabs/postman-sandbox 6 | // Definitions by: PostmanLabs 7 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 8 | // TypeScript Version: 2.4 9 | /// 10 | `, 11 | 12 | postmanLegacyString = 13 | ` 14 | /** 15 | * @deprecated 16 | */ 17 | declare var postman: PostmanLegacy; 18 | 19 | declare interface PostmanLegacy { 20 | 21 | /*** 22 | * Sets the next request to be executed. 23 | * @param requestName Name of the next request to be executed. 24 | * 25 | * @deprecated Use pm.execution.setNextRequest() instead 26 | */ 27 | setNextRequest(requestName: string): void 28 | } 29 | 30 | /** 31 | * @deprecated Use pm.test() instead 32 | */ 33 | declare var tests; 34 | 35 | /** 36 | * @deprecated Use pm.globals instead 37 | */ 38 | declare var globals; 39 | 40 | /** 41 | * @deprecated Use pm.environment instead 42 | */ 43 | declare var environment; 44 | 45 | /** 46 | * @deprecated Use pm.iterationData instead 47 | */ 48 | declare var data; 49 | 50 | /** 51 | * @deprecated Use pm.request instead 52 | */ 53 | declare var request; 54 | 55 | /** 56 | * @deprecated Use pm.cookies instead 57 | * @excludeFromPrerequestScript 58 | */ 59 | declare var responseCookies; 60 | 61 | /** 62 | * @deprecated Use pm.response.headers instead 63 | * @excludeFromPrerequestScript 64 | */ 65 | declare var responseHeaders; 66 | 67 | /** 68 | * @deprecated Use pm.response.responseTime instead 69 | * @excludeFromPrerequestScript 70 | */ 71 | declare var responseTime; 72 | 73 | /** 74 | * @deprecated Use pm.response.code instead 75 | * @excludeFromPrerequestScript 76 | */ 77 | declare var responseCode; 78 | 79 | /** 80 | * @deprecated Use pm.response.text() instead 81 | * @excludeFromPrerequestScript 82 | */ 83 | declare var responseBody; 84 | 85 | /** 86 | * @deprecated Use pm.info.iteration instead 87 | */ 88 | declare var iteration; 89 | 90 | /** 91 | * @deprecated Use require('lodash') instead 92 | */ 93 | declare var _; 94 | 95 | /** 96 | * @deprecated Use global "crypto" object instead 97 | */ 98 | declare var CryptoJS; 99 | 100 | /** 101 | * @deprecated Use require('ajv') instead 102 | */ 103 | declare var tv4; 104 | 105 | /** 106 | * @deprecated Use require('xml2js') instead 107 | */ 108 | declare var xml2Json; 109 | 110 | /** 111 | * @deprecated 112 | */ 113 | declare var Backbone; 114 | 115 | /** 116 | * @deprecated Use require('cheerio') instead 117 | */ 118 | declare var cheerio; 119 | `, 120 | 121 | postmanExtensionString = 122 | `interface Postman { 123 | test: Test; 124 | } 125 | 126 | interface Test { 127 | 128 | /** 129 | * You can use this function to write test specifications inside either the Pre-request Script or Tests sandbox. 130 | * Writing tests inside this function allows you to name the test accurately and this function also ensures the 131 | * rest of the script is not blocked even if there are errors inside the function. 132 | * @param testName 133 | * @param specFunction 134 | */ 135 | (testName: string, specFunction: Function): void 136 | 137 | /** 138 | * Get the total number tests from a specific location. 139 | */ 140 | index(): number, 141 | 142 | /** 143 | * By appending .skip(), you may tell test runner to ignore test case. 144 | * @param testName 145 | */ 146 | skip(testName: string): void 147 | }`, 148 | 149 | cookieListExtensionString = 150 | `interface CookieList { 151 | jar() : PostmanCookieJar 152 | }`, 153 | 154 | responseExtensionString = 155 | `interface Response extends Assertable { 156 | 157 | } 158 | 159 | interface Assertable { 160 | to: { 161 | have: AssertableHave 162 | 163 | /** 164 | * The properties inside the "pm.response.to.be" object allows you to easily assert a set of pre-defined rules. 165 | */ 166 | be: AssertableBe 167 | } 168 | } 169 | 170 | interface AssertableHave { 171 | status(code: number): any 172 | status(reason: string): any 173 | header(key: string): any 174 | header(key: string, optionalValue: string): any 175 | body(): any 176 | body(optionalStringValue: string): any 177 | body(optionalRegExpValue: RegExp): any 178 | jsonBody(): any 179 | jsonBody(optionalExpectEqual: object): any 180 | jsonBody(optionalExpectPath: string): any 181 | jsonBody(optionalExpectPath: string, optionalValue: any): any 182 | jsonSchema(schema: object): any 183 | jsonSchema(schema: object, ajvOptions: object): any 184 | } 185 | 186 | interface AssertableBe { 187 | 188 | /** 189 | * Checks 1XX status code 190 | */ 191 | info: number 192 | 193 | /** 194 | * Checks 2XX status code 195 | */ 196 | success: number 197 | 198 | /** 199 | * Checks 3XX status code 200 | */ 201 | redirection: number 202 | 203 | /** 204 | * Checks 4XX status code 205 | */ 206 | clientError: number 207 | 208 | /** 209 | * Checks 5XX 210 | */ 211 | serverError: number 212 | 213 | /** 214 | * Checks 4XX or 5XX 215 | */ 216 | error: number 217 | 218 | /** 219 | * Status code must be 200 220 | */ 221 | ok: number 222 | 223 | /** 224 | * Status code must be 202 225 | */ 226 | accepted: number 227 | 228 | /** 229 | * Status code must be 400 230 | */ 231 | badRequest: number 232 | 233 | /** 234 | * Status code must be 401 235 | */ 236 | unauthorized: number 237 | 238 | /** 239 | * Status code 403 240 | */ 241 | forbidden: number 242 | 243 | /** 244 | * Status code of response is checked to be 404 245 | */ 246 | notFound: number 247 | 248 | /** 249 | * Checks whether response status code is 429 250 | */ 251 | rateLimited: number 252 | }`; 253 | 254 | module.exports = { 255 | heading, 256 | postmanLegacyString, 257 | postmanExtensionString, 258 | cookieListExtensionString, 259 | responseExtensionString 260 | }; 261 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postman-sandbox", 3 | "version": "6.1.1", 4 | "description": "Sandbox for Postman Scripts to run in Node.js or browser", 5 | "author": "Postman Inc.", 6 | "license": "Apache-2.0", 7 | "main": "index.js", 8 | "browser": { 9 | "./lib/bundle/index.js": "./lib/bundle/index.browser.js", 10 | "./.cache/bootcode.js": "./.cache/bootcode.browser.js" 11 | }, 12 | "homepage": "https://github.com/postmanlabs/postman-sandbox#readme", 13 | "bugs": { 14 | "url": "https://github.com/postmanlabs/postman-sandbox/issues", 15 | "email": "help@postman.com" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/postmanlabs/postman-sandbox.git" 20 | }, 21 | "keywords": [ 22 | "contextify", 23 | "postman", 24 | "sandbox", 25 | "vm" 26 | ], 27 | "scripts": { 28 | "build-common-types": "node npm/build-types.js", 29 | "build-sandbox-types": "node npm/build-sandbox-types.js", 30 | "build-types": "npm run build-common-types && npm run build-sandbox-types", 31 | "cache": "node npm/cache.js $1", 32 | "codecov": "node npm/publish-coverage.js", 33 | "prepublishOnly": "node npm/prepublish.js", 34 | "pretest": "npm run cache", 35 | "release": "node npm/create-release.js", 36 | "test": "npm run test-lint && npm run test-system && npm run test-unit && npm run test-vm && npm run test-integration && npm run test-browser", 37 | "test-browser": "node npm/test-browser.js", 38 | "test-integration": "node npm/test-integration.js", 39 | "test-lint": "node npm/test-lint.js", 40 | "test-system": "node npm/test-system.js", 41 | "test-unit": "nyc --nycrc-path=.nycrc.js node npm/test-unit.js", 42 | "test-vm": "node npm/test-vm.js" 43 | }, 44 | "dependencies": { 45 | "lodash": "4.17.21", 46 | "postman-collection": "5.0.2", 47 | "teleport-javascript": "1.0.0", 48 | "uvm": "4.0.0" 49 | }, 50 | "devDependencies": { 51 | "@postman/shipit": "^0.4.0", 52 | "@postman/tough-cookie": "4.1.3-postman.1", 53 | "@stylistic/eslint-plugin-js": "^1.8.0", 54 | "ajv": "6.12.5", 55 | "assert": "2.0.0", 56 | "async": "^3.2.6", 57 | "backbone": "1.6.0", 58 | "browserify": "^16.5.2", 59 | "buffer": "6.0.3", 60 | "chai": "4.4.1", 61 | "chai-postman": "2.0.1", 62 | "chalk": "^4.1.2", 63 | "cheerio": "0.22.0", 64 | "crypto-js": "3.3.0", 65 | "csv-parse": "1.2.4", 66 | "editorconfig": "^2.0.1", 67 | "eslint": "^8.57.0", 68 | "eslint-plugin-jsdoc": "^47.0.2", 69 | "eslint-plugin-lodash": "^7.4.0", 70 | "eslint-plugin-mocha": "^10.5.0", 71 | "eslint-plugin-n": "^16.6.2", 72 | "eslint-plugin-security": "^2.1.1", 73 | "jquery": "^3.7.1", 74 | "js-yaml": "^4.1.0", 75 | "jsdoc": "^3.6.10", 76 | "karma": "^6.4.4", 77 | "karma-browserify": "^8.1.0", 78 | "karma-chrome-launcher": "^3.2.0", 79 | "karma-mocha": "^2.0.1", 80 | "karma-mocha-reporter": "^2.2.5", 81 | "liquid-json": "0.3.1", 82 | "lodash3": "3.10.2", 83 | "mocha": "^11.1.0", 84 | "moment": "2.30.1", 85 | "nyc": "^17.1.0", 86 | "packity": "^0.3.5", 87 | "parse-gitignore": "^2.0.0", 88 | "recursive-readdir": "^2.2.3", 89 | "shelljs": "^0.8.5", 90 | "sinon": "^18.0.1", 91 | "sinon-chai": "^3.7.0", 92 | "terser": "^5.39.0", 93 | "tsd-jsdoc": "^2.5.0", 94 | "tv4": "1.3.0", 95 | "uniscope": "3.1.0", 96 | "watchify": "^4.0.0", 97 | "xml2js": "0.6.2" 98 | }, 99 | "engines": { 100 | "node": ">=18" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "mocha" 4 | ], 5 | "env": { 6 | "mocha": true, 7 | "browser": true, 8 | "node": true, 9 | "es6": true 10 | }, 11 | "globals": { 12 | "describe": true, 13 | "expect": true, 14 | "sinon": true, 15 | "it": true 16 | }, 17 | "rules": { 18 | // Mocha 19 | "mocha/handle-done-callback": "error", 20 | "mocha/max-top-level-suites": "error", 21 | "mocha/no-exclusive-tests": "error", 22 | "mocha/no-global-tests": "error", 23 | "mocha/no-hooks-for-single-case": "off", 24 | "mocha/no-hooks": "off", 25 | "mocha/no-identical-title": "error", 26 | "mocha/no-mocha-arrows": "error", 27 | "mocha/no-nested-tests": "error", 28 | "mocha/no-pending-tests": "error", 29 | "mocha/no-return-and-callback": "error", 30 | "mocha/no-sibling-hooks": "error", 31 | "mocha/no-skipped-tests": "warn", 32 | "mocha/no-synchronous-tests": "off", 33 | "mocha/no-top-level-hooks": "warn", 34 | "mocha/valid-test-description": "off", 35 | "mocha/valid-suite-description": "off" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/integration/_bootstrap.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable mocha/no-top-level-hooks */ 2 | var _expect; 3 | 4 | before(function () { 5 | global.expect && (_expect = global.expect); 6 | global.expect = require('chai').expect; 7 | }); 8 | 9 | after(function () { 10 | _expect ? (global.expect = _expect) : (delete global.expect); 11 | _expect = null; 12 | }); 13 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | module.exports = function (config) { 3 | var configuration = { 4 | 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: '', 7 | 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['mocha', 'browserify'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | '../index.js', 15 | '../test/unit/**/*.js' 16 | ], 17 | 18 | // preprocess matching files before serving them to the browser 19 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 20 | preprocessors: { 21 | '../index.js': ['browserify'], // Mention path as per your test js folder 22 | '../test/unit/**/*.js': ['browserify'] // Mention path as per your library js folder 23 | }, 24 | // test results reporter to use 25 | // possible values: 'dots', 'progress' 26 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 27 | reporters: ['mocha'], 28 | 29 | // web server port 30 | port: 9876, 31 | 32 | // the number of milliseconds to wait for an idle browser to come back up before bailing 33 | browserNoActivityTimeout: 20000, 34 | 35 | // enable / disable colors in the output (reporters and logs) 36 | colors: true, 37 | 38 | // level of logging 39 | // one of: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 40 | logLevel: config.LOG_WARN, 41 | 42 | // enable / disable watching file and executing tests whenever any file changes 43 | autoWatch: false, 44 | 45 | // start these browsers 46 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 47 | browsers: ['ChromeHeadless'], 48 | 49 | // Continuous Integration mode 50 | // if true, Karma captures browsers, runs the tests and exits 51 | singleRun: true, 52 | 53 | // Concurrency level 54 | // how many browser should be started simultaneously 55 | concurrency: Infinity, 56 | 57 | // Uncomment "karma-browserify" if you see an error like this: 58 | // Error: No provider for "framework:browserify"! (Resolving: framework:browserify) 59 | plugins: [ 60 | 'karma-mocha', 61 | 'karma-chrome-launcher', 62 | 'karma-browserify', 63 | 'karma-mocha-reporter' 64 | ], 65 | 66 | // Pass options to the client frameworks. 67 | client: { 68 | mocha: { 69 | timeout: 10000 // 10 seconds 70 | } 71 | } 72 | }; 73 | 74 | // Use `npm run test-browser -- --debug` to debug tests in Chrome console 75 | if (process.argv[2] === '--debug') { 76 | configuration.browsers = ['Chrome']; 77 | configuration.singleRun = false; 78 | } 79 | 80 | config.set(configuration); 81 | }; 82 | -------------------------------------------------------------------------------- /test/system/bootcode-dependencies.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect, 2 | env = require('../../lib/environment'), 3 | Bundle = require('../../lib/bundle'), 4 | 5 | expectedDependenciesBrowser = [ 6 | '@faker-js/faker', 7 | '@postman/tough-cookie', 8 | 'ajv', 9 | 'array-filter', 10 | 'assert', 11 | 'assertion-error', 12 | 'available-typed-arrays', 13 | 'backbone', 14 | 'base64-js', 15 | 'boolbase', 16 | 'browserify', 17 | 'buffer', 18 | 'call-bind', 19 | 'chai', 20 | 'chai-postman', 21 | 'charset', 22 | 'check-error', 23 | 'cheerio', 24 | 'core-util-is', 25 | 'crypto-js', 26 | 'css-select', 27 | 'css-what', 28 | 'csv-parse', 29 | 'deep-eql', 30 | 'define-properties', 31 | 'dom-serializer', 32 | 'domelementtype', 33 | 'domhandler', 34 | 'domutils', 35 | 'entities', 36 | 'es-abstract', 37 | 'es6-object-assign', 38 | 'events', 39 | 'fast-deep-equal', 40 | 'fast-json-stable-stringify', 41 | 'file-type', 42 | 'foreach', 43 | 'function-bind', 44 | 'get-func-name', 45 | 'get-intrinsic', 46 | 'has', 47 | 'has-symbols', 48 | 'htmlparser2', 49 | 'http-reasons', 50 | 'iconv-lite', 51 | 'ieee754', 52 | 'inherits', 53 | 'is-arguments', 54 | 'is-buffer', 55 | 'is-generator-function', 56 | 'is-nan', 57 | 'is-typed-array', 58 | 'isarray', 59 | 'jquery', 60 | 'json-schema-traverse', 61 | 'liquid-json', 62 | 'lodash', 63 | 'lodash.assignin', 64 | 'lodash.bind', 65 | 'lodash.defaults', 66 | 'lodash.filter', 67 | 'lodash.flatten', 68 | 'lodash.foreach', 69 | 'lodash.map', 70 | 'lodash.merge', 71 | 'lodash.pick', 72 | 'lodash.reduce', 73 | 'lodash.reject', 74 | 'lodash.some', 75 | 'lodash3', 76 | 'loupe', 77 | 'mime-db', 78 | 'mime-format', 79 | 'mime-types', 80 | 'moment', 81 | 'nth-check', 82 | 'object-is', 83 | 'object-keys', 84 | 'os-browserify', 85 | 'path-browserify', 86 | 'pathval', 87 | 'postman-collection', 88 | 'postman-sandbox', 89 | 'postman-url-encoder', 90 | 'process', 91 | 'process-nextick-args', 92 | 'psl', 93 | 'punycode', 94 | 'querystring-es3', 95 | 'querystringify', 96 | 'readable-stream', 97 | 'requires-port', 98 | 'safe-buffer', 99 | 'safer-buffer', 100 | 'sax', 101 | 'semver', 102 | 'stream-browserify', 103 | 'string_decoder', 104 | 'teleport-javascript', 105 | 'timers-browserify', 106 | 'tv4', 107 | 'type-detect', 108 | 'underscore', 109 | 'uniscope', 110 | 'universalify', 111 | 'uri-js', 112 | 'url', 113 | 'url-parse', 114 | 'util', 115 | 'util-deprecate', 116 | 'uuid', 117 | 'which-typed-array', 118 | 'xml2js', 119 | 'xmlbuilder' 120 | ], 121 | expectedDependenciesNode = [ 122 | '@faker-js/faker', 123 | '@postman/tough-cookie', 124 | 'ajv', 125 | 'array-filter', 126 | 'assert', 127 | 'assertion-error', 128 | 'available-typed-arrays', 129 | 'backbone', 130 | 'boolbase', 131 | 'browserify', 132 | 'call-bind', 133 | 'chai', 134 | 'chai-postman', 135 | 'charset', 136 | 'check-error', 137 | 'cheerio', 138 | 'core-util-is', 139 | 'crypto-js', 140 | 'css-select', 141 | 'css-what', 142 | 'csv-parse', 143 | 'deep-eql', 144 | 'define-properties', 145 | 'dom-serializer', 146 | 'domelementtype', 147 | 'domhandler', 148 | 'domutils', 149 | 'entities', 150 | 'es-abstract', 151 | 'es6-object-assign', 152 | 'events', 153 | 'fast-deep-equal', 154 | 'fast-json-stable-stringify', 155 | 'file-type', 156 | 'foreach', 157 | 'function-bind', 158 | 'get-func-name', 159 | 'get-intrinsic', 160 | 'has', 161 | 'has-symbols', 162 | 'htmlparser2', 163 | 'http-reasons', 164 | 'iconv-lite', 165 | 'inherits', 166 | 'is-arguments', 167 | 'is-buffer', 168 | 'is-generator-function', 169 | 'is-nan', 170 | 'is-typed-array', 171 | 'isarray', 172 | 'jquery', 173 | 'json-schema-traverse', 174 | 'liquid-json', 175 | 'lodash', 176 | 'lodash.assignin', 177 | 'lodash.bind', 178 | 'lodash.defaults', 179 | 'lodash.filter', 180 | 'lodash.flatten', 181 | 'lodash.foreach', 182 | 'lodash.map', 183 | 'lodash.merge', 184 | 'lodash.pick', 185 | 'lodash.reduce', 186 | 'lodash.reject', 187 | 'lodash.some', 188 | 'lodash3', 189 | 'loupe', 190 | 'mime-db', 191 | 'mime-format', 192 | 'mime-types', 193 | 'moment', 194 | 'nth-check', 195 | 'object-is', 196 | 'object-keys', 197 | 'os-browserify', 198 | 'path-browserify', 199 | 'pathval', 200 | 'postman-collection', 201 | 'postman-sandbox', 202 | 'postman-url-encoder', 203 | 'process', 204 | 'process-nextick-args', 205 | 'psl', 206 | 'punycode', 207 | 'querystring-es3', 208 | 'querystringify', 209 | 'readable-stream', 210 | 'requires-port', 211 | 'safe-buffer', 212 | 'safer-buffer', 213 | 'sax', 214 | 'semver', 215 | 'stream-browserify', 216 | 'string_decoder', 217 | 'teleport-javascript', 218 | 'timers-browserify', 219 | 'tv4', 220 | 'type-detect', 221 | 'underscore', 222 | 'uniscope', 223 | 'universalify', 224 | 'uri-js', 225 | 'url', 226 | 'url-parse', 227 | 'util', 228 | 'util-deprecate', 229 | 'uuid', 230 | 'which-typed-array', 231 | 'xml2js', 232 | 'xmlbuilder' 233 | ]; 234 | 235 | function getCurrentDependencies (isBrowser, cb) { 236 | Bundle.load({ ...env, preferBrowserResolver: isBrowser }) 237 | .listDependencies(function (err, dependencies) { 238 | console.info(err ? 'failed' : 'done'); 239 | 240 | return cb(err, dependencies); 241 | }); 242 | } 243 | 244 | describe('bootcode dependencies', function () { 245 | this.timeout(60 * 1000); 246 | 247 | it('should not change on Node', function (done) { 248 | getCurrentDependencies(false, function (err, currentDependencies) { 249 | if (err) { return done(err); } 250 | 251 | expect(currentDependencies).to.eql(expectedDependenciesNode); 252 | done(); 253 | }); 254 | }); 255 | 256 | it('should not change on Browser', function (done) { 257 | getCurrentDependencies(true, function (err, currentDependencies) { 258 | if (err) { return done(err); } 259 | 260 | expect(currentDependencies).to.eql(expectedDependenciesBrowser); 261 | done(); 262 | }); 263 | }); 264 | }); 265 | -------------------------------------------------------------------------------- /test/system/bootcode-size.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | path = require('path'), 3 | expect = require('chai').expect, 4 | 5 | CACHE_DIR = path.join(__dirname, '/../../.cache'), 6 | THRESHOLD = 2.5 * 1024 * 1024; // 2.5 MB 7 | 8 | describe('bootcode size', function () { 9 | this.timeout(60 * 1000); 10 | 11 | it('should not exceed the threshold', function (done) { 12 | fs.readdir(CACHE_DIR, function (err, files) { 13 | if (err) { return done(err); } 14 | 15 | files.forEach(function (file) { 16 | var size = fs.statSync(CACHE_DIR + '/' + file).size; 17 | 18 | expect(size, (file + ' threshold exceeded')).to.be.below(THRESHOLD); 19 | }); 20 | 21 | done(); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/system/editorconfig.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview Ensures that editorconfig settings are appropriate 3 | */ 4 | 5 | const editorconfig = require('editorconfig'), 6 | expect = require('chai').expect, 7 | 8 | /** 9 | * The width (in spaces) of tabs used for indentation throughout the project 10 | * 11 | * @type {Number} 12 | */ 13 | TAB_WIDTH = 4; 14 | 15 | describe('.editorconfig', function () { 16 | var config = editorconfig.parseSync('.editorconfig'); // eslint-disable-line no-sync 17 | 18 | it('should have a tab_width of 4', function () { 19 | expect(config.tab_width).to.equal(TAB_WIDTH); 20 | }); 21 | 22 | it('should have a charset of utf-8', function () { 23 | expect(config.charset).to.equal('utf-8'); 24 | }); 25 | 26 | it('should have an indent_size of 4', function () { 27 | expect(config.indent_size).to.equal(TAB_WIDTH); 28 | }); 29 | 30 | it('should have an indent_style of 4', function () { 31 | expect(config.indent_style).to.equal('space'); 32 | }); 33 | 34 | it('should have a truthy insert_final_newline value', function () { 35 | expect(config.insert_final_newline, 'Should insert final line').to.be.true; 36 | }); 37 | 38 | it('should have a truthy trim_trailing_whitespace', function () { 39 | expect(config.trim_trailing_whitespace, 'Should trim trailing whitespace').to.be.true; 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/system/jsdoc-config.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview This test specs runs tests on the ,jsdoc-config.json file of repository. It has a set of strict tests 3 | * on the content of the file as well. Any change to .jsdoc-config.json must be accompanied by valid test case in this 4 | * spec-sheet. 5 | */ 6 | const fs = require('fs'), 7 | expect = require('chai').expect; 8 | 9 | describe('JSDoc configuration', function () { 10 | var json, 11 | content, 12 | jsdocConfigPath = './.jsdoc-config.json'; 13 | 14 | it('should exist', function (done) { 15 | fs.stat(jsdocConfigPath, done); 16 | }); 17 | 18 | it('should have readable content', function () { 19 | expect(content = fs.readFileSync(jsdocConfigPath).toString(), 'Should have readable content').to.be.ok; 20 | }); 21 | 22 | it('should have valid JSON content', function () { 23 | expect(json = JSON.parse(content), 'Should have valid JSON content').to.be.ok; 24 | }); 25 | 26 | describe('tags', function () { 27 | it('should allow unknown tags', function () { 28 | expect(json.tags.allowUnknownTags, 'Should allow unknown tags').to.be.ok; 29 | }); 30 | 31 | it('should have jsdoc and closure dictionaries', function () { 32 | expect(json.tags.dictionaries).to.eql(['jsdoc', 'closure']); 33 | }); 34 | }); 35 | 36 | describe('source', function () { 37 | it('should have an include pattern', function () { 38 | expect(json.source.includePattern).to.equal('.+\\.js(doc)?$'); 39 | }); 40 | 41 | it('should have an exclude pattern', function () { 42 | expect(json.source.excludePattern).to.equal('(^|\\/|\\\\)_'); 43 | }); 44 | }); 45 | 46 | describe('plugins', function () { 47 | it('should have the markdown plugin', function () { 48 | expect(json.plugins, 'Should use the markdown plugin').to.include('plugins/markdown'); 49 | }); 50 | }); 51 | 52 | describe('templates', function () { 53 | it('should not have clever links', function () { 54 | expect(json.templates.cleverLinks).to.be.false; 55 | }); 56 | 57 | it('should not have monospace links', function () { 58 | expect(json.templates.monospaceLinks).to.be.false; 59 | }); 60 | 61 | it('should highlight tutorial code', function () { 62 | expect(json.templates.highlightTutorialCode).to.be.ok; 63 | }); 64 | }); 65 | 66 | describe('opts', function () { 67 | it('should use the Postman JSDoc theme', function () { 68 | expect(json.opts.template).to.equal('./node_modules/postman-jsdoc-theme'); 69 | }); 70 | 71 | it('should use UTF-8 encoding', function () { 72 | expect(json.opts.encoding).to.equal('utf8'); 73 | }); 74 | 75 | it('should create documentation in out/docs', function () { 76 | expect(json.opts.destination).to.equal('./out/docs'); 77 | }); 78 | 79 | it('should recurse', function () { 80 | expect(json.opts.recurse).to.be.ok; 81 | }); 82 | 83 | it('should have a valid readme', function () { 84 | expect(json.opts.readme, 'Should use a valid readme').to.equal('README.md'); 85 | }); 86 | }); 87 | 88 | describe('markdown', function () { 89 | it('should have a gfm parser', function () { 90 | expect(json.markdown.parser, 'Should use the gfm markdown parser').to.equal('gfm'); 91 | }); 92 | 93 | it('should have jsdoc and closure dictionaries', function () { 94 | expect(json.markdown.hardwrap).to.be.false; 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/system/npm-publish.test.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect, 2 | // eslint-disable-next-line security/detect-child-process 3 | { execSync: exec } = require('child_process'); 4 | 5 | describe('npm publish', function () { 6 | // @note npm pack won't refresh the .cache because of prepublishOnly 7 | // but we make sure it's refreshed before running tests using pretest script 8 | const packageInfo = JSON.parse(exec('npm pack --dry-run --json'))[0], 9 | packagedFiles = packageInfo.files.map(({ path }) => { return path; }); 10 | 11 | it('should have a valid package name', function () { 12 | expect(packageInfo.name).to.equal('postman-sandbox'); 13 | }); 14 | 15 | it('should include boot code in .cache', function () { 16 | expect(packagedFiles).to.include('.cache/bootcode.js'); 17 | expect(packagedFiles).to.include('.cache/bootcode.browser.js'); 18 | }); 19 | 20 | it('should not publish unnecessary files', function () { 21 | const allowedFiles = ['index.js', 'package.json', 'LICENSE.md', 'README.md', 'CHANGELOG.yaml']; 22 | 23 | packagedFiles.map((path) => { 24 | expect(allowedFiles.includes(path) || 25 | path.startsWith('lib/') || 26 | path.startsWith('types/') || 27 | path.startsWith('.cache/')).to.be.true; 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /test/unit/_bootstrap.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable mocha/no-top-level-hooks */ 2 | var chai = require('chai'), 3 | sinon = require('sinon'), 4 | sinonChai = require('sinon-chai'), 5 | 6 | _expect, 7 | _sinon; 8 | 9 | chai.use(sinonChai); 10 | 11 | before(function () { 12 | global.expect && (_expect = global.expect); 13 | global.expect = chai.expect; 14 | 15 | global.sinon && (_sinon = global.sinon); 16 | global.sinon = sinon; 17 | }); 18 | 19 | after(function () { 20 | _expect ? (global.expect = _expect) : (delete global.expect); 21 | _expect = null; 22 | 23 | _sinon ? (global.sinon = _sinon) : (delete global.sinon); 24 | _sinon = null; 25 | }); 26 | 27 | describe('_bootstrap', function () { 28 | this.timeout(1000 * 60); // set a timeout 29 | 30 | var Sandbox = require('../../lib'); 31 | 32 | it('bundling should work for sandbox', function (done) { 33 | // we simply create a context and run to ensure it is working 34 | Sandbox.createContext(function (err, ctx) { 35 | if (err) { return done(err); } 36 | ctx.on('error', done); 37 | ctx.ping(done); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/execution.test.js: -------------------------------------------------------------------------------- 1 | // @note: this is where we override prototype functions 2 | // to make sure sandbox added properties do not leak 3 | require('../../lib/sandbox/purse.js'); 4 | 5 | var _ = require('lodash'), 6 | Execution = require('../../lib/sandbox/execution'), 7 | pmAPI = require('../../lib/sandbox/pmapi'), 8 | execution, 9 | pm; // eslint-disable-line no-unused-vars 10 | 11 | 12 | describe('execution', function () { 13 | before(function () { 14 | execution = new Execution('id', { listen: 'test' }, {}, {}); 15 | pm = new pmAPI(execution, _.noop, _.noop); // eslint-disable-line no-unused-vars 16 | }); 17 | 18 | it('can be serialized', function () { 19 | var json; 20 | 21 | expect(function () { 22 | json = execution.toJSON(); 23 | }).to.not.throw(); 24 | expect(json).to.include.keys(['request', 'response']); 25 | }); 26 | 27 | it('does not leak sandbox helpers when serialized', function () { 28 | var json; 29 | 30 | expect(execution).to.have.nested.property('request.to'); 31 | expect(execution).to.have.nested.property('response.to'); 32 | 33 | json = execution.toJSON(); 34 | 35 | expect(json).to.not.have.nested.property('request.to'); 36 | expect(execution).to.have.nested.property('request.to'); 37 | expect(json).to.not.have.nested.property('response.to'); 38 | expect(execution).to.have.nested.property('response.to'); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/pm-variables-tracking.test.js: -------------------------------------------------------------------------------- 1 | var sdk = require('postman-collection'), 2 | Sandbox = require('../../lib'); 3 | 4 | describe('pm api variables', function () { 5 | this.timeout(1000 * 60); 6 | 7 | it('should have tracking enabled by default', function (done) { 8 | Sandbox.createContext(function (err, ctx) { 9 | if (err) { return done(err); } 10 | 11 | ctx.execute(` 12 | var assert = require('assert'); 13 | 14 | assert.equal(pm.variables.mutations.count(), 0); 15 | pm.variables.set('foo', 'foo'); 16 | assert.equal(pm.variables.mutations.count(), 1); 17 | 18 | assert.equal(pm.environment.mutations.count(), 0); 19 | pm.environment.set('foo', 'foo'); 20 | assert.equal(pm.environment.mutations.count(), 1); 21 | 22 | assert.equal(pm.globals.mutations.count(), 0); 23 | pm.globals.set('foo', 'foo'); 24 | assert.equal(pm.globals.mutations.count(), 1); 25 | 26 | assert.equal(pm.collectionVariables.mutations.count(), 0); 27 | pm.collectionVariables.set('foo', 'foo'); 28 | assert.equal(pm.collectionVariables.mutations.count(), 1); 29 | `, { 30 | context: {} 31 | }, done); 32 | }); 33 | }); 34 | 35 | it('should bubble mutations in result', function (done) { 36 | Sandbox.createContext(function (err, ctx) { 37 | if (err) { return done(err); } 38 | 39 | ctx.execute(` 40 | var assert = require('assert'); 41 | 42 | pm.variables.set('foo', '_variable'); 43 | pm.environment.set('foo', 'environment'); 44 | pm.globals.set('foo', 'global'); 45 | pm.collectionVariables.set('foo', 'collectionVariables'); 46 | `, { 47 | context: {} 48 | }, function (err, result) { 49 | if (err) { 50 | return done(err); 51 | } 52 | 53 | expect(result._variables.mutations).to.be.ok; 54 | expect(new sdk.MutationTracker(result._variables.mutations).count()).to.equal(1); 55 | 56 | expect(result.environment.mutations).to.be.ok; 57 | expect(new sdk.MutationTracker(result.environment.mutations).count()).to.equal(1); 58 | 59 | expect(result.globals.mutations).to.be.ok; 60 | expect(new sdk.MutationTracker(result.globals.mutations).count()).to.equal(1); 61 | 62 | expect(result.collectionVariables.mutations).to.be.ok; 63 | expect(new sdk.MutationTracker(result.collectionVariables.mutations).count()).to.equal(1); 64 | 65 | done(); 66 | }); 67 | }); 68 | }); 69 | 70 | it('should should drop initial mutations in context', function (done) { 71 | var scopeDefinition = { 72 | values: [ 73 | { key: 'bar', value: 'bar value' } 74 | ], 75 | mutations: { 76 | autoCompact: true, 77 | compacted: { 78 | bar: ['bar', 'bar value'] 79 | } 80 | } 81 | }; 82 | 83 | Sandbox.createContext({ debug: false }, function (err, ctx) { 84 | if (err) { return done(err); } 85 | 86 | ctx.execute(` 87 | var assert = require('assert'); 88 | 89 | assert.equal(pm.variables.get('bar'), 'bar value'); 90 | pm.variables.set('foo', '_variable'); 91 | 92 | assert.equal(pm.environment.get('bar'), 'bar value'); 93 | pm.environment.set('foo', 'environment'); 94 | 95 | assert.equal(pm.globals.get('bar'), 'bar value'); 96 | pm.globals.set('foo', 'global'); 97 | 98 | assert.equal(pm.collectionVariables.get('bar'), 'bar value'); 99 | pm.collectionVariables.set('foo', 'collectionVariables'); 100 | `, { 101 | context: { 102 | globals: scopeDefinition, 103 | _variables: scopeDefinition, 104 | environment: scopeDefinition, 105 | collectionVariables: scopeDefinition 106 | } 107 | }, function (err, result) { 108 | if (err) { 109 | return done(err); 110 | } 111 | 112 | expect(result._variables.mutations).to.be.ok; 113 | expect(new sdk.MutationTracker(result._variables.mutations).count()).to.equal(1); 114 | 115 | expect(result.environment.mutations).to.be.ok; 116 | expect(new sdk.MutationTracker(result.environment.mutations).count()).to.equal(1); 117 | 118 | expect(result.globals.mutations).to.be.ok; 119 | expect(new sdk.MutationTracker(result.globals.mutations).count()).to.equal(1); 120 | 121 | expect(result.collectionVariables.mutations).to.be.ok; 122 | expect(new sdk.MutationTracker(result.collectionVariables.mutations).count()).to.equal(1); 123 | 124 | done(); 125 | }); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/unit/sandbox-assertion-events.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox assertion events', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'), 4 | assestions = []; 5 | 6 | before(function (done) { 7 | var contextsExecuted = 0, 8 | doneCalled = false; 9 | 10 | Sandbox.createContext(function (err, ctx) { 11 | if (err) { return done(err); } 12 | 13 | ctx.on('execution.assertion', function (cursor, results) { 14 | assestions = assestions.concat(results); 15 | }); 16 | 17 | var callback = function (err) { 18 | contextsExecuted++; 19 | 20 | if (err && !doneCalled) { 21 | doneCalled = true; 22 | 23 | return done(err); 24 | } 25 | 26 | if (contextsExecuted === 2 && !doneCalled) { 27 | doneCalled = true; 28 | 29 | return done(); 30 | } 31 | }; 32 | 33 | ctx.execute(` 34 | pm.test("pass1", function () { 35 | pm.expect(123).to.be.a('number'); 36 | }); 37 | 38 | tests['fail2'] = undefined; 39 | 40 | pm.test("fail1", function () { 41 | throw new Error('sample error 1'); 42 | }); 43 | 44 | tests['pass2'] = true; 45 | `, {}, callback); 46 | 47 | ctx.execute(` 48 | pm.test("pass3", function () { 49 | pm.expect(123).to.be.a('number'); 50 | }); 51 | 52 | tests['fail5'] = undefined; 53 | tests['fail6'] = false; 54 | 55 | pm.test("fail3", function () { 56 | throw new Error('sample error 2'); 57 | }); 58 | 59 | pm.test("fail4", function () { 60 | pm.expect('a').to.equal('b'); 61 | }); 62 | 63 | tests['pass4'] = true; 64 | `, {}, callback); 65 | }); 66 | }); 67 | 68 | it('should be indexed across parallel executions', function () { 69 | expect(assestions.map(function (test) { return test.index; })).to.eql([0, 1, 2, 3, 0, 1, 2, 3, 4, 5]); 70 | }); 71 | 72 | it('should be called for async and sync assertions', function () { 73 | expect(assestions.length).to.equal(10); 74 | 75 | // async tests assertions for 1st execution in order 76 | expect(assestions[0]).to.deep.include({ 77 | name: 'pass1', 78 | passed: true, 79 | error: null 80 | }); 81 | expect(assestions[1]).to.deep.include({ 82 | name: 'fail1', 83 | passed: false, 84 | error: { 85 | type: 'Error', 86 | name: 'Error', 87 | message: 'sample error 1' 88 | } 89 | }); 90 | 91 | // sync tests assestions for 1st execution in order 92 | expect(assestions[2]).to.deep.nested.include({ 93 | name: 'fail2', 94 | passed: false, 95 | 'error.name': 'AssertionError', 96 | 'error.message': 'expected undefined to be truthy' 97 | }); 98 | expect(assestions[3]).to.deep.include({ 99 | name: 'pass2', 100 | passed: true, 101 | error: null 102 | }); 103 | 104 | // async tests assertions for 2nd execution in order 105 | expect(assestions[4]).to.deep.include({ 106 | name: 'pass3', 107 | passed: true, 108 | error: null 109 | }); 110 | expect(assestions[5]).to.deep.nested.include({ 111 | name: 'fail3', 112 | passed: false, 113 | 'error.name': 'Error', 114 | 'error.message': 'sample error 2' 115 | }); 116 | expect(assestions[6]).to.deep.nested.include({ 117 | name: 'fail4', 118 | passed: false, 119 | 'error.name': 'AssertionError', 120 | 'error.message': 'expected \'a\' to equal \'b\'' 121 | }); 122 | 123 | // sync tests assestions for 2nd execution in order 124 | expect(assestions[7]).to.deep.nested.include({ 125 | name: 'fail5', 126 | passed: false, 127 | 'error.name': 'AssertionError', 128 | 'error.message': 'expected undefined to be truthy' 129 | }); 130 | expect(assestions[8]).to.deep.nested.include({ 131 | name: 'fail6', 132 | passed: false, 133 | 'error.name': 'AssertionError', 134 | 'error.message': 'expected false to be truthy' 135 | }); 136 | expect(assestions[9]).to.deep.include({ 137 | name: 'pass4', 138 | passed: true, 139 | error: null 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /test/unit/sandbox-codemarkers.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox "sandbox2" code markers', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'); 4 | 5 | it('should not expose legacy variables when "use sandbox2" is specified', function (done) { 6 | Sandbox.createContext(function (err, ctx) { 7 | if (err) { return done(err); } 8 | 9 | ctx.execute(`"use sandbox2"; 10 | var assert = require('assert'); 11 | assert.equal(typeof _, 'undefined', 'lodash should be undefined'); 12 | assert.equal(typeof postman, 'undefined', 'postman legacy interface should be down'); 13 | `, done); 14 | }); 15 | }); 16 | 17 | it('should ensure that major generic globals is still available in sandbox2 mode', function (done) { 18 | Sandbox.createContext(function (err, ctx) { 19 | if (err) { return done(err); } 20 | 21 | ctx.execute(`"use sandbox2"; 22 | var assert = require('assert'); 23 | assert.equal(typeof Buffer, 'function', 'Buffer should be available'); 24 | assert.equal(typeof pm, 'object', 'pm object should be available'); 25 | `, done); 26 | }); 27 | }); 28 | 29 | it('should ensure that legacy test target globals are not present', function (done) { 30 | Sandbox.createContext(function (err, ctx) { 31 | if (err) { return done(err); } 32 | 33 | ctx.execute({ 34 | listen: 'test', 35 | script: `"use sandbox2"; 36 | var assert = require('assert'); 37 | assert.equal(typeof tests, 'undefined', 'test object should be undefiend'); 38 | ` 39 | }, done); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/sandbox-dispose.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox disposal', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'); 4 | 5 | it('should work', function (done) { 6 | Sandbox.createContext({ debug: false }, function (err, context) { 7 | if (err) { return done(err); } 8 | context.on('error', done); 9 | 10 | var status = { 11 | started: false, 12 | disconnected: false, 13 | misfiredTimer: false 14 | }; 15 | 16 | context.on('console', function (cur, lvl, msg) { 17 | switch (msg) { 18 | case 'started': 19 | status.started = true; 20 | 21 | setTimeout(context.dispose.bind(context), 1); 22 | setTimeout(function () { 23 | expect(status).to.deep.include({ 24 | started: true, 25 | disconnected: true, 26 | misfiredTimer: false 27 | }); 28 | done(); 29 | }, 100); 30 | break; 31 | 32 | case 'timeout': 33 | status.misfiredTimer = true; 34 | done(new Error('expected sandbox timeout to be cleared')); 35 | break; 36 | 37 | default: 38 | done(new Error('unexpected console communication from sandbox')); 39 | } 40 | }); 41 | 42 | context.execute(` 43 | console.log('started'); 44 | 45 | setTimeout(function () { 46 | console.log('timeout'); 47 | }, 50); 48 | `, { 49 | timeout: 1000 50 | }, function (err) { 51 | status.disconnected = true; 52 | expect(err).to.have.property('message', 'sandbox: execution interrupted, bridge disconnecting.'); 53 | }); 54 | }); 55 | }); 56 | 57 | it('should clear running intervals', function (done) { 58 | Sandbox.createContext(function (err, ctx) { 59 | expect(err).to.not.exist; 60 | 61 | var intervals = { 62 | terminal: -1, 63 | current: 0 64 | }; 65 | 66 | ctx.on('console', function (cursor, level, message) { 67 | (message === 'tick') && (intervals.current += 1); 68 | (message === 'timeout') && ctx.dispose(); 69 | }); 70 | 71 | ctx.on('error', done); 72 | 73 | ctx.execute(` 74 | setInterval(function () { 75 | console.log('tick'); 76 | }, 25); 77 | 78 | setTimeout(function () { 79 | console.log('timeout'); 80 | }, 125); 81 | `, function (err) { 82 | expect(err).to.have.property('message', 'sandbox: execution interrupted, bridge disconnecting.'); 83 | expect(intervals.current).to.be.above(0); 84 | intervals.terminal = intervals.current; 85 | 86 | setTimeout(function () { 87 | expect(intervals.current).to.equal(intervals.terminal); 88 | done(); 89 | }, 100); 90 | }); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /test/unit/sandbox-error-events.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox error events', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'); 4 | 5 | it('should throw generic execution.error event on error', function (done) { 6 | Sandbox.createContext(function (err, ctx) { 7 | if (err) { return done(err); } 8 | 9 | ctx.on('execution.error', function (cursor, err) { 10 | expect(cursor).to.have.property('execution', 'my-execution-id'); 11 | expect(err).to.have.property('message', 'this will regurgitate!'); 12 | done(); 13 | }); 14 | 15 | ctx.execute('throw new Error("this will regurgitate!")', { 16 | id: 'my-execution-id' 17 | }, function () {}); // eslint-disable-line no-empty-function 18 | }); 19 | }); 20 | 21 | it('should throw execution specific execution.error.:id event on error', function (done) { 22 | Sandbox.createContext(function (err, ctx) { 23 | if (err) { return done(err); } 24 | 25 | ctx.on('execution.error.my-execution-id', function (cursor, err) { 26 | expect(cursor).to.have.property('execution', 'my-execution-id'); 27 | expect(err).to.have.property('message', 'this will regurgitate!'); 28 | done(); 29 | }); 30 | 31 | ctx.execute('throw new Error("this will regurgitate!")', { 32 | id: 'my-execution-id' 33 | }, function () {}); // eslint-disable-line no-empty-function 34 | }); 35 | }); 36 | 37 | it('should forward unhandled promise rejection errors', function (done) { 38 | Sandbox.createContext(function (err, ctx) { 39 | if (err) { return done(err); } 40 | 41 | const executionError = sinon.spy(), 42 | executionErrorSpecific = sinon.spy(); 43 | 44 | ctx.on('execution.error', executionError); 45 | ctx.on('execution.error.exec-id', executionErrorSpecific); 46 | 47 | ctx.execute(` 48 | async function makeMeThrow () { 49 | await Promise.reject(new Error('catch me if you can')); 50 | } 51 | 52 | makeMeThrow(); 53 | `, { id: 'exec-id' }, function (err) { 54 | expect(err).to.be.null; 55 | 56 | expect(executionError).to.have.been.calledOnce; 57 | expect(executionErrorSpecific).to.have.been.calledOnce; 58 | 59 | expect(executionError.args[0][0]).to.be.an('object').and.have.property('execution', 'exec-id'); 60 | expect(executionError.args[0][1]).to.be.an('object') 61 | .and.have.property('message', 'catch me if you can'); 62 | 63 | expect(executionErrorSpecific.args[0][0]).to.be.an('object').and.have.property('execution', 'exec-id'); 64 | expect(executionErrorSpecific.args[0][1]).to.be.an('object') 65 | .and.have.property('message', 'catch me if you can'); 66 | 67 | done(); 68 | }); 69 | }); 70 | }); 71 | 72 | it('should forward errors from top level await', function (done) { 73 | Sandbox.createContext(function (err, ctx) { 74 | if (err) { return done(err); } 75 | 76 | const executionError = sinon.spy(), 77 | executionErrorSpecific = sinon.spy(); 78 | 79 | ctx.on('execution.error', executionError); 80 | ctx.on('execution.error.exec-id', executionErrorSpecific); 81 | ctx.on('error', done); 82 | 83 | ctx.execute(` 84 | async function makeMeThrow () { 85 | return Promise.reject(new Error('catch me if you can')); 86 | } 87 | 88 | await makeMeThrow(); 89 | `, { id: 'exec-id' }, function (err) { 90 | expect(err).to.be.an('object').and.have.property('message', 'catch me if you can'); 91 | 92 | expect(executionError).to.have.been.calledOnce; 93 | expect(executionErrorSpecific).to.have.been.calledOnce; 94 | 95 | expect(executionError.args[0][0]).to.be.an('object').and.have.property('execution', 'exec-id'); 96 | expect(executionError.args[0][1]).to.be.an('object') 97 | .and.have.property('message', 'catch me if you can'); 98 | 99 | expect(executionErrorSpecific.args[0][0]).to.be.an('object').and.have.property('execution', 'exec-id'); 100 | expect(executionErrorSpecific.args[0][1]).to.be.an('object') 101 | .and.have.property('message', 'catch me if you can'); 102 | 103 | done(); 104 | }); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/ajv.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - AJV', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var Ajv = require('ajv'), 21 | assert = require('assert'); 22 | 23 | assert.strictEqual(typeof Ajv, 'function', 'typeof Ajv must be function'); 24 | assert.strictEqual(typeof new Ajv(), 'object', 'typeof new Ajv() must be object'); 25 | `, done); 26 | }); 27 | 28 | describe('methods', function () { 29 | it('validate', function (done) { 30 | context.execute(` 31 | var Ajv = require('ajv'), 32 | assert = require('assert'), 33 | 34 | ajv = new Ajv(), 35 | 36 | schema = { 37 | "$schema": "http://json-schema.org/draft-07/schema#", 38 | "type": "object", 39 | "properties": { 40 | "alpha": { 41 | "type": "boolean" 42 | } 43 | } 44 | }; 45 | 46 | assert(ajv.validate(schema, {alpha: true}), 'AJV schema validation must identify valid objects'); 47 | `, done); 48 | }); 49 | 50 | it('compile', function (done) { 51 | context.execute(` 52 | var Ajv = require('ajv'), 53 | assert = require('assert'), 54 | 55 | ajv = new Ajv(), 56 | 57 | schema = { 58 | "$schema": "http://json-schema.org/draft-07/schema#", 59 | "type": "object", 60 | "properties": { 61 | "alpha": { 62 | "type": "boolean" 63 | } 64 | } 65 | }, 66 | 67 | validate = ajv.compile(schema); 68 | 69 | assert(validate({alpha: true}), 'AJV schema validation must identify valid objects'); 70 | `, done); 71 | }); 72 | 73 | it('compileAsync', function (done) { 74 | context.execute(` 75 | var Ajv = require('ajv'), 76 | 77 | SCHEMAS = { 78 | 'https://schema.getpostman.com/collection.json': { 79 | $id: 'https://schema.getpostman.com/collection.json', 80 | required: ['request'], 81 | properties: { 82 | name: {type: 'string'}, 83 | request: {$ref: 'request.json'} 84 | } 85 | }, 86 | 'https://schema.getpostman.com/request.json': { 87 | $id: 'https://schema.getpostman.com/request.json', 88 | required: ['url'], 89 | properties: { 90 | method: {type: 'string'}, 91 | url: {$ref: 'url.json'} 92 | } 93 | }, 94 | 'https://schema.getpostman.com/url.json': { 95 | $id: 'https://schema.getpostman.com/url.json', 96 | properties: { 97 | protocol: {type: 'string'}, 98 | host: {type: 'string'} 99 | } 100 | } 101 | }, 102 | 103 | ajv = new Ajv({ 104 | loadSchema: function (uri) { 105 | return new Promise(function (resolve, reject) { 106 | setTimeout(function () { 107 | SCHEMAS[uri] ? resolve(SCHEMAS[uri]) : reject(new Error('404')); 108 | }, 10); 109 | }); 110 | } 111 | }); 112 | 113 | ajv.compileAsync(SCHEMAS['https://schema.getpostman.com/collection.json']) 114 | .then(function (validate) { 115 | const valid = validate({ 116 | name: 'test', 117 | request: { 118 | method: 'GET', 119 | url: 'https://getpostman.com' 120 | } 121 | }); 122 | pm.globals.set('valid', valid); 123 | }); 124 | `, { 125 | timeout: 200 126 | }, function (err, res) { 127 | if (err) { return done(err); } 128 | 129 | expect(err).to.be.null; 130 | expect(res).to.nested.include({ 131 | 'return.async': true 132 | }); 133 | 134 | expect(res).to.have.property('globals').that.has.property('values').that.is.an('array'); 135 | expect(res.globals.values[0].value).to.be.true; 136 | done(); 137 | }); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/cheerio.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - Cheerio', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context, 5 | markup = ` 6 |
  • Apple
  • Orange
  • Pear
7 | `; 8 | 9 | beforeEach(function (done) { 10 | Sandbox.createContext(function (err, ctx) { 11 | context = ctx; 12 | done(err); 13 | }); 14 | }); 15 | 16 | afterEach(function () { 17 | context.dispose(); 18 | context = null; 19 | }); 20 | 21 | it('should exist', function (done) { 22 | context.execute(` 23 | var assert = require('assert'); 24 | 25 | assert.strictEqual(typeof cheerio, 'function', 'typeof cheerio must be object'); 26 | assert.strictEqual(typeof cheerio.load, 'function', 'typeof cheerio.load must be function'); 27 | `, done); 28 | }); 29 | 30 | describe('basic functionality', function () { 31 | it('should work with selectors', function (done) { 32 | context.execute(` 33 | var assert = require('assert'), 34 | $ = cheerio.load(\`${markup}\`); 35 | 36 | assert.strictEqual($('.apple', '#fruits').text(), 'Apple', 'Text extraction must work correctly'); 37 | assert.strictEqual($('ul .pear').attr('class'), 'pear', 'Attribute extraction must work correctly'); 38 | assert.strictEqual($('li[class=orange]').html(), 'Orange', 'Attribute extraction must work correctly'); 39 | `, done); 40 | }); 41 | 42 | it('should work with attributes', function (done) { 43 | context.execute(` 44 | var assert = require('assert'), 45 | $ = cheerio.load(\`${markup}\`); 46 | 47 | assert($('li').hasClass('pear'), 'Class missing'); 48 | assert.strictEqual($('ul').attr('id'), 'fruits', 'Attributes must work correctly'); 49 | assert.strictEqual($.html($('.apple').addClass('fruit red')), '
  • Apple
  • ', 50 | 'Attributes should be mutated correctly'); 51 | assert.strictEqual($.html($('.pear').removeAttr('class')), '
  • Pear
  • ', 'HTML must be valid'); 52 | `, done); 53 | }); 54 | 55 | it('should work with traversals', function (done) { 56 | context.execute(` 57 | var assert = require('assert'), 58 | $ = cheerio.load(\`${markup}\`); 59 | 60 | assert.strictEqual($('#fruits').find('li').length, 3, 'Array representation must be valid'); 61 | assert.strictEqual($('#fruits').find($('.apple')).length, 1, 'Element filtering must be valid'); 62 | assert.strictEqual($('.pear').parent().attr('id'), 'fruits', 'Parent traversal must be valid'); 63 | assert($('.apple').next().hasClass('orange'), 'Adjacent node traversal must be valid'); 64 | assert.strictEqual($('.pear').siblings().length, 2, 'Sibling traversal must be valid'); 65 | assert.strictEqual($('#fruits').contents().length, 3, 'Child node traversal must be valid'); 66 | `, done); 67 | }); 68 | 69 | it('should work with renders', function (done) { 70 | context.execute(` 71 | var assert = require('assert'), 72 | $ = cheerio.load(\`${markup}\`); 73 | 74 | assert.strictEqual($.html(), \`${markup}\`, 'HTML render must be valid'); 75 | `, done); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/crypto.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - CryptoJS', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | 22 | assert.strictEqual(typeof CryptoJS, 'object', 'typeof CryptoJS must be object'); 23 | assert.strictEqual(typeof CryptoJS.AES.encrypt, 'function', 'typeof CryptoJS.AES.encrypt must be function'); 24 | assert.strictEqual(typeof CryptoJS.AES.decrypt, 'function', 'typeof CryptoJS.AES.decrypt must be function'); 25 | `, done); 26 | }); 27 | 28 | it('should have basic functionality working', function (done) { 29 | context.execute(` 30 | var assert = require('assert'), 31 | ciphertext = CryptoJS.AES.encrypt('my message', 'secret key 123'), 32 | 33 | bytes = CryptoJS.AES.decrypt(ciphertext.toString(), 'secret key 123'), 34 | plaintext = bytes.toString(CryptoJS.enc.Utf8); 35 | 36 | assert.strictEqual(plaintext, 'my message', 'Encryption-decryption must be valid'); 37 | `, done); 38 | }); 39 | 40 | describe('random generation', function () { 41 | it('should work correctly for strings', function (done) { 42 | context.execute(` 43 | var assert = require('assert'); 44 | 45 | assert.strictEqual(CryptoJS.lib.WordArray.create(8).words, 8, 'Random byte order must match provision'); 46 | `, done); 47 | }); 48 | 49 | it('should work correctly for bytes', function (done) { 50 | context.execute(` 51 | var assert = require('assert'), 52 | rand = CryptoJS.lib.WordArray.random, 53 | randomSample = rand(8); 54 | 55 | assert.deepEqual(rand(), {words: [], sigBytes: 0}, 'Random bytes must be empty without a range'); 56 | assert(Array.isArray(randomSample.words), 'Random byte order must have an array of random words'); 57 | assert(randomSample.words.length, 'Random byte order array must be non empty for valid range'); 58 | assert.strictEqual(rand(8).sigBytes, 8, 'Random byte order must match provision'); 59 | assert.notDeepEqual(rand(8), rand(8), 'Random bytes must be non-identical'); 60 | `, done); 61 | }); 62 | 63 | it('should correctly concatenate sequences', function (done) { 64 | context.execute(` 65 | var assert = require('assert'), 66 | wordArray = CryptoJS.lib.WordArray.create([0x12345678], 4); 67 | 68 | assert.strictEqual(wordArray.concat(CryptoJS.lib.WordArray.create([0x12345678], 3)).toString(), 69 | '12345678123456', 'Concat must merge into a sequence'); 70 | assert.strictEqual(wordArray.toString(), '12345678123456', 'Concat must mutate the WordArray'); 71 | `, done); 72 | }); 73 | 74 | it('should correctly concatenate long sequences', function (done) { 75 | context.execute(` 76 | var assert = require('assert'), 77 | alpha = CryptoJS.lib.WordArray.create(), 78 | beta = CryptoJS.lib.WordArray.create(), 79 | gamma = CryptoJS.lib.WordArray.create(); 80 | 81 | for (var i = 0; i < 500000; i++) { beta.words[i] = gamma.words[i] = i; } 82 | beta.sigBytes = gamma.sigBytes = 500000; 83 | 84 | assert.strictEqual(beta.toString() + gamma.toString(), alpha.concat(beta.concat(gamma)).toString(), 85 | 'Sequence concatenation must be consistent'); 86 | `, done); 87 | }); 88 | 89 | it('should correctly clamp random bytes', function (done) { 90 | context.execute(` 91 | var assert = require('assert'), 92 | wordArray = CryptoJS.lib.WordArray.create([0x12345678, 0x12345678], 3); 93 | 94 | wordArray.clamp(); 95 | assert.strictEqual([0x12345600].toString(), wordArray.words.toString(), 'Clamped bytes must match'); 96 | `, done); 97 | }); 98 | 99 | it('should correctly clone random bytes', function (done) { 100 | context.execute(` 101 | var assert = require('assert'), 102 | wordArray = CryptoJS.lib.WordArray.create([0x12345678]), 103 | clone = wordArray.clone(); 104 | 105 | clone.words[0] = 0; 106 | assert.notEqual(wordArray.toString(), clone.toString(), 'Byte cloning must be deep'); 107 | `, done); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/csv-parse.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - csv-parse/lib/sync', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'), 21 | csvParse = require('csv-parse/lib/sync'); 22 | 23 | assert.strictEqual(typeof csvParse, 'function', 'typeof csv-parse must be function'); 24 | `, done); 25 | }); 26 | 27 | describe('basic functionality', function () { 28 | it('should work correctly for rudimentary csv data', function (done) { 29 | context.execute(` 30 | var assert = require('assert'), 31 | csvParse = require('csv-parse/lib/sync'), 32 | 33 | data = csvParse('foo'); 34 | 35 | assert.deepStrictEqual(data, [['foo']], 'csv-parse must work correctly'); 36 | `, done); 37 | }); 38 | 39 | it('should work correctly for a singelton set', function (done) { 40 | context.execute(` 41 | var assert = require('assert'), 42 | csvParse = require('csv-parse/lib/sync'), 43 | 44 | data = csvParse('foo\\n123'); 45 | 46 | assert.deepStrictEqual(data, [['foo'], ['123']], 'csv-parse must work correctly'); 47 | `, done); 48 | }); 49 | 50 | it('should handle malformed input correctly', function (done) { 51 | context.execute(` 52 | var assert = require('assert'), 53 | csvParse = require('csv-parse/lib/sync'); 54 | 55 | assert.deepStrictEqual(csvParse('foo,bar\\n123'), [['foo', 'bar']]); 56 | `, done); 57 | }); 58 | }); 59 | 60 | describe('options', function () { 61 | it('should correctly treat the first row as a header', function (done) { 62 | context.execute(` 63 | var assert = require('assert'), 64 | csvParse = require('csv-parse/lib/sync'), 65 | 66 | data = csvParse('foo\\n123', { columns: true }); 67 | 68 | assert.deepStrictEqual(data, [{foo: '123'}], 'Column headers must be treated correctly'); 69 | `, done); 70 | }); 71 | 72 | it('should correctly handle custom escape sequences', function (done) { 73 | context.execute(` 74 | var assert = require('assert'), 75 | csvParse = require('csv-parse/lib/sync'), 76 | 77 | data = csvParse('foo,bar\\n"alpha","b/"et/"a"', { escape: '/' }); 78 | 79 | assert.deepStrictEqual(data, [['foo','bar'],['alpha','b"et"a']], 80 | 'Custom escape sequences must be respected'); 81 | `, done); 82 | }); 83 | 84 | it('should correctly parse stringified numbers', function (done) { 85 | context.execute(` 86 | var assert = require('assert'), 87 | csvParse = require('csv-parse/lib/sync'), 88 | 89 | data = csvParse('foo\\n123', { auto_parse: true }); 90 | 91 | assert.deepStrictEqual(data, [['foo'], [123]], 'Stringified numbers must be parsed correctly'); 92 | `, done); 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/deprecation.test.js: -------------------------------------------------------------------------------- 1 | const env = require('../../../lib/environment'), 2 | DEPRECATED_LIBS = ['atob', 'btoa', 'crypto-js', 'tv4', 'backbone']; 3 | 4 | // TODO: Restore this once the deprecation warnings are added back 5 | // Revert PR:https://github.com/postmanlabs/postman-sandbox/pull/1086 6 | describe.skip('sandbox library - deprecation', function () { 7 | this.timeout(1000 * 60); 8 | var Sandbox = require('../../../'), 9 | context; 10 | 11 | beforeEach(function (done) { 12 | Sandbox.createContext({}, function (err, ctx) { 13 | context = ctx; 14 | done(err); 15 | }); 16 | }); 17 | 18 | afterEach(function () { 19 | context.dispose(); 20 | context = null; 21 | }); 22 | 23 | it('should show deprecation warning for "tv4"', function (done) { 24 | const consoleSpy = sinon.spy(); 25 | 26 | context.on('console', consoleSpy); 27 | context.execute(` 28 | const requiredTv4 = require('tv4'); 29 | tv4.validate; 30 | `, function (err) { 31 | if (err) { 32 | return done(err); 33 | } 34 | 35 | expect(consoleSpy).to.be.calledTwice; 36 | 37 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 38 | expect(consoleSpy.firstCall.args[2]) 39 | .to.equal('Using "tv4" library is deprecated. Use "ajv" library instead.'); 40 | 41 | expect(consoleSpy.secondCall.args[1]).to.equal('warn'); 42 | expect(consoleSpy.secondCall.args[2]) 43 | .to.equal('Using "tv4" is deprecated. Use "require(\'ajv\')" instead.'); 44 | 45 | done(); 46 | }); 47 | }); 48 | 49 | it('should show deprecation warning for "crypto-js"', function (done) { 50 | const consoleSpy = sinon.spy(); 51 | 52 | context.on('console', consoleSpy); 53 | context.execute(` 54 | const requiredCryptoJS = require('crypto-js'); 55 | CryptoJS.lib; 56 | `, function (err) { 57 | if (err) { 58 | return done(err); 59 | } 60 | 61 | expect(consoleSpy).to.be.calledTwice; 62 | 63 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 64 | expect(consoleSpy.firstCall.args[2]) 65 | .to.equal('Using "crypto-js" library is deprecated. Use global "crypto" object instead.'); 66 | 67 | expect(consoleSpy.secondCall.args[1]).to.equal('warn'); 68 | expect(consoleSpy.secondCall.args[2]) 69 | .to.equal('Using "CryptoJS" is deprecated. Use global "crypto" object instead.'); 70 | 71 | done(); 72 | }); 73 | }); 74 | 75 | 76 | it('should show deprecation warning for "backbone', function (done) { 77 | const consoleSpy = sinon.spy(); 78 | 79 | context.on('console', consoleSpy); 80 | context.execute(` 81 | const requiredBackbone = require('backbone'); 82 | Backbone.Backbone; 83 | `, function (err) { 84 | if (err) { 85 | return done(err); 86 | } 87 | 88 | expect(consoleSpy).to.be.calledTwice; 89 | 90 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 91 | expect(consoleSpy.firstCall.args[2]) 92 | .to.equal('Using "backbone" library is deprecated.'); 93 | 94 | expect(consoleSpy.secondCall.args[1]).to.equal('warn'); 95 | expect(consoleSpy.secondCall.args[2]) 96 | .to.equal('Using "Backbone" is deprecated.'); 97 | 98 | done(); 99 | }); 100 | }); 101 | 102 | it('should show deprecation warning for "atob', function (done) { 103 | const consoleSpy = sinon.spy(); 104 | 105 | context.on('console', consoleSpy); 106 | context.execute(` 107 | const requiredAtob = require('atob'); 108 | atob('YQ=='); 109 | `, function (err) { 110 | if (err) { 111 | return done(err); 112 | } 113 | 114 | expect(consoleSpy).to.be.calledOnce; 115 | 116 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 117 | expect(consoleSpy.firstCall.args[2]) 118 | .to.equal('Using "atob" library is deprecated. Use global "atob" function instead.'); 119 | 120 | done(); 121 | }); 122 | }); 123 | 124 | it('should show deprecation warning for "btoa', function (done) { 125 | const consoleSpy = sinon.spy(); 126 | 127 | context.on('console', consoleSpy); 128 | context.execute(` 129 | const requiredBtoa = require('btoa'); 130 | btoa('a'); 131 | `, function (err) { 132 | if (err) { 133 | return done(err); 134 | } 135 | 136 | expect(consoleSpy).to.be.calledOnce; 137 | 138 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 139 | expect(consoleSpy.firstCall.args[2]) 140 | .to.equal('Using "btoa" library is deprecated. Use global "btoa" function instead.'); 141 | 142 | done(); 143 | }); 144 | }); 145 | 146 | it('should not show warning for non-deprecated libraries', function (done) { 147 | const consoleSpy = sinon.spy(), 148 | libs = Object.entries(env.require).map(([key, value]) => { 149 | return value.expose ?? key; 150 | }), 151 | code = libs.map((lib) => { 152 | return !DEPRECATED_LIBS.includes(lib) ? `require('${lib}');` : ''; 153 | }).join('\n'); 154 | 155 | 156 | context.on('console', consoleSpy); 157 | context.execute(code, function (err) { 158 | if (err) { 159 | return done(err); 160 | } 161 | 162 | expect(consoleSpy).to.not.be.called; 163 | done(); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/legacy.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - legacy', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should not show a warning if no legacy vars used', function (done) { 19 | const consoleSpy = sinon.spy(); 20 | 21 | context.on('console', consoleSpy); 22 | context.execute(` 23 | pm.iterationData.get('foo'); 24 | `, function (err) { 25 | if (err) { 26 | return done(err); 27 | } 28 | 29 | expect(consoleSpy).to.not.be.called; 30 | done(); 31 | }); 32 | }); 33 | 34 | it('should show warning on using legacy vars', function (done) { 35 | const consoleSpy = sinon.spy(); 36 | 37 | context.on('console', consoleSpy); 38 | context.execute(` 39 | data['foo'] = 'bar'; 40 | `, function (err) { 41 | if (err) { 42 | return done(err); 43 | } 44 | 45 | expect(consoleSpy).to.be.calledOnce; 46 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 47 | expect(consoleSpy.firstCall.args[2]) 48 | .to.equal('Using "data" is deprecated. Use "pm.iterationData" instead.'); 49 | done(); 50 | }); 51 | }); 52 | 53 | it('should show a warning on using legacy functions', function (done) { 54 | const consoleSpy = sinon.spy(); 55 | 56 | context.on('console', consoleSpy); 57 | context.execute(` 58 | cheerio.load('foo'); 59 | `, function (err) { 60 | if (err) { 61 | return done(err); 62 | } 63 | 64 | expect(consoleSpy).to.be.calledOnce; 65 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 66 | expect(consoleSpy.firstCall.args[2]) 67 | .to.equal('Using "cheerio" is deprecated. Use "require(\'cheerio\')" instead.'); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('should show a single warning per execution for each global', function (done) { 73 | const consoleSpy = sinon.spy(); 74 | 75 | context.on('console', consoleSpy); 76 | context.execute(` 77 | data['foo'] = 'bar1'; 78 | data['foo'] = 'bar2'; 79 | environment['foo'] = 'bar'; 80 | `, function (err) { 81 | if (err) { 82 | return done(err); 83 | } 84 | 85 | expect(consoleSpy).to.be.calledTwice; 86 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 87 | expect(consoleSpy.firstCall.args[2]) 88 | .to.equal('Using "data" is deprecated. Use "pm.iterationData" instead.'); 89 | 90 | expect(consoleSpy.secondCall.args[1]).to.equal('warn'); 91 | expect(consoleSpy.secondCall.args[2]) 92 | .to.equal('Using "environment" is deprecated. Use "pm.environment" instead.'); 93 | done(); 94 | }); 95 | }); 96 | 97 | it('should have special handling for postman.*', function (done) { 98 | const consoleSpy = sinon.spy(); 99 | 100 | context.on('console', consoleSpy); 101 | context.execute(` 102 | postman.setNextRequest('abc'); 103 | postman.setEnvironmentVariable('foo', 'bar'); 104 | `, function (err) { 105 | if (err) { 106 | return done(err); 107 | } 108 | 109 | expect(consoleSpy).to.be.calledTwice; 110 | expect(consoleSpy.firstCall.args[1]).to.equal('warn'); 111 | expect(consoleSpy.firstCall.args[2]) 112 | .to.equal('Using "postman.setNextRequest" is deprecated. Use "pm.execution.setNextRequest()" instead.'); 113 | expect(consoleSpy.secondCall.args[1]).to.equal('warn'); 114 | expect(consoleSpy.secondCall.args[2]) 115 | .to.equal('Using "postman.setEnvironmentVariable" is deprecated. Use "pm.environment.set()" instead.'); 116 | done(); 117 | }); 118 | }); 119 | 120 | it('should support "responseBody" with size upto 50MB', function (done) { 121 | context.execute({ 122 | listen: 'test', 123 | script: ` 124 | const assert = require('assert'); 125 | assert.strictEqual( 126 | responseBody, 127 | Buffer.alloc(50 * 1024 * 1024, 'a').toString(), 128 | 'responseBody <= 50MB should be available' 129 | ); 130 | ` 131 | }, { 132 | context: { 133 | response: { 134 | stream: { 135 | type: 'Base64', 136 | data: Buffer.alloc(50 * 1024 * 1024, 'a').toString('base64') 137 | } 138 | } 139 | } 140 | }, done); 141 | }); 142 | 143 | it('should truncate "responseBody" with size > 50MB', function (done) { 144 | context.execute({ 145 | listen: 'test', 146 | script: ` 147 | const assert = require('assert'); 148 | assert.strictEqual(typeof responseBody, 'undefined', 'responseBody > 50MB should not be available'); 149 | ` 150 | }, { 151 | context: { 152 | response: { 153 | stream: { 154 | type: 'Base64', 155 | data: Buffer.alloc(51 * 1024 * 1024, 'a').toString('base64') 156 | } 157 | } 158 | } 159 | }, done); 160 | }); 161 | }); 162 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/liquid-json.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - liquid-json', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('parses JSON object to string', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | assert.strictEqual(JSON.stringify({x:1}), '{"x":1}'); 22 | `, done); 23 | }); 24 | 25 | it('parses JSON as string', function (done) { 26 | context.execute(` 27 | var assert = require('assert'); 28 | assert.deepStrictEqual(JSON.parse('{"x":1}'), {x:1}); 29 | `, done); 30 | }); 31 | 32 | it('correctly removes UTF-8 BOM', function (done) { 33 | context.execute(` 34 | var assert = require('assert'); 35 | assert.deepStrictEqual(JSON.parse('{"x":1}'), {x:1}); 36 | `, done); 37 | }); 38 | 39 | it('correctly removes UTF-16 BOM', function (done) { 40 | context.execute(` 41 | var assert = require('assert'); 42 | assert.deepStrictEqual(JSON.parse(String.fromCharCode(0xFEFF) + '{"x":1}'), {x:1}); 43 | `, done); 44 | }); 45 | 46 | it('correctly removes big endian UTF-16 BOM', function (done) { 47 | context.execute(` 48 | var assert = require('assert'); 49 | assert.deepStrictEqual(JSON.parse('þÿ{"x":1}'), {x:1}); 50 | `, done); 51 | }); 52 | 53 | it('correctly removes little endian UTF-16 BOM', function (done) { 54 | context.execute(` 55 | var assert = require('assert'); 56 | assert.deepStrictEqual(JSON.parse('ÿþ{"x":1}'), {x:1}); 57 | `, done); 58 | }); 59 | 60 | it('correctly removes UTF-32 BOM', function (done) { 61 | context.execute(` 62 | var assert = require('assert'); 63 | assert.deepStrictEqual(JSON.parse('뮿{"x":1}'), {x:1}); 64 | `, done); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/lodash3.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - lodash3', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | assert.strictEqual(typeof _, 'function', 'typeof _ must be function'); 22 | `, done); 23 | }); 24 | 25 | it('should be the correct version (avoid lodash4 conflict)', function (done) { 26 | context.execute(` 27 | var assert = require('assert'); 28 | assert.strictEqual(_ && _.VERSION, '3.10.2', '_.VERSION must be 3.10.2'); 29 | `, done); 30 | }); 31 | 32 | it('should have basic functionalities working', function (done) { 33 | context.execute(` 34 | var assert = require('assert'); 35 | assert.deepEqual(_.keys({a: true, b: true}), ['a', 'b'], '_.keys shoud return keys as array'); 36 | `, done); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/moment-min.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - moment.min', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'), 21 | moment = require('moment'); 22 | 23 | assert.strictEqual(typeof moment, 'function', 'typeof moment must be function'); 24 | `, done); 25 | }); 26 | 27 | it('should format dates', function (done) { 28 | context.execute(` 29 | var assert = require('assert'), 30 | moment = require('moment'); 31 | 32 | assert.strictEqual(moment('2017-01-01T10:10:10.000').format('MMMM Do YYYY, h:mm:ss a'), 33 | 'January 1st 2017, 10:10:10 am'); 34 | assert.strictEqual(moment('2017-01-01T10:10:10.000').format('dddd'), 'Sunday'); 35 | assert.strictEqual(moment('2017-01-01T10:10:10.000').format("MMM Do YY"), 'Jan 1st 17'); 36 | assert.strictEqual(moment('2017-01-01T10:10:10.000').format('YYYY [escaped] YYYY'), '2017 escaped 2017'); 37 | `, done); 38 | }); 39 | 40 | it('should format relative time', function (done) { 41 | context.execute(` 42 | var assert = require('assert'), 43 | moment = require('moment'), 44 | start = moment([2007, 0, 28]), 45 | end = moment([2007, 0, 29]); 46 | 47 | assert.strictEqual(start.to(start, true), 'a few seconds'); 48 | assert.strictEqual(start.to(start), 'a few seconds ago'); 49 | 50 | assert.strictEqual(start.to(end, true), 'a day'); 51 | assert.strictEqual(start.to(end), 'in a day'); 52 | 53 | assert.strictEqual(end.to(start, true), 'a day'); 54 | assert.strictEqual(end.to(start), 'a day ago'); 55 | `, done); 56 | }); 57 | 58 | it('should format calendar time', function (done) { 59 | context.execute(` 60 | var assert = require('assert'), 61 | moment = require('moment'), 62 | reference = '2017-01-01T10:10:10.000'; 63 | 64 | assert.strictEqual(moment(reference).calendar(reference), 'Today at 10:10 AM'); 65 | 66 | assert.strictEqual(moment(reference).subtract(1, 'day').calendar(reference), 'Yesterday at 10:10 AM'); 67 | assert.strictEqual(moment(reference).subtract(10, 'days').calendar(reference), '12/22/2016'); 68 | 69 | assert.strictEqual(moment(reference).add(1, 'day').calendar(reference), 'Tomorrow at 10:10 AM'); 70 | assert.strictEqual(moment(reference).add(10, 'days').calendar(reference), '01/11/2017'); 71 | `, done); 72 | }); 73 | 74 | describe('locales', function () { 75 | it('should work with the US locale', function (done) { 76 | context.execute(` 77 | var assert = require('assert'), 78 | moment = require('moment'); 79 | 80 | moment.locale('en'); 81 | assert.strictEqual(moment.locale(), 'en'); 82 | 83 | assert.strictEqual(moment.weekdays(3), 'Wednesday'); 84 | assert.strictEqual(moment.weekdaysShort(3), 'Wed'); 85 | 86 | assert.strictEqual(moment.months(1), 'February'); 87 | assert.strictEqual(moment.monthsShort(1), 'Feb'); 88 | `, done); 89 | }); 90 | 91 | it('should not work with the UK locale', function (done) { 92 | context.execute(` 93 | var assert = require('assert'), 94 | moment = require('moment'); 95 | 96 | moment.locale('en-gb'); 97 | assert.strictEqual(moment.locale(), 'en'); 98 | 99 | assert.strictEqual(moment.weekdays(3), 'Wednesday'); 100 | assert.strictEqual(moment.weekdaysShort(3), 'Wed'); 101 | 102 | assert.strictEqual(moment.months(1), 'February'); 103 | assert.strictEqual(moment.monthsShort(1), 'Feb'); 104 | `, done); 105 | }); 106 | 107 | it('should not work with the Chinese locale', function (done) { 108 | context.execute(` 109 | var assert = require('assert'), 110 | moment = require('moment'); 111 | 112 | moment.locale('zh-cn'); 113 | assert.strictEqual(moment.locale(), 'en'); 114 | 115 | assert.strictEqual(moment.weekdays(3), 'Wednesday'); 116 | assert.strictEqual(moment.weekdaysShort(3), 'Wed'); 117 | 118 | assert.strictEqual(moment.months(1), 'February'); 119 | assert.strictEqual(moment.monthsShort(1), 'Feb'); 120 | `, done); 121 | }); 122 | 123 | it('should not work with the pseudo-locale', function (done) { 124 | context.execute(` 125 | var assert = require('assert'), 126 | moment = require('moment'); 127 | 128 | moment.locale('x-pseudo'); 129 | assert.strictEqual(moment.locale(), 'en'); 130 | 131 | assert.strictEqual(moment.weekdays(3), 'Wednesday'); 132 | assert.strictEqual(moment.weekdaysShort(3), 'Wed'); 133 | 134 | assert.strictEqual(moment.months(1), 'February'); 135 | assert.strictEqual(moment.monthsShort(1), 'Feb'); 136 | `, done); 137 | }); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/postman-collection.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - postman-collection', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should be exposed via require', function (done) { 19 | context.execute(` 20 | var assert = require('assert'), 21 | sdk = require('postman-collection'); 22 | 23 | assert.strictEqual(!!sdk, true, '!!sdk should be truthy'); 24 | `, done); 25 | }); 26 | 27 | it('should construct a collection', function (done) { 28 | context.execute(` 29 | var assert = require('assert'), 30 | sdk = require('postman-collection'), 31 | 32 | collection; 33 | 34 | collection = new sdk.Collection({ 35 | item: { 36 | id: 'get-one', 37 | request: 'http://postman-echo.com/get?test=123' 38 | } 39 | }); 40 | 41 | 42 | assert.strictEqual(sdk.Collection.isCollection(collection), 43 | true, 'Collection.isCollection(collection) should be true'); 44 | 45 | assert.strictEqual(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i 46 | .test(collection.id), true, 'collection must have a valid id'); 47 | 48 | assert.strictEqual(sdk.PropertyList.isPropertyList(collection.items), true, 'has an itemgroup'); 49 | assert.strictEqual(collection.items.has('get-one'), true, 'items.has lookup get-one item'); 50 | 51 | assert.strictEqual(collection.items.one('get-one').request.url.toString(), 52 | 'http://postman-echo.com/get?test=123'); 53 | `, done); 54 | }); 55 | 56 | it('should construct a response', function (done) { 57 | context.execute(` 58 | var assert = require('assert'), 59 | sdk = require('postman-collection'), 60 | 61 | response; 62 | 63 | response = new sdk.Response({ 64 | stream: Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]) 65 | }); 66 | 67 | 68 | assert.strictEqual(response.text(), 'buffer', 'converts stream in response to text'); 69 | `, done); 70 | }); 71 | 72 | it('should construct a variable scope', function (done) { 73 | context.execute(` 74 | var assert = require('assert'), 75 | sdk = require('postman-collection'), 76 | 77 | variables; 78 | 79 | variables = new sdk.VariableScope({ 80 | values: [{ 81 | key: 'var1', 82 | value: 'val1' 83 | }, { 84 | key: 'var2', 85 | value: 'val2' 86 | }] 87 | }); 88 | 89 | 90 | assert.strictEqual(variables.syncVariablesTo().var1, 'val1'); 91 | assert.strictEqual(variables.syncVariablesTo().var2, 'val2'); 92 | `, done); 93 | }); 94 | 95 | it('should resolve dynamic variables using pm.variables.replaceIn', function (done) { 96 | context.execute(` 97 | var assert = require('assert'), 98 | replaceIn = pm.variables.replaceIn, 99 | variables = [ 100 | '$guid', '$timestamp', '$isoTimestamp', '$randomInt', '$randomPhoneNumber', '$randomWords', 101 | '$randomLocale', '$randomDirectoryPath', '$randomCity', '$randomCountry', '$randomBs', 102 | '$randomUrl', '$randomPassword', '$randomFullName', '$randomMonth', '$randomLoremParagraphs' 103 | ], 104 | guidRE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, 105 | ipRE = /^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}(?= - -)/; 106 | 107 | for (var variable of variables) { 108 | var resolved = replaceIn(\`{{\${variable}}}\`); 109 | assert.notStrictEqual(resolved, \`{{\${variable}}}\`); 110 | } 111 | 112 | assert.ok(guidRE.test(replaceIn("{{$guid}}"))); 113 | assert.ok(replaceIn("{{$randomWords}}").split(' ').length > 1); 114 | assert.ok(replaceIn('{{$randomPhoneNumber}}').length === 12); 115 | assert.ok(replaceIn('{{$randomBankAccount}}').length === 8); 116 | assert.strictEqual(replaceIn('{{$randomImageUrl}}'), 'http://placeimg.com/640/480'); 117 | `, done); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/postman.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - postman legacy', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | describe('postman environment related functions', function () { 19 | it('should be able to clear all environments', function (done) { 20 | context.execute(` 21 | var assert = require('assert'); 22 | assert.strictEqual(environment.a, 'b', 'environment must be initially set'); 23 | postman.clearEnvironmentVariables(); 24 | assert.equal(Object.keys(environment).length, 0, 'environment must be cleared'); 25 | `, { 26 | context: { 27 | environment: [{ 28 | key: 'a', 29 | value: 'b' 30 | }] 31 | } 32 | }, done); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/sugar.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - sugarjs', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | 22 | assert.strictEqual(typeof new Date().format, 'function', 'Extended Date prototype must exist'); 23 | assert.strictEqual(typeof 'asdasd'.has, 'function', 'Extended String prototype must exist'); 24 | assert.strictEqual(typeof Object.each, 'function', 'Extended Object prototype must exist'); 25 | assert.strictEqual(typeof Date.create, 'function', 'Extended Date prototype must exist'); 26 | `, done); 27 | }); 28 | 29 | it('should expose sugarjs functionality', function (done) { 30 | context.execute(` 31 | var assert = require('assert'), 32 | d = new Date(1470659144696); // Monday, Aug 08, 2016 33 | 34 | assert.strictEqual(d.format('{Weekday}'), 'Monday', 'Date.prototype.format must format dates correctly'); 35 | assert('asdasd'.has('as'), 'String.prototype.has must detect sub strings correctly'); 36 | assert(!!(1).daysAfter(new Date()).format('{yyyy}{MM}{dd}'), 'Date.prototype.format must be a function'); 37 | `, done); 38 | }); 39 | 40 | describe('Extended prototypes', function () { 41 | it('Array must work correctly', function (done) { 42 | context.execute(` 43 | var assert = require('assert'); 44 | 45 | assert(['a', 'b', 'c'].none('d'), 'Array.prototype.none must work correctly'); 46 | assert([ [1,2], [2,3] ].any([2,3]), 'Array.prototype.any must work correctly'); 47 | assert.strictEqual([ 1, 2, 3, 4, 5 ].average(), 3, 'Array.prototype.average must work correctly'); 48 | `, done); 49 | }); 50 | 51 | it('Date must work correctly', function (done) { 52 | context.execute(` 53 | var assert = require('assert'); 54 | 55 | assert.equal(Date.now(), (new Date()).getTime(), 'Date.prototype.getTime must work correctly'); 56 | assert(Date.create('next week').isFuture(), 'Date.prototype.isFuture must work correctly'); 57 | assert(Date.create('2000').isLeapYear(), 'Date.prototype.isLeapYear must work correctly'); 58 | assert(Date.create('last week').isPast(), 'Date.prototype.isPast must work correctly'); 59 | assert(new Date().isValid(), 'Date.prototype.isValid must work correctly'); 60 | assert(!(new Date('random string').isValid()), 'Negated Date.prototype.isValid must work correctly'); 61 | `, done); 62 | }); 63 | 64 | it('Function must work correctly', function (done) { 65 | context.execute(` 66 | var assert = require('assert'); 67 | 68 | var fCount = 0, 69 | fn = (function() { fCount++; }).once(); 70 | 71 | fn(); fn(); fn(); 72 | assert.strictEqual(fCount, 1, 'Function.prototype.once must work correctly'); 73 | `, done); 74 | }); 75 | 76 | it('Number must work correctly', function (done) { 77 | context.execute(` 78 | var assert = require('assert'); 79 | 80 | assert((56).isEven(), 'Number.prototype.isEven must work correctly'); 81 | assert.strictEqual((56).hex(), '38', 'Number.prototype.hex must work correctly'); 82 | assert.strictEqual((56).ordinalize(), '56th', 'Number.prototype.ordinalize must work correctly'); 83 | assert.strictEqual((56789.10).format(), '56,789.1', 'Number.prototype.format must work correctly'); 84 | `, done); 85 | }); 86 | 87 | it('String must work correctly', function (done) { 88 | context.execute(` 89 | var assert = require('assert'); 90 | 91 | assert('jumpy'.endsWith('py'), 'String.prototype.endsWith must work correctly'); 92 | assert.strictEqual('abc'.shift(5), 'fgh', 'String.prototype.shift must work correctly'); 93 | assert.strictEqual('a'.repeat(5), 'aaaaa', 'String.prototype.repeat must work correctly'); 94 | assert(!('jumpy'.endsWith('MPY')), 'Negated String.prototype.endsWith must work correctly'); 95 | assert.strictEqual('a-beta'.camelize(), 'ABeta', 'String.prototype.camelize must work correctly'); 96 | assert.strictEqual('a-b_cD'.spacify(), 'a b c d', 'String.prototype.spacify must work correctly'); 97 | `, done); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/tv4.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - TV4', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | 22 | assert.strictEqual(typeof tv4, 'object', 'typeof tv4 must be object'); 23 | assert.strictEqual(typeof tv4.validate, 'function', 'typeof tv4.validate must be function'); 24 | `, done); 25 | }); 26 | 27 | it('should have basic functionality working', function (done) { 28 | context.execute(` 29 | var assert = require('assert'), 30 | schema = { 31 | "$schema": "http://json-schema.org/draft-04/schema#", 32 | "type": "object", 33 | "properties": { 34 | "alpha": { 35 | "type": "boolean" 36 | } 37 | } 38 | }; 39 | 40 | assert(tv4.validate({alpha: true}, schema), 'TV4 schema validation must identify valid objects'); 41 | `, done); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/uuid-vendor.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - uuid~vendor', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should be exposed via require', function (done) { 19 | context.execute(` 20 | var assert = require('assert'), 21 | uuid = require('uuid'); 22 | 23 | assert.strictEqual(typeof uuid, 'function', 'typeof require("uuid") must be function'); 24 | `, done); 25 | }); 26 | 27 | it('should generate v4 uuid', function (done) { 28 | context.execute(` 29 | var assert = require('assert'), 30 | uuid = require('uuid'); 31 | 32 | assert.strictEqual(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(uuid()), 33 | true, 'generated string must be uuid v4'); 34 | `, done); 35 | }); 36 | 37 | it('should have a .v4 function for compatibility', function (done) { 38 | context.execute(` 39 | var assert = require('assert'), 40 | uuid = require('uuid'); 41 | 42 | assert.strictEqual(/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i.test(uuid.v4()), 43 | true, 'generated string must be uuid v4'); 44 | `, done); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/unit/sandbox-libraries/xml2Json.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox library - xml2Json', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext({}, function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist', function (done) { 19 | context.execute(` 20 | var assert = require('assert'); 21 | 22 | assert.strictEqual(typeof xml2Json, 'function', 'typeof xml2Json must be function'); 23 | `, done); 24 | }); 25 | 26 | it('should have basic functionality working', function (done) { 27 | context.execute(` 28 | var assert = require('assert'), 29 | xml = 'Homestyle Breakfast950', 30 | object = xml2Json(xml).food; 31 | 32 | assert.strictEqual(object.key, 'Homestyle Breakfast', 'xml2Json conversion must be valid'); 33 | assert.strictEqual(object.value, '950', 'xml2Json conversion must be valid'); 34 | `, done); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/sandbox-shenanigans.test.js: -------------------------------------------------------------------------------- 1 | describe('script in sandbox', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'); 4 | 5 | it('should retain global variables in subsequent calls', function (done) { 6 | Sandbox.createContext(function (err, ctx) { 7 | if (err) { return done(err); } 8 | ctx.on('error', done); 9 | 10 | ctx.execute(` 11 | var assert = require('assert'); 12 | 13 | // check absence of a global var on first run 14 | assert.equal(typeof oneGlobalVariable, 'undefined', 'oneGlobalVariable should be undefined at first'); 15 | 16 | // set a global variable 17 | oneGlobalVariable = 'test-global-variable'; 18 | `, function (err) { 19 | if (err) { return done(err); } 20 | 21 | ctx.execute(` 22 | var assert = require('assert'); 23 | assert.notEqual(typeof oneGlobalVariable, 'undefined', 'oneGlobalVariable should be defined'); 24 | assert.equal(oneGlobalVariable, 'test-global-variable', 'oneGlobalVariable should have value'); 25 | `, done); 26 | }); 27 | }); 28 | }); 29 | 30 | it('should be able to define variables with names matching legacy globals', function (done) { 31 | Sandbox.createContext(function (err, ctx) { 32 | if (err) { return done(err); } 33 | ctx.on('error', done); 34 | ctx.on('execution.error', done); 35 | 36 | ctx.execute('const data = 1;', done); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/sandbox-timeout.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox timeout', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../lib'); 4 | 5 | it('should accept a timeout option', function (done) { 6 | Sandbox.createContext({ 7 | timeout: 10000 // 10 seconds 8 | }, function (err, ctx) { 9 | if (err) { return done(err); } 10 | ctx.on('error', done); 11 | 12 | ctx.ping(function (err, ttl, packet) { 13 | expect(err).to.be.null; 14 | expect(packet).to.be.ok; 15 | expect(ttl).be.a('number').that.is.above(-1); 16 | done(); 17 | }); 18 | }); 19 | }); 20 | 21 | it('should timeout on infinite loop', function (done) { 22 | Sandbox.createContext({ 23 | timeout: 500 // 500 ms 24 | }, function (err, ctx) { 25 | if (err) { return done(err); } 26 | 27 | ctx.on('error', done); 28 | 29 | ctx.execute('while(1)', function (err) { 30 | expect(err).to.be.ok; 31 | expect(err).to.have.property('message', 'sandbox not responding'); 32 | done(); 33 | }); 34 | }); 35 | }); 36 | 37 | it('should not timeout if code is error-free', function (done) { 38 | Sandbox.createContext({ 39 | timeout: 500 // 500 ms 40 | }, function (err, ctx) { 41 | if (err) { return done(err); } 42 | 43 | ctx.on('error', done); 44 | ctx.on('execution.error', done); 45 | ctx.on('execution.result.1', (err) => { 46 | expect(err).to.be.null; 47 | }); 48 | ctx.execute('var x = "i am doing nothing!";', { id: '1' }, done); 49 | }); 50 | }); 51 | 52 | it('should clear timeout on bridge disconnect', function (done) { 53 | Sandbox.createContext({ 54 | timeout: 500 // 500 ms 55 | }, function (err, ctx) { 56 | if (err) { return done(err); } 57 | 58 | ctx.on('error', done); 59 | 60 | ctx.execute('while(1)', { id: '1' }, function (err) { 61 | expect(err).to.be.ok; 62 | expect(err).to.have.property('message'); 63 | ctx.once('execution.result.1', (err) => { 64 | expect(err).to.be.ok; 65 | expect(err).to.have.property('message'); 66 | expect(err).to.have.property('message', 'sandbox not responding'); 67 | 68 | ctx.once('execution.result.1', (err) => { 69 | expect(err).to.be.ok; 70 | expect(err).to.have.property('message'); 71 | expect(err).to.have.property('message', 'sandbox: execution interrupted, ' + 72 | 'bridge disconnecting.'); 73 | }); 74 | }); 75 | done(); 76 | }); 77 | ctx.dispose(); 78 | }); 79 | }); 80 | 81 | 82 | it('should reconnect on execute after a timeout', function (done) { 83 | Sandbox.createContext(function (err, ctx) { 84 | if (err) { return done(err); } 85 | 86 | ctx.on('error', done); 87 | ctx.on('console', (...args) => { 88 | expect(args).to.be.ok; 89 | expect(args[2]).to.equal('knock knock'); 90 | }); 91 | 92 | ctx.execute('while(1)', { id: '1', timeout: 500 }, (err) => { 93 | expect(err).to.be.ok; 94 | expect(err).to.have.property('message', 'sandbox not responding'); 95 | 96 | ctx.execute('console.log("knock knock")', { id: '2' }, done); 97 | }); 98 | }); 99 | }); 100 | 101 | it('should not reconnect on execute if context is already disposed', function (done) { 102 | Sandbox.createContext(function (err, ctx) { 103 | if (err) { return done(err); } 104 | 105 | ctx.on('error', (err) => { 106 | expect(err).to.be.ok; 107 | expect(err).to.have.property('message', 'uvm: unable to dispatch "execute" post disconnection.'); 108 | done(); 109 | }); 110 | ctx.on('console', () => { 111 | done(new Error('should not have fired')); 112 | }); 113 | 114 | ctx.execute(';', (err) => { 115 | if (err) { return done(err); } 116 | 117 | ctx.dispose(); 118 | ctx.execute('console.log("knock knock")', done); 119 | }); 120 | }); 121 | }); 122 | 123 | it('should retain init and connect options on reconnect', function (done) { 124 | Sandbox.createContextFleet({ 125 | grpc: ` 126 | function initializeExecution () { 127 | return { 128 | request: { 129 | type: 'grpc-request' 130 | }, 131 | response: { 132 | type: 'grpc-response' 133 | } 134 | } 135 | }; 136 | 137 | function chaiPlugin (chai) { 138 | const Assertion = chai.Assertion; 139 | 140 | Assertion.addProperty('grpcRequest', function () { 141 | this.assert(this._obj.type === 'grpc-request', 142 | 'expecting a gRPC request but got #{this}', 143 | 'not expecting a gRPC request object'); 144 | }); 145 | 146 | Assertion.addProperty('grpcResponse', function () { 147 | this.assert(this._obj.type === 'grpc-response', 148 | 'expecting a gRPC response but got #{this}', 149 | 'not expecting a gRPC response object'); 150 | }); 151 | } 152 | 153 | module.exports = { initializeExecution, chaiPlugin }; 154 | `, 155 | websocket: '' 156 | }, {}, { timeout: 500 }, (err, fleet) => { 157 | if (err) { return done(err); } 158 | 159 | fleet.getContext('grpc', (err, ctx) => { 160 | if (err) { return done(err); } 161 | 162 | ctx.on('error', done); 163 | ctx.on('execution.assertion', (_, assertions) => { 164 | assertions.forEach((a) => { 165 | expect(a.passed).to.be.true; 166 | }); 167 | }); 168 | 169 | ctx.execute('while(1)', () => { 170 | ctx.execute(` 171 | pm.test('Should be gRPC request and response', () => { 172 | pm.request.to.be.grpcRequest; 173 | pm.response.to.be.grpcResponse; 174 | }); 175 | while(1); 176 | `, (err) => { 177 | expect(err).to.be.ok; 178 | expect(err).to.have.property('message', 'sandbox not responding'); 179 | done(); 180 | }); 181 | }); 182 | }); 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/unit/timers.test.js: -------------------------------------------------------------------------------- 1 | describe('timers module', function () { 2 | this.timeout(1000 * 60); 3 | var Timers = require('../../lib/sandbox/timers'); 4 | 5 | it('should be able to set an event', function (done) { 6 | var status = 'uneventful', 7 | timers, 8 | id; 9 | 10 | timers = new Timers(null, done, null, function () { 11 | expect(status).to.eql('eventful'); 12 | done(); 13 | }); 14 | 15 | id = timers.setEvent(function (msg) { 16 | status = msg; 17 | }); 18 | 19 | setTimeout(function () { 20 | timers.clearEvent(id, 'eventful'); 21 | }, 10); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/vendors/atob.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox vendor - atob', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist in global', function (done) { 19 | context.execute(` 20 | const assert = require('assert'); 21 | assert.strictEqual(typeof atob, 'function', 'typeof atob must be function'); 22 | `, done); 23 | }); 24 | 25 | it('should be exposed via require', function (done) { 26 | context.execute(` 27 | const assert = require('assert'); 28 | const atob = require('atob'); 29 | assert.strictEqual(typeof atob, 'function', 'typeof atob must be function'); 30 | `, done); 31 | }); 32 | 33 | it('should have same implementation exposed via global, require and buffer', function (done) { 34 | context.execute(` 35 | const assert = require('assert'); 36 | const requiredAtob = require('atob'); 37 | const bufferAtob = require('buffer').atob; 38 | assert.strictEqual(atob === requiredAtob, true); 39 | assert.strictEqual(atob === bufferAtob, true); 40 | `, done); 41 | }); 42 | 43 | it('should decode base64 encoded string', function (done) { 44 | context.execute(` 45 | const assert = require('assert'); 46 | const decoded = atob('cG9zdG1hbi1zYW5kYm94'); 47 | assert.strictEqual(decoded, 'postman-sandbox'); 48 | `, done); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/unit/vendors/btoa.test.js: -------------------------------------------------------------------------------- 1 | describe('sandbox vendor - btoa', function () { 2 | this.timeout(1000 * 60); 3 | var Sandbox = require('../../../'), 4 | context; 5 | 6 | beforeEach(function (done) { 7 | Sandbox.createContext(function (err, ctx) { 8 | context = ctx; 9 | done(err); 10 | }); 11 | }); 12 | 13 | afterEach(function () { 14 | context.dispose(); 15 | context = null; 16 | }); 17 | 18 | it('should exist in global', function (done) { 19 | context.execute(` 20 | const assert = require('assert'); 21 | assert.strictEqual(typeof btoa, 'function', 'typeof btoa must be function'); 22 | `, done); 23 | }); 24 | 25 | it('should be exposed via require', function (done) { 26 | context.execute(` 27 | const assert = require('assert'); 28 | const btoa = require('btoa'); 29 | assert.strictEqual(typeof btoa, 'function', 'typeof btoa must be function'); 30 | `, done); 31 | }); 32 | 33 | it('should have same implementation exposed via global, require and buffer', function (done) { 34 | context.execute(` 35 | const assert = require('assert'); 36 | const requiredBtoa = require('btoa'); 37 | const bufferBtoa = require('buffer').btoa; 38 | assert.strictEqual(btoa === requiredBtoa, true); 39 | assert.strictEqual(btoa === bufferBtoa, true); 40 | `, done); 41 | }); 42 | 43 | it('should encode a string to base64', function (done) { 44 | context.execute(` 45 | const assert = require('assert'); 46 | const decoded = btoa('postman-sandbox'); 47 | assert.strictEqual(decoded, 'cG9zdG1hbi1zYW5kYm94'); 48 | `, done); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/vm/_bootstrap.js: -------------------------------------------------------------------------------- 1 | // Assigned 'global' to fix mocha v9.1.2 bump 2 | // Ref: https://github.com/mochajs/mocha/pull/4746 3 | var Mocha = Object.assign(require('mocha'), global).Mocha, 4 | 5 | mocha; 6 | 7 | // @hack to avoid path.resolve errors 8 | Mocha.prototype.loadFiles = function (fn) { 9 | var self = this, 10 | suite = this.suite; 11 | 12 | this.files.forEach(function (file) { 13 | suite.emit('pre-require', global, file, self); 14 | suite.emit('require', require(file), file, self); 15 | suite.emit('post-require', global, file, self); 16 | }); 17 | fn && fn(); 18 | }; 19 | 20 | mocha = new Mocha({ timeout: 1000 * 60, useColors: true }); 21 | 22 | // eslint-disable-next-line no-undef 23 | __specs.forEach(mocha.addFile.bind(mocha)); // @hack __specs is exposed in the VM context 24 | 25 | // eslint-disable-next-line no-undef 26 | mocha.run(__next); // @hack exposed in the VM context 27 | -------------------------------------------------------------------------------- /test/vm/postman-collection.test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'), 2 | sdk = require('postman-collection'); 3 | 4 | describe('Collection SDK in Node VM', function () { 5 | it('should exist', function () { 6 | assert.strictEqual(Boolean(sdk), true, '!!sdk should be truthy'); 7 | }); 8 | 9 | it('should work correctly for collections', function () { 10 | var collection = new sdk.Collection({ 11 | item: { 12 | id: 'get-one', 13 | request: 'http://postman-echo.com/get?test=123' 14 | } 15 | }); 16 | 17 | assert.strictEqual(sdk.Collection.isCollection(collection), 18 | true, 'Collection.isCollection(collection) should be true'); 19 | 20 | assert.strictEqual((/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i) 21 | .test(collection.id), true, 'collection must have a valid id'); 22 | 23 | assert.strictEqual(sdk.PropertyList.isPropertyList(collection.items), true, 'has an itemgroup'); 24 | assert.strictEqual(collection.items.has('get-one'), true, 'items.has lookup get-one item'); 25 | 26 | assert.strictEqual(collection.items.one('get-one').request.url.toString(), 27 | 'http://postman-echo.com/get?test=123'); 28 | }); 29 | 30 | it('should work correctly for responses', function () { 31 | var response = new sdk.Response({ 32 | stream: Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]) 33 | }); 34 | 35 | assert.strictEqual(response.text(), 'buffer', 'converts stream in response to text'); 36 | }); 37 | 38 | it('should work correctly for variable scopes', function () { 39 | var variables = new sdk.VariableScope({ 40 | values: [{ 41 | key: 'var1', 42 | value: 'val1' 43 | }, { 44 | key: 'var2', 45 | value: 'val2' 46 | }] 47 | }); 48 | 49 | assert.strictEqual(variables.syncVariablesTo().var1, 'val1'); 50 | assert.strictEqual(variables.syncVariablesTo().var2, 'val2'); 51 | }); 52 | }); 53 | --------------------------------------------------------------------------------