chore: migrate Node.js implementation for js-sandbox to isolated-vm (#3973)
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
@@ -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
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
"requests": [
|
||||
{
|
||||
"v": "3",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
},
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "test-secret-headers",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
@@ -18,13 +24,16 @@
|
||||
}
|
||||
],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/headers",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||
"endpoint": "<<echoHoppBaseURL>>/headers",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "3",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
},
|
||||
"body": {
|
||||
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
|
||||
"contentType": "application/json"
|
||||
@@ -34,14 +43,20 @@
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"endpoint": "<<echoHoppBaseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
"v": "3",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
},
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "test-secret-query-params",
|
||||
"method": "GET",
|
||||
"params": [
|
||||
@@ -53,7 +68,7 @@
|
||||
],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/get",
|
||||
"endpoint": "<<echoHoppBaseURL>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||
},
|
||||
@@ -65,14 +80,17 @@
|
||||
"username": "<<secretBasicAuthUsername>>",
|
||||
"authActive": true
|
||||
},
|
||||
"body": { "body": null, "contentType": null },
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "test-secret-basic-auth",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"preRequestScript": ""
|
||||
},
|
||||
{
|
||||
@@ -84,30 +102,42 @@
|
||||
"username": "testuser",
|
||||
"authActive": true
|
||||
},
|
||||
"body": { "body": null, "contentType": null },
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "test-secret-bearer-auth",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
"endpoint": "<<httpbinBaseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.get(\"secretBearerToken\")\n const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||
},
|
||||
{
|
||||
"v": "3",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"body": { "body": null, "contentType": null },
|
||||
"auth": {
|
||||
"authType": "none",
|
||||
"authActive": true
|
||||
},
|
||||
"body": {
|
||||
"body": null,
|
||||
"contentType": null
|
||||
},
|
||||
"name": "test-secret-fallback",
|
||||
"method": "GET",
|
||||
"params": [],
|
||||
"headers": [],
|
||||
"requestVariables": [],
|
||||
"endpoint": "<<baseURL>>",
|
||||
"endpoint": "<<echoHoppBaseURL>>",
|
||||
"testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})",
|
||||
"preRequestScript": ""
|
||||
}
|
||||
],
|
||||
"auth": { "authType": "inherit", "authActive": false },
|
||||
"auth": {
|
||||
"authType": "inherit",
|
||||
"authActive": false
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"v": 2,
|
||||
"name": "secret-envs-setters-coll",
|
||||
"name": "secret-envs-persistence-coll",
|
||||
"folders": [],
|
||||
"requests": [
|
||||
{
|
||||
@@ -24,8 +24,8 @@
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"endpoint": "<<baseURL>>/headers",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||
"endpoint": "<<echoHoppBaseURL>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
|
||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
@@ -49,8 +49,8 @@
|
||||
"active": true
|
||||
}
|
||||
],
|
||||
"endpoint": "<<baseURL>>/headers",
|
||||
"testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
|
||||
"endpoint": "<<echoHoppBaseURL>>",
|
||||
"testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n \n if (secretHeaderValue) {\n pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n }\n\n pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
|
||||
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
@@ -68,8 +68,8 @@
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"endpoint": "<<echoHoppBaseURL>>/post",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n const secretBodyValue = pw.env.get(\"secretBodyValue\")\n pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n \n if (secretBodyValue) {\n pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
|
||||
"preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
|
||||
},
|
||||
{
|
||||
@@ -93,7 +93,7 @@
|
||||
],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/get",
|
||||
"endpoint": "<<echoHoppBaseURL>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
|
||||
"preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
|
||||
},
|
||||
@@ -114,8 +114,8 @@
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
|
||||
"testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n // The endpoint at times results in a `502` bad gateway\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});",
|
||||
"preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}"
|
||||
},
|
||||
{
|
||||
@@ -136,8 +136,8 @@
|
||||
"params": [],
|
||||
"requestVariables": [],
|
||||
"headers": [],
|
||||
"endpoint": "<<baseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
"endpoint": "<<httpbinBaseURL>>/bearer",
|
||||
"testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n pw.expect(secretBearerToken).toBe(\"test-token\")\n\n // Safeguard to prevent test failures due to the endpoint\n if (pw.response.status !== 200) {\n return\n }\n\n if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});",
|
||||
"preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
|
||||
}
|
||||
],
|
||||
@@ -146,4 +146,4 @@
|
||||
"authActive": false
|
||||
},
|
||||
"headers": []
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"requests": [
|
||||
{
|
||||
"v": "3",
|
||||
"endpoint": "https://httpbin.org/post",
|
||||
"endpoint": "https://echo.hoppscotch.io/post",
|
||||
"name": "req",
|
||||
"params": [],
|
||||
"headers": [
|
||||
@@ -18,7 +18,7 @@
|
||||
"method": "POST",
|
||||
"auth": { "authType": "none", "authActive": true },
|
||||
"preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")",
|
||||
"testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request sccript\", () => {\n pw.expect(pw.response.body.json.key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
|
||||
"testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request script\", () => {\n pw.expect(pw.response.body.headers[\"custom-header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request script\", () => {\n pw.expect(JSON.parse(pw.response.body.data).key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n pw.env.unset(\"preReqVarOne\")\n pw.env.unset(\"postReqVarTwo\")\n\n pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
|
||||
"body": {
|
||||
"contentType": "application/json",
|
||||
"body": "{\n \"key\": \"<<customBodyValue>>\"\n}"
|
||||
|
||||
@@ -32,7 +32,12 @@
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"key": "baseURL",
|
||||
"key": "echoHoppBaseURL",
|
||||
"value": "https://echo.hoppscotch.io",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "httpbinBaseURL",
|
||||
"value": "https://httpbin.org",
|
||||
"secret": false
|
||||
}
|
||||
|
||||
@@ -38,7 +38,12 @@
|
||||
"secret": true
|
||||
},
|
||||
{
|
||||
"key": "baseURL",
|
||||
"key": "echoHoppBaseURL",
|
||||
"value": "https://echo.hoppscotch.io",
|
||||
"secret": false
|
||||
},
|
||||
{
|
||||
"key": "httpbinBaseURL",
|
||||
"value": "https://httpbin.org",
|
||||
"secret": false
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ import { resolve } from "path";
|
||||
|
||||
import { ExecResponse } from "./types";
|
||||
|
||||
export const runCLI = (args: string, options = {}): Promise<ExecResponse> =>
|
||||
{
|
||||
const CLI_PATH = resolve(__dirname, "../../bin/hopp");
|
||||
const command = `node ${CLI_PATH} ${args}`
|
||||
export const runCLI = (args: string, options = {}): Promise<ExecResponse> => {
|
||||
const CLI_PATH = resolve(__dirname, "../../bin/hopp.js");
|
||||
const command = `node ${CLI_PATH} ${args}`;
|
||||
|
||||
return new Promise((resolve) =>
|
||||
exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
|
||||
);
|
||||
}
|
||||
return new Promise((resolve) =>
|
||||
exec(command, options, (error, stdout, stderr) =>
|
||||
resolve({ error, stdout, stderr })
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const trimAnsi = (target: string) => {
|
||||
const ansiRegex =
|
||||
@@ -25,12 +26,18 @@ export const getErrorCode = (out: string) => {
|
||||
return ansiTrimmedStr.split(" ")[0];
|
||||
};
|
||||
|
||||
export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => {
|
||||
export const getTestJsonFilePath = (
|
||||
file: string,
|
||||
kind: "collection" | "environment"
|
||||
) => {
|
||||
const kindDir = {
|
||||
collection: "collections",
|
||||
environment: "environments",
|
||||
}[kind];
|
||||
|
||||
const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`);
|
||||
const filePath = resolve(
|
||||
__dirname,
|
||||
`../../src/__tests__/samples/${kindDir}/${file}`
|
||||
);
|
||||
return filePath;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import chalk from "chalk";
|
||||
import { Command } from "commander";
|
||||
import * as E from "fp-ts/Either";
|
||||
|
||||
import { version } from "../package.json";
|
||||
import { test } from "./commands/test";
|
||||
import { handleError } from "./handlers/error";
|
||||
@@ -20,7 +21,7 @@ const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent(
|
||||
"https://docs.hoppscotch.io/documentation/clients/cli"
|
||||
)}`;
|
||||
|
||||
const program = new Command()
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("hopp")
|
||||
|
||||
@@ -7,6 +7,41 @@ import { error } from "../types/errors";
|
||||
import { FormDataEntry } from "../types/request";
|
||||
import { isHoppErrnoException } from "./checks";
|
||||
|
||||
const getValidRequests = (
|
||||
collections: HoppCollection[],
|
||||
collectionFilePath: string
|
||||
) => {
|
||||
return collections.map((collection) => {
|
||||
// Validate requests using zod schema
|
||||
const requestSchemaParsedResult = z
|
||||
.array(entityReference(HoppRESTRequest))
|
||||
.safeParse(collection.requests);
|
||||
|
||||
// Handle validation errors
|
||||
if (!requestSchemaParsedResult.success) {
|
||||
throw error({
|
||||
code: "MALFORMED_COLLECTION",
|
||||
path: collectionFilePath,
|
||||
data: "Please check the collection data.",
|
||||
});
|
||||
}
|
||||
|
||||
// Recursively validate requests in nested folders
|
||||
if (collection.folders.length > 0) {
|
||||
collection.folders = getValidRequests(
|
||||
collection.folders,
|
||||
collectionFilePath
|
||||
);
|
||||
}
|
||||
|
||||
// Return validated collection
|
||||
return {
|
||||
...collection,
|
||||
requests: requestSchemaParsedResult.data,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses array of FormDataEntry to FormData.
|
||||
* @param values Array of FormDataEntry.
|
||||
@@ -82,22 +117,5 @@ export async function parseCollectionData(
|
||||
});
|
||||
}
|
||||
|
||||
return collectionSchemaParsedResult.data.map((collection) => {
|
||||
const requestSchemaParsedResult = z
|
||||
.array(entityReference(HoppRESTRequest))
|
||||
.safeParse(collection.requests);
|
||||
|
||||
if (!requestSchemaParsedResult.success) {
|
||||
throw error({
|
||||
code: "MALFORMED_COLLECTION",
|
||||
path,
|
||||
data: "Please check the collection data.",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...collection,
|
||||
requests: requestSchemaParsedResult.data,
|
||||
};
|
||||
});
|
||||
return getValidRequests(collectionSchemaParsedResult.data, path);
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export default {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
collectCoverage: true,
|
||||
setupFilesAfterEnv: ["./jest.setup.ts"],
|
||||
moduleNameMapper: {
|
||||
"~/(.*)": "<rootDir>/src/$1",
|
||||
"^lodash-es$": "lodash",
|
||||
},
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
require("@relmify/jest-fp-ts")
|
||||
4
packages/hoppscotch-js-sandbox/node.d.ts
vendored
4
packages/hoppscotch-js-sandbox/node.d.ts
vendored
@@ -1,2 +1,2 @@
|
||||
export { default } from "./dist/node.d.ts"
|
||||
export * from "./dist/node.d.ts"
|
||||
export { default } from "./dist/node/index.d.ts"
|
||||
export * from "./dist/node/index.d.ts"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
|
||||
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
|
||||
"test": "pnpm exec jest",
|
||||
"test": "vitest run",
|
||||
"build": "vite build && tsc --emitDeclarationOnly",
|
||||
"clean": "pnpm tsc --build --clean",
|
||||
"postinstall": "pnpm run build",
|
||||
@@ -69,10 +69,18 @@
|
||||
"eslint-config-prettier": "8.6.0",
|
||||
"eslint-plugin-prettier": "4.2.1",
|
||||
"io-ts": "2.2.16",
|
||||
"jest": "27.5.1",
|
||||
"prettier": "2.8.4",
|
||||
"ts-jest": "27.1.5",
|
||||
"typescript": "4.9.5",
|
||||
"vite": "5.0.5"
|
||||
"vite": "5.0.5",
|
||||
"vitest": "0.34.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"isolated-vm": "4.7.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"isolated-vm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
15
packages/hoppscotch-js-sandbox/setupFiles.ts
Normal file
15
packages/hoppscotch-js-sandbox/setupFiles.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
// Vitest doesn't work without globals
|
||||
// Ref: https://github.com/relmify/jest-fp-ts/issues/11
|
||||
|
||||
import decodeMatchers from "@relmify/jest-fp-ts/dist/decodeMatchers"
|
||||
import eitherMatchers from "@relmify/jest-fp-ts/dist/eitherMatchers"
|
||||
import optionMatchers from "@relmify/jest-fp-ts/dist/optionMatchers"
|
||||
import theseMatchers from "@relmify/jest-fp-ts/dist/theseMatchers"
|
||||
import eitherOrTheseMatchers from "@relmify/jest-fp-ts/dist/eitherOrTheseMatchers"
|
||||
import { expect } from "vitest"
|
||||
|
||||
expect.extend(decodeMatchers.matchers)
|
||||
expect.extend(eitherMatchers.matchers)
|
||||
expect.extend(optionMatchers.matchers)
|
||||
expect.extend(theseMatchers.matchers)
|
||||
expect.extend(eitherOrTheseMatchers.matchers)
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runPreRequestScript } from "~/pre-request/node-vm"
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript, runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
describe("Base64 helper functions", () => {
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -23,7 +24,7 @@ describe("toBe", () => {
|
||||
return expect(
|
||||
func(
|
||||
`
|
||||
pw.expect(2).toBe(2)
|
||||
pw.expect(2).toBe(2)
|
||||
`,
|
||||
fakeResponse
|
||||
)()
|
||||
@@ -1,8 +1,9 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,6 +1,6 @@
|
||||
import "@relmify/jest-fp-ts"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runPreRequestScript } from "~/pre-request/node-vm"
|
||||
import { runPreRequestScript } from "~/node"
|
||||
|
||||
describe("runPreRequestScript", () => {
|
||||
test("returns the updated environment properly", () => {
|
||||
@@ -1,4 +1,6 @@
|
||||
import { preventCyclicObjects } from "~/utils"
|
||||
import { preventCyclicObjects } from "~/shared-utils"
|
||||
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
describe("preventCyclicObjects", () => {
|
||||
test("succeeds with a simple object", () => {
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
|
||||
import { runTestScript } from "~/test-runner/node-vm"
|
||||
import { describe, expect, test } from "vitest"
|
||||
|
||||
import { runTestScript } from "~/node"
|
||||
import { TestResponse } from "~/types"
|
||||
|
||||
const fakeResponse: TestResponse = {
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./pre-request/node-vm"
|
||||
export * from "./test-runner/node-vm"
|
||||
2
packages/hoppscotch-js-sandbox/src/node/index.ts
Normal file
2
packages/hoppscotch-js-sandbox/src/node/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { runPreRequestScript } from "./pre-request"
|
||||
export { runTestScript } from "./test-runner"
|
||||
91
packages/hoppscotch-js-sandbox/src/node/pre-request.ts
Normal file
91
packages/hoppscotch-js-sandbox/src/node/pre-request.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/lib/TaskEither"
|
||||
import { createRequire } from "module"
|
||||
|
||||
import type ivmT from "isolated-vm"
|
||||
|
||||
import { TestResult } from "~/types"
|
||||
import { getPreRequestScriptMethods } from "~/shared-utils"
|
||||
import { getSerializedAPIMethods } from "./utils"
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url)
|
||||
const ivm = nodeRequire("isolated-vm")
|
||||
|
||||
export const runPreRequestScript = (
|
||||
preRequestScript: string,
|
||||
envs: TestResult["envs"]
|
||||
): TE.TaskEither<string, TestResult["envs"]> =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
const isolate: ivmT.Isolate = new ivm.Isolate()
|
||||
const context = await isolate.createContext()
|
||||
return { isolate, context }
|
||||
},
|
||||
(reason) => `Context initialization failed: ${reason}`
|
||||
),
|
||||
TE.chain(({ isolate, context }) =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
const jail = context.global
|
||||
|
||||
const { pw, updatedEnvs } = getPreRequestScriptMethods(envs)
|
||||
|
||||
const serializedAPIMethods = getSerializedAPIMethods(pw)
|
||||
jail.setSync("serializedAPIMethods", serializedAPIMethods, {
|
||||
copy: true,
|
||||
})
|
||||
|
||||
jail.setSync("atob", atob)
|
||||
jail.setSync("btoa", btoa)
|
||||
|
||||
// Methods in the isolate context can't be invoked straightaway
|
||||
const finalScript = `
|
||||
const pw = new Proxy(serializedAPIMethods, {
|
||||
get: (pwObjTarget, pwObjProp) => {
|
||||
const topLevelEntry = pwObjTarget[pwObjProp]
|
||||
|
||||
// "pw.env" set of API methods
|
||||
if (topLevelEntry && typeof topLevelEntry === "object") {
|
||||
return new Proxy(topLevelEntry, {
|
||||
get: (subTarget, subProp) => {
|
||||
const subLevelProperty = subTarget[subProp]
|
||||
if (subLevelProperty && subLevelProperty.typeof === "function") {
|
||||
return (...args) => subLevelProperty.applySync(null, args)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
${preRequestScript}
|
||||
`
|
||||
|
||||
// Create a script and compile it
|
||||
const script = await isolate.compileScript(finalScript)
|
||||
|
||||
// Run the pre-request script in the provided context
|
||||
await script.run(context)
|
||||
|
||||
return updatedEnvs
|
||||
},
|
||||
(reason) => reason
|
||||
),
|
||||
TE.fold(
|
||||
(error) => TE.left(`Script execution failed: ${error}`),
|
||||
(result) =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
await isolate.dispose()
|
||||
return result
|
||||
},
|
||||
(disposeError) => `Isolate disposal failed: ${disposeError}`
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
217
packages/hoppscotch-js-sandbox/src/node/test-runner.ts
Normal file
217
packages/hoppscotch-js-sandbox/src/node/test-runner.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { createRequire } from "module"
|
||||
|
||||
import type ivmT from "isolated-vm"
|
||||
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
import {
|
||||
getTestRunnerScriptMethods,
|
||||
preventCyclicObjects,
|
||||
} from "~/shared-utils"
|
||||
import { getSerializedAPIMethods } from "./utils"
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url)
|
||||
const ivm = nodeRequire("isolated-vm")
|
||||
|
||||
export const runTestScript = (
|
||||
testScript: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse
|
||||
): TE.TaskEither<string, TestResult> =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
const isolate: ivmT.Isolate = new ivm.Isolate()
|
||||
const context = await isolate.createContext()
|
||||
return { isolate, context }
|
||||
},
|
||||
(reason) => `Context initialization failed: ${reason}`
|
||||
),
|
||||
TE.chain(({ isolate, context }) =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () =>
|
||||
executeScriptInContext(
|
||||
testScript,
|
||||
envs,
|
||||
response,
|
||||
isolate,
|
||||
context
|
||||
),
|
||||
(reason) => `Script execution failed: ${reason}`
|
||||
),
|
||||
TE.chain((result) =>
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
await isolate.dispose()
|
||||
return result
|
||||
},
|
||||
(disposeReason) => `Isolate disposal failed: ${disposeReason}`
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
const executeScriptInContext = (
|
||||
testScript: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse,
|
||||
isolate: ivmT.Isolate,
|
||||
context: ivmT.Context
|
||||
): Promise<TestResult> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Parse response object
|
||||
const responseObjHandle = preventCyclicObjects(response)
|
||||
if (E.isLeft(responseObjHandle)) {
|
||||
return reject(`Response parsing failed: ${responseObjHandle.left}`)
|
||||
}
|
||||
|
||||
const jail = context.global
|
||||
|
||||
const { pw, testRunStack, updatedEnvs } = getTestRunnerScriptMethods(envs)
|
||||
|
||||
const serializedAPIMethods = getSerializedAPIMethods({
|
||||
...pw,
|
||||
response: responseObjHandle.right,
|
||||
})
|
||||
jail.setSync("serializedAPIMethods", serializedAPIMethods, { copy: true })
|
||||
|
||||
jail.setSync("atob", atob)
|
||||
jail.setSync("btoa", btoa)
|
||||
|
||||
jail.setSync("ivm", ivm)
|
||||
|
||||
// Methods in the isolate context can't be invoked straightaway
|
||||
const finalScript = `
|
||||
const pw = new Proxy(serializedAPIMethods, {
|
||||
get: (pwObj, pwObjProp) => {
|
||||
// pw.expect(), pw.env, etc.
|
||||
const topLevelEntry = pwObj[pwObjProp]
|
||||
|
||||
// If the entry exists and is a function
|
||||
// pw.expect(), pw.test(), etc.
|
||||
if (topLevelEntry && topLevelEntry.typeof === "function") {
|
||||
// pw.test() just involves invoking the function via "applySync()"
|
||||
if (pwObjProp === "test") {
|
||||
return (...args) => topLevelEntry.applySync(null, args)
|
||||
}
|
||||
|
||||
// pw.expect() returns an object with matcher methods
|
||||
return (...args) => {
|
||||
// Invoke "pw.expect()" and get access to the object with matcher methods
|
||||
const expectFnResult = topLevelEntry.applySync(
|
||||
null,
|
||||
args.map((expectVal) => {
|
||||
if (typeof expectVal === "object") {
|
||||
if (expectVal === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Only arrays and objects stringified here should be parsed from the "pw.expect()" method definition
|
||||
// The usecase is that any JSON string supplied should be preserved
|
||||
// An extra "isStringifiedWithinIsolate" prop is added to indicate it has to be parsed
|
||||
|
||||
if (Array.isArray(expectVal)) {
|
||||
return JSON.stringify({
|
||||
arr: expectVal,
|
||||
isStringifiedWithinIsolate: true,
|
||||
})
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
...expectVal,
|
||||
isStringifiedWithinIsolate: true,
|
||||
})
|
||||
}
|
||||
|
||||
return expectVal
|
||||
})
|
||||
)
|
||||
|
||||
// Matcher methods that can be chained with "pw.expect()"
|
||||
// pw.expect().toBe(), etc
|
||||
if (expectFnResult.typeof === "object") {
|
||||
// Access the getter that points to the negated matcher methods via "{ accessors: true }"
|
||||
const matcherMethods = {
|
||||
not: expectFnResult.getSync("not", { accessors: true }),
|
||||
}
|
||||
|
||||
// Serialize matcher methods for use in the isolate context
|
||||
const matcherMethodNames = [
|
||||
"toBe",
|
||||
"toBeLevel2xx",
|
||||
"toBeLevel3xx",
|
||||
"toBeLevel4xx",
|
||||
"toBeLevel5xx",
|
||||
"toBeType",
|
||||
"toHaveLength",
|
||||
"toInclude",
|
||||
]
|
||||
matcherMethodNames.forEach((methodName) => {
|
||||
matcherMethods[methodName] = expectFnResult.getSync(methodName)
|
||||
})
|
||||
|
||||
return new Proxy(matcherMethods, {
|
||||
get: (matcherMethodTarget, matcherMethodProp) => {
|
||||
// pw.expect().not.toBe(), etc
|
||||
const matcherMethodEntry = matcherMethodTarget[matcherMethodProp]
|
||||
|
||||
if (matcherMethodProp === "not") {
|
||||
return new Proxy(matcherMethodEntry, {
|
||||
get: (negatedObjTarget, negatedObjprop) => {
|
||||
// Return the negated matcher method defn that is invoked from the test script
|
||||
const negatedMatcherMethodDefn = negatedObjTarget.getSync(negatedObjprop)
|
||||
return negatedMatcherMethodDefn
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Return the matcher method defn that is invoked from the test script
|
||||
return matcherMethodEntry
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "pw.env" set of API methods
|
||||
if (typeof topLevelEntry === "object" && pwObjProp !== "response") {
|
||||
return new Proxy(topLevelEntry, {
|
||||
get: (subTarget, subProp) => {
|
||||
const subLevelProperty = subTarget[subProp]
|
||||
if (
|
||||
subLevelProperty &&
|
||||
subLevelProperty.typeof === "function"
|
||||
) {
|
||||
return (...args) => subLevelProperty.applySync(null, args)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return topLevelEntry
|
||||
},
|
||||
})
|
||||
|
||||
${testScript}
|
||||
`
|
||||
|
||||
// Create a script and compile it
|
||||
const script = isolate.compileScript(finalScript)
|
||||
|
||||
// Run the test script in the provided context
|
||||
script
|
||||
.then((script) => script.run(context))
|
||||
.then(() => {
|
||||
resolve({
|
||||
tests: testRunStack,
|
||||
envs: updatedEnvs,
|
||||
})
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
23
packages/hoppscotch-js-sandbox/src/node/utils.ts
Normal file
23
packages/hoppscotch-js-sandbox/src/node/utils.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createRequire } from "module"
|
||||
|
||||
const nodeRequire = createRequire(import.meta.url)
|
||||
const ivm = nodeRequire("isolated-vm")
|
||||
|
||||
// Helper function to recursively wrap methods in `ivm.Reference`
|
||||
export const getSerializedAPIMethods = (
|
||||
namespaceObj: Record<string, unknown>
|
||||
): Record<string, unknown> => {
|
||||
const result: Record<string, unknown> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(namespaceObj)) {
|
||||
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
||||
result[key] = getSerializedAPIMethods(value as Record<string, unknown>)
|
||||
} else if (typeof value === "function") {
|
||||
result[key] = new ivm.Reference(value)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as TE from "fp-ts/lib/TaskEither"
|
||||
import { createContext, runInContext } from "vm"
|
||||
|
||||
import { TestResult } from "~/types"
|
||||
import { getPreRequestScriptMethods } from "~/utils"
|
||||
|
||||
export const runPreRequestScript = (
|
||||
preRequestScript: string,
|
||||
envs: TestResult["envs"]
|
||||
): TE.TaskEither<string, TestResult["envs"]> =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
return createContext()
|
||||
},
|
||||
(reason) => `Context initialization failed: ${reason}`
|
||||
),
|
||||
TE.chain((context) =>
|
||||
TE.tryCatch(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
const { pw, updatedEnvs } = getPreRequestScriptMethods(envs)
|
||||
|
||||
// Expose pw to the context
|
||||
context.pw = pw
|
||||
context.atob = atob
|
||||
context.btoa = btoa
|
||||
|
||||
// Run the pre-request script in the provided context
|
||||
runInContext(preRequestScript, context)
|
||||
|
||||
resolve(updatedEnvs)
|
||||
}),
|
||||
(reason) => `Script execution failed: ${reason}`
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -182,6 +182,36 @@ const getSharedMethods = (envs: TestResult["envs"]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getResolvedExpectValue = (expectVal: any) => {
|
||||
if (typeof expectVal !== "string") {
|
||||
return expectVal
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedExpectVal = JSON.parse(expectVal)
|
||||
|
||||
// Supplying non-primitive values is not permitted in the `isStringifiedWithinIsolate` property indicates that the object was stringified before executing the script from the isolate context
|
||||
// This is done to ensure a JSON string supplied as the "expectVal" is not parsed and preserved as is
|
||||
if (typeof parsedExpectVal === "object") {
|
||||
if (parsedExpectVal.isStringifiedWithinIsolate !== true) {
|
||||
return expectVal
|
||||
}
|
||||
|
||||
// For an array, the contents are stored in the `arr` property
|
||||
if (Array.isArray(parsedExpectVal.arr)) {
|
||||
return parsedExpectVal.arr
|
||||
}
|
||||
|
||||
delete parsedExpectVal.isStringifiedWithinIsolate
|
||||
return parsedExpectVal
|
||||
}
|
||||
|
||||
return expectVal
|
||||
} catch (_) {
|
||||
return expectVal
|
||||
}
|
||||
}
|
||||
|
||||
export function preventCyclicObjects(
|
||||
obj: Record<string, any>
|
||||
): E.Left<string> | E.Right<Record<string, any>> {
|
||||
@@ -215,15 +245,18 @@ export const createExpectation = (
|
||||
) => {
|
||||
const result: Record<string, unknown> = {}
|
||||
|
||||
// Non-primitive values supplied are stringified in the isolate context
|
||||
const resolvedExpectVal = getResolvedExpectValue(expectVal)
|
||||
|
||||
const toBeFn = (expectedVal: any) => {
|
||||
let assertion = expectVal === expectedVal
|
||||
let assertion = resolvedExpectVal === expectedVal
|
||||
|
||||
if (negated) {
|
||||
assertion = !assertion
|
||||
}
|
||||
|
||||
const status = assertion ? "pass" : "fail"
|
||||
const message = `Expected '${expectVal}' to${
|
||||
const message = `Expected '${resolvedExpectVal}' to${
|
||||
negated ? " not" : ""
|
||||
} be '${expectedVal}'`
|
||||
|
||||
@@ -240,7 +273,7 @@ export const createExpectation = (
|
||||
rangeStart: number,
|
||||
rangeEnd: number
|
||||
) => {
|
||||
const parsedExpectVal = parseInt(expectVal)
|
||||
const parsedExpectVal = parseInt(resolvedExpectVal)
|
||||
|
||||
if (!Number.isNaN(parsedExpectVal)) {
|
||||
let assertion =
|
||||
@@ -260,7 +293,7 @@ export const createExpectation = (
|
||||
message,
|
||||
})
|
||||
} else {
|
||||
const message = `Expected ${level}-level status but could not parse value '${expectVal}'`
|
||||
const message = `Expected ${level}-level status but could not parse value '${resolvedExpectVal}'`
|
||||
currTestStack[currTestStack.length - 1].expectResults.push({
|
||||
status: "error",
|
||||
message,
|
||||
@@ -288,14 +321,14 @@ export const createExpectation = (
|
||||
"function",
|
||||
].includes(expectedType)
|
||||
) {
|
||||
let assertion = typeof expectVal === expectedType
|
||||
let assertion = typeof resolvedExpectVal === expectedType
|
||||
|
||||
if (negated) {
|
||||
assertion = !assertion
|
||||
}
|
||||
|
||||
const status = assertion ? "pass" : "fail"
|
||||
const message = `Expected '${expectVal}' to${
|
||||
const message = `Expected '${resolvedExpectVal}' to${
|
||||
negated ? " not" : ""
|
||||
} be type '${expectedType}'`
|
||||
|
||||
@@ -316,7 +349,12 @@ export const createExpectation = (
|
||||
}
|
||||
|
||||
const toHaveLengthFn = (expectedLength: any) => {
|
||||
if (!(Array.isArray(expectVal) || typeof expectVal === "string")) {
|
||||
if (
|
||||
!(
|
||||
Array.isArray(resolvedExpectVal) ||
|
||||
typeof resolvedExpectVal === "string"
|
||||
)
|
||||
) {
|
||||
const message =
|
||||
"Expected toHaveLength to be called for an array or string"
|
||||
currTestStack[currTestStack.length - 1].expectResults.push({
|
||||
@@ -328,7 +366,7 @@ export const createExpectation = (
|
||||
}
|
||||
|
||||
if (typeof expectedLength === "number" && !Number.isNaN(expectedLength)) {
|
||||
let assertion = expectVal.length === expectedLength
|
||||
let assertion = resolvedExpectVal.length === expectedLength
|
||||
|
||||
if (negated) {
|
||||
assertion = !assertion
|
||||
@@ -355,7 +393,12 @@ export const createExpectation = (
|
||||
}
|
||||
|
||||
const toIncludeFn = (needle: any) => {
|
||||
if (!(Array.isArray(expectVal) || typeof expectVal === "string")) {
|
||||
if (
|
||||
!(
|
||||
Array.isArray(resolvedExpectVal) ||
|
||||
typeof resolvedExpectVal === "string"
|
||||
)
|
||||
) {
|
||||
const message = "Expected toInclude to be called for an array or string"
|
||||
currTestStack[currTestStack.length - 1].expectResults.push({
|
||||
status: "error",
|
||||
@@ -382,13 +425,13 @@ export const createExpectation = (
|
||||
return undefined
|
||||
}
|
||||
|
||||
let assertion = expectVal.includes(needle)
|
||||
let assertion = resolvedExpectVal.includes(needle)
|
||||
|
||||
if (negated) {
|
||||
assertion = !assertion
|
||||
}
|
||||
|
||||
const expectValPretty = JSON.stringify(expectVal)
|
||||
const expectValPretty = JSON.stringify(resolvedExpectVal)
|
||||
const needlePretty = JSON.stringify(needle)
|
||||
const status = assertion ? "pass" : "fail"
|
||||
const message = `Expected ${expectValPretty} to${
|
||||
@@ -1,57 +0,0 @@
|
||||
import * as E from "fp-ts/Either"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import { pipe } from "fp-ts/function"
|
||||
import { createContext, runInContext } from "vm"
|
||||
|
||||
import { TestResponse, TestResult } from "~/types"
|
||||
import { getTestRunnerScriptMethods, preventCyclicObjects } from "~/utils"
|
||||
|
||||
export const runTestScript = (
|
||||
testScript: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse
|
||||
): TE.TaskEither<string, TestResult> =>
|
||||
pipe(
|
||||
TE.tryCatch(
|
||||
async () => {
|
||||
return createContext()
|
||||
},
|
||||
(reason) => `Context initialization failed: ${reason}`
|
||||
),
|
||||
TE.chain((context) =>
|
||||
TE.tryCatch(
|
||||
() => executeScriptInContext(testScript, envs, response, context),
|
||||
(reason) => `Script execution failed: ${reason}`
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
const executeScriptInContext = (
|
||||
testScript: string,
|
||||
envs: TestResult["envs"],
|
||||
response: TestResponse,
|
||||
context: any
|
||||
): Promise<TestResult> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Parse response object
|
||||
const responseObjHandle = preventCyclicObjects(response)
|
||||
if (E.isLeft(responseObjHandle)) {
|
||||
return reject(`Response parsing failed: ${responseObjHandle.left}`)
|
||||
}
|
||||
|
||||
const { pw, testRunStack, updatedEnvs } = getTestRunnerScriptMethods(envs)
|
||||
|
||||
// Expose pw to the context
|
||||
context.pw = { ...pw, response: responseObjHandle.right }
|
||||
context.atob = atob
|
||||
context.btoa = btoa
|
||||
|
||||
// Run the test script in the provided context
|
||||
runInContext(testScript, context)
|
||||
|
||||
resolve({
|
||||
tests: testRunStack,
|
||||
envs: updatedEnvs,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./pre-request/web-worker"
|
||||
export * from "./test-runner/web-worker"
|
||||
2
packages/hoppscotch-js-sandbox/src/web/index.ts
Normal file
2
packages/hoppscotch-js-sandbox/src/web/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { runPreRequestScript } from "./pre-request"
|
||||
export { runTestScript } from "./test-runner"
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
|
||||
import { TestResult } from "~/types"
|
||||
import { getPreRequestScriptMethods } from "~/utils"
|
||||
import { getPreRequestScriptMethods } from "~/shared-utils"
|
||||
|
||||
const executeScriptInContext = (
|
||||
preRequestScript: string,
|
||||
@@ -2,7 +2,10 @@ import * as E from "fp-ts/Either"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
|
||||
import { SandboxTestResult, TestResponse, TestResult } from "~/types"
|
||||
import { getTestRunnerScriptMethods, preventCyclicObjects } from "~/utils"
|
||||
import {
|
||||
getTestRunnerScriptMethods,
|
||||
preventCyclicObjects,
|
||||
} from "~/shared-utils"
|
||||
|
||||
const executeScriptInContext = (
|
||||
testScript: string,
|
||||
@@ -7,16 +7,20 @@ export default defineConfig({
|
||||
emptyOutDir: true,
|
||||
lib: {
|
||||
entry: {
|
||||
web: "./src/web.ts",
|
||||
node: "./src/node.ts",
|
||||
web: "./src/web/index.ts",
|
||||
node: "./src/node/index.ts",
|
||||
},
|
||||
name: "js-sandbox",
|
||||
formats: ["es", "cjs"],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: ["vm"],
|
||||
external: ["module"],
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: "node",
|
||||
setupFiles: ["./setupFiles.ts"],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"~": resolve(__dirname, "./src"),
|
||||
|
||||
4
packages/hoppscotch-js-sandbox/web.d.ts
vendored
4
packages/hoppscotch-js-sandbox/web.d.ts
vendored
@@ -1,2 +1,2 @@
|
||||
export { default } from "./dist/web.d.ts"
|
||||
export * from "./dist/web.d.ts"
|
||||
export { default } from "./dist/web/index.d.ts"
|
||||
export * from "./dist/web/index.d.ts"
|
||||
|
||||
Reference in New Issue
Block a user