Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate Node.js implementation for js-sandbox to isolated-vm #3973

Merged
merged 19 commits into from Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5cc8716
chore: migrate JS sandbox to isolated-vm
jamesgeorge007 Mar 26, 2024
23e959a
fix: pre-request script execution
jamesgeorge007 Apr 3, 2024
bac9743
refactor: serialize test script API methods before sending to the iso…
jamesgeorge007 Apr 3, 2024
c2bd74a
chore: migrate test framework to vitest
jamesgeorge007 Apr 4, 2024
d75a76d
chore: resolve build errors wrt wrt CJS interop with ESM
jamesgeorge007 Apr 4, 2024
318402d
refactor: leverage Proxy to intercept scripting API calls
jamesgeorge007 Apr 5, 2024
023072e
refactor: spawn child process to add no-node-snapshot flag when required
AndrewBastin Apr 11, 2024
0e2d2d0
chore: bump jest timeout for test.spec.ts
AndrewBastin Apr 11, 2024
27d58c0
fix: condition to spawn child process
jamesgeorge007 Apr 11, 2024
7eca5ba
chore: make `isolated-vm` a peer-dependency
jamesgeorge007 Apr 11, 2024
0659182
test: specify node as the command while invoking the CLI binary
jamesgeorge007 Apr 12, 2024
3268220
refactor: update js-sandbox directory hierarchy
jamesgeorge007 Apr 12, 2024
5ba17f0
test: update directory hierarchy
jamesgeorge007 Apr 12, 2024
01515fd
chore: cleanup
jamesgeorge007 Apr 12, 2024
3bd099c
test: fix flaky tests
jamesgeorge007 Apr 15, 2024
2f3ca3f
chore: account for nested folders while validating requests
jamesgeorge007 Apr 16, 2024
62d4545
chore: compile shared utils for sandbox
jamesgeorge007 Apr 17, 2024
da356c7
test: fix flaky tests
jamesgeorge007 Apr 18, 2024
8321fc8
chore: ensure consistent file naming convention
jamesgeorge007 Apr 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 7 additions & 8 deletions .github/workflows/tests.yml
Expand Up @@ -17,22 +17,21 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup environment
run: mv .env.example .env

- name: Setup node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Setup pnpm
uses: pnpm/action-setup@v2.2.4
uses: pnpm/action-setup@v3
with:
version: 8
run_install: true

- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: pnpm

- name: Run tests
run: pnpm test
27 changes: 26 additions & 1 deletion packages/hoppscotch-cli/bin/hopp.js
@@ -1,6 +1,31 @@
#!/usr/bin/env node
// * The entry point of the CLI
// @ts-check

import { cli } from "../dist/index.js";

cli(process.argv);
import { spawnSync } from "child_process";
import { cloneDeep } from "lodash-es";

const nodeVersion = parseInt(process.versions.node.split(".")[0]);

// As per isolated-vm documentation, we need to supply `--no-node-snapshot` for node >= 20
// src: https://github.com/laverdet/isolated-vm?tab=readme-ov-file#requirements
if (nodeVersion >= 20 && !process.execArgv.includes("--no-node-snapshot")) {
const argCopy = cloneDeep(process.argv);

// Replace first argument with --no-node-snapshot
// We can get argv[0] from process.argv0
argCopy[0] = "--no-node-snapshot";

const result = spawnSync(
process.argv0,
argCopy,
{ stdio: "inherit" }
);

// Exit with the same status code as the spawned process
process.exit(result.status ?? 0);
} else {
cli(process.argv);
}
3 changes: 2 additions & 1 deletion packages/hoppscotch-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@hoppscotch/cli",
"version": "0.7.0",
"version": "0.7.1",
"description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io",
"type": "module",
Expand Down Expand Up @@ -44,6 +44,7 @@
"axios": "1.6.7",
"chalk": "5.3.0",
"commander": "11.1.0",
"isolated-vm": "4.7.2",
"lodash-es": "4.17.21",
"qs": "6.11.2",
"verzod": "0.2.2",
Expand Down
Expand Up @@ -224,7 +224,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
});

describe("Secret environment variables", () => {
jest.setTimeout(10000);
jest.setTimeout(100000);

// Reads secret environment values from system environment
test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => {
Expand Down
@@ -1,27 +1,55 @@
{
"v": 1,
"name": "coll-v1",
"folders": [],
"requests": [
"v": 1,
"name": "coll-v1",
"folders": [
{
"v": 1,
"name": "coll-v1-child",
"folders": [],
"requests": [
{
"url": "https://httpbin.org",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}
"url": "https://echo.hoppscotch.io",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}
],
"requests": [
{
"url": "https://echo.hoppscotch.io",
"path": "/get",
"headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true }
],
"params": [
{ "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false }
],
"name": "req-v0",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"contentType": "application/json",
"body": "",
"auth": "Bearer Token",
"bearerToken": "token123"
}
]
}
@@ -1,11 +1,60 @@
{
"v": 1,
"name": "coll-v1",
"folders": [],
"folders": [
{
"v": 1,
"name": "coll-v1-child",
"folders": [],
"requests": [
{
"v": "1",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v1-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
}
}
]
}
],
"requests": [
{
"v": "1",
"endpoint": "https://httpbin.org/get",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
Expand Down Expand Up @@ -33,7 +82,7 @@
"name": "req-v1",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
Expand Down
@@ -1,11 +1,66 @@
{
"v": 2,
"name": "coll-v2",
"folders": [],
"folders": [
{
"v": 2,
"name": "coll-v2-child",
"folders": [],
"requests": [
{
"v": "2",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v2-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}
],
"requests": [
{
"v": "2",
"endpoint": "https://httpbin.org/get",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
Expand Down Expand Up @@ -33,7 +88,7 @@
"name": "req-v2",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
Expand Down
@@ -1,11 +1,66 @@
{
"v": 2,
"name": "coll-v2",
"folders": [],
"folders": [
{
"v": 2,
"name": "coll-v2-child",
"folders": [],
"requests": [
{
"v": "3",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
"value": "Inactive Header",
"active": false
},
{
"key": "Authorization",
"value": "Bearer token123",
"active": true
}
],
"params": [
{
"key": "key",
"value": "value",
"active": true
},
{
"key": "inactive-key",
"value": "inactive-param",
"active": false
}
],
"name": "req-v3-II",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
},
"auth": {
"authType": "bearer",
"authActive": true,
"token": "token123"
},
"requestVariables": []
}
],
"auth": {
"authType": "inherit",
"authActive": true
},
"headers": []
}
],
"requests": [
{
"v": "3",
"endpoint": "https://httpbin.org/get",
"endpoint": "https://echo.hoppscotch.io",
"headers": [
{
"key": "Inactive-Header",
Expand Down Expand Up @@ -33,7 +88,7 @@
"name": "req-v3",
"method": "GET",
"preRequestScript": "",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
"testScript": "pw.test(\"Asserts request params\", () => {\n pw.expect(pw.response.body.args.key).toBe(\"value\")\n pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
"body": {
"contentType": null,
"body": null
Expand Down