Просмотр исходного кода

chore: merge hoppscotch/release/2023.12.6 into hoppscotch/release/2024.3.0

Andrew Bastin 1 год назад
Родитель
Сommit
55a94bdccc

+ 1 - 1
packages/hoppscotch-backend/package.json

@@ -1,6 +1,6 @@
 {
   "name": "hoppscotch-backend",
-  "version": "2023.12.3",
+  "version": "2023.12.6",
   "description": "",
   "author": "",
   "private": true,

+ 1 - 1
packages/hoppscotch-backend/src/mailer/mailer.service.ts

@@ -25,7 +25,7 @@ export class MailerService {
   ): string {
     switch (mailDesc.template) {
       case 'team-invitation':
-        return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
+        return `A user has invited you to join a team workspace in Hoppscotch`;
 
       case 'user-invitation':
         return 'Sign in to Hoppscotch';

+ 8 - 2
packages/hoppscotch-backend/src/mailer/templates/team-invitation.hbs

@@ -27,6 +27,12 @@
       color: #3869D4;
     }
 
+    a.nohighlight {
+      color: inherit !important;
+      text-decoration: none !important;
+      cursor: default !important;
+    }
+
     a img {
       border: none;
     }
@@ -458,7 +464,7 @@
                     <td class="content-cell">
                       <div class="f-fallback">
 <h1>Hi there,</h1>
-<p>{{invitee}} with {{invite_team_name}} has invited you to use Hoppscotch to collaborate with them. Click the button below to set up your account and get started:</p>
+<p><a class="nohighlight" name="invitee" href="#">{{invitee}}</a> with <a class="nohighlight" name="invite_team_name" href="#">{{invite_team_name}}</a> has invited you to use Hoppscotch to collaborate with them. Click the button below to set up your account and get started:</p>
 <!-- Action -->
 <table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
   <tr>
@@ -484,7 +490,7 @@
   Welcome aboard, <br />
   Your friends at Hoppscotch
 </p>
-<p><strong>P.S.</strong> If you don't associate with {{invitee}} or {{invite_team_name}}, just ignore this email.</p>
+<p><strong>P.S.</strong> If you don't associate with <a class="nohighlight" name="invitee" href="#">{{invitee}}</a> or <a class="nohighlight" name="invite_team_name" href="#">{{invite_team_name}}</a>, just ignore this email.</p>
 <!-- Sub copy -->
 <table class="body-sub">
   <tr>

+ 63 - 57
packages/hoppscotch-backend/src/mailer/templates/user-invitation.hbs

@@ -14,7 +14,7 @@
   -->
     <style type="text/css" rel="stylesheet" media="all">
     /* Base ------------------------------ */
-    
+
     @import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
     body {
       width: 100% !important;
@@ -22,19 +22,25 @@
       margin: 0;
       -webkit-text-size-adjust: none;
     }
-    
+
     a {
       color: #3869D4;
     }
-    
+
+    a.nohighlight {
+      color: inherit !important;
+      text-decoration: none !important;
+      cursor: default !important;
+    }
+
     a img {
       border: none;
     }
-    
+
     td {
       word-break: break-word;
     }
-    
+
     .preheader {
       display: none !important;
       visibility: hidden;
@@ -47,13 +53,13 @@
       overflow: hidden;
     }
     /* Type ------------------------------ */
-    
+
     body,
     td,
     th {
       font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
     }
-    
+
     h1 {
       margin-top: 0;
       color: #333333;
@@ -61,7 +67,7 @@
       font-weight: bold;
       text-align: left;
     }
-    
+
     h2 {
       margin-top: 0;
       color: #333333;
@@ -69,7 +75,7 @@
       font-weight: bold;
       text-align: left;
     }
-    
+
     h3 {
       margin-top: 0;
       color: #333333;
@@ -77,12 +83,12 @@
       font-weight: bold;
       text-align: left;
     }
-    
+
     td,
     th {
       font-size: 16px;
     }
-    
+
     p,
     ul,
     ol,
@@ -91,25 +97,25 @@
       font-size: 16px;
       line-height: 1.625;
     }
-    
+
     p.sub {
       font-size: 13px;
     }
     /* Utilities ------------------------------ */
-    
+
     .align-right {
       text-align: right;
     }
-    
+
     .align-left {
       text-align: left;
     }
-    
+
     .align-center {
       text-align: center;
     }
     /* Buttons ------------------------------ */
-    
+
     .button {
       background-color: #3869D4;
       border-top: 10px solid #3869D4;
@@ -124,7 +130,7 @@
       -webkit-text-size-adjust: none;
       box-sizing: border-box;
     }
-    
+
     .button--green {
       background-color: #22BC66;
       border-top: 10px solid #22BC66;
@@ -132,7 +138,7 @@
       border-bottom: 10px solid #22BC66;
       border-left: 18px solid #22BC66;
     }
-    
+
     .button--red {
       background-color: #FF6136;
       border-top: 10px solid #FF6136;
@@ -140,7 +146,7 @@
       border-bottom: 10px solid #FF6136;
       border-left: 18px solid #FF6136;
     }
-    
+
     @media only screen and (max-width: 500px) {
       .button {
         width: 100% !important;
@@ -148,21 +154,21 @@
       }
     }
     /* Attribute list ------------------------------ */
-    
+
     .attributes {
       margin: 0 0 21px;
     }
-    
+
     .attributes_content {
       background-color: #F4F4F7;
       padding: 16px;
     }
-    
+
     .attributes_item {
       padding: 0;
     }
     /* Related Items ------------------------------ */
-    
+
     .related {
       width: 100%;
       margin: 0;
@@ -171,31 +177,31 @@
       -premailer-cellpadding: 0;
       -premailer-cellspacing: 0;
     }
-    
+
     .related_item {
       padding: 10px 0;
       color: #CBCCCF;
       font-size: 15px;
       line-height: 18px;
     }
-    
+
     .related_item-title {
       display: block;
       margin: .5em 0 0;
     }
-    
+
     .related_item-thumb {
       display: block;
       padding-bottom: 10px;
     }
-    
+
     .related_heading {
       border-top: 1px solid #CBCCCF;
       text-align: center;
       padding: 25px 0 10px;
     }
     /* Discount Code ------------------------------ */
-    
+
     .discount {
       width: 100%;
       margin: 0;
@@ -206,33 +212,33 @@
       background-color: #F4F4F7;
       border: 2px dashed #CBCCCF;
     }
-    
+
     .discount_heading {
       text-align: center;
     }
-    
+
     .discount_body {
       text-align: center;
       font-size: 15px;
     }
     /* Social Icons ------------------------------ */
-    
+
     .social {
       width: auto;
     }
-    
+
     .social td {
       padding: 0;
       width: auto;
     }
-    
+
     .social_icon {
       height: 20px;
       margin: 0 8px 10px 8px;
       padding: 0;
     }
     /* Data table ------------------------------ */
-    
+
     .purchase {
       width: 100%;
       margin: 0;
@@ -241,7 +247,7 @@
       -premailer-cellpadding: 0;
       -premailer-cellspacing: 0;
     }
-    
+
     .purchase_content {
       width: 100%;
       margin: 0;
@@ -250,50 +256,50 @@
       -premailer-cellpadding: 0;
       -premailer-cellspacing: 0;
     }
-    
+
     .purchase_item {
       padding: 10px 0;
       color: #51545E;
       font-size: 15px;
       line-height: 18px;
     }
-    
+
     .purchase_heading {
       padding-bottom: 8px;
       border-bottom: 1px solid #EAEAEC;
     }
-    
+
     .purchase_heading p {
       margin: 0;
       color: #85878E;
       font-size: 12px;
     }
-    
+
     .purchase_footer {
       padding-top: 15px;
       border-top: 1px solid #EAEAEC;
     }
-    
+
     .purchase_total {
       margin: 0;
       text-align: right;
       font-weight: bold;
       color: #333333;
     }
-    
+
     .purchase_total--label {
       padding: 0 15px 0 0;
     }
-    
+
     body {
       background-color: #F2F4F6;
       color: #51545E;
     }
-    
+
     p {
       color: #51545E;
     }
-    
+
     .email-wrapper {
       width: 100%;
       margin: 0;
@@ -303,7 +309,7 @@
       -premailer-cellspacing: 0;
       background-color: #F2F4F6;
     }
-    
+
     .email-content {
       width: 100%;
       margin: 0;
@@ -313,16 +319,16 @@
       -premailer-cellspacing: 0;
     }
     /* Masthead ----------------------- */
-    
+
     .email-masthead {
       padding: 25px 0;
       text-align: center;
     }
-    
+
     .email-masthead_logo {
       width: 94px;
     }
-    
+
     .email-masthead_name {
       font-size: 16px;
       font-weight: bold;
@@ -331,7 +337,7 @@
       text-shadow: 0 1px 0 white;
     }
     /* Body ------------------------------ */
-    
+
     .email-body {
       width: 100%;
       margin: 0;
@@ -340,7 +346,7 @@
       -premailer-cellpadding: 0;
       -premailer-cellspacing: 0;
     }
-    
+
     .email-body_inner {
       width: 570px;
       margin: 0 auto;
@@ -350,7 +356,7 @@
       -premailer-cellspacing: 0;
       background-color: #FFFFFF;
     }
-    
+
     .email-footer {
       width: 570px;
       margin: 0 auto;
@@ -360,11 +366,11 @@
       -premailer-cellspacing: 0;
       text-align: center;
     }
-    
+
     .email-footer p {
       color: #A8AAAF;
     }
-    
+
     .body-action {
       width: 100%;
       margin: 30px auto;
@@ -374,25 +380,25 @@
       -premailer-cellspacing: 0;
       text-align: center;
     }
-    
+
     .body-sub {
       margin-top: 25px;
       padding-top: 25px;
       border-top: 1px solid #EAEAEC;
     }
-    
+
     .content-cell {
       padding: 45px;
     }
     /*Media Queries ------------------------------ */
-    
+
     @media only screen and (max-width: 600px) {
       .email-body_inner,
       .email-footer {
         width: 100% !important;
       }
     }
-    
+
     @media (prefers-color-scheme: dark) {
       body,
       .email-body,

+ 5 - 1
packages/hoppscotch-cli/package.json

@@ -58,9 +58,13 @@
     "@types/qs": "^6.9.11",
     "fp-ts": "^2.16.2",
     "jest": "^29.7.0",
+    "lodash": "^4.17.21",
     "prettier": "^3.2.4",
+    "qs": "^6.11.2",
     "ts-jest": "^29.1.2",
     "tsup": "^8.0.1",
-    "typescript": "^5.3.3"
+    "typescript": "^5.3.3",
+    "verzod": "^0.2.2",
+    "zod": "^3.22.4"
   }
 }

+ 201 - 85
packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts

@@ -3,138 +3,247 @@ import { ExecException } from "child_process";
 import { HoppErrorCode } from "../../types/errors";
 import { runCLI, getErrorCode, getTestJsonFilePath } from "../utils";
 
-describe("Test 'hopp test <file>' command:", () => {
-  test("No collection file path provided.", async () => {
-    const args = "test";
-    const { stderr } = await runCLI(args);
+describe("Test `hopp test <file>` command:", () => {
+  describe("Argument parsing", () => {
+    test("Errors with the code `INVALID_ARGUMENT` for not supplying enough arguments", async () => {
+      const args = "test";
+      const { stderr } = await runCLI(args);
+
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
+    });
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
-  });
+    test("Errors with the code `INVALID_ARGUMENT` for an invalid command", async () => {
+      const args = "invalid-arg";
+      const { stderr } = await runCLI(args);
 
-  test("Collection file not found.", async () => {
-    const args = "test notfound.json";
-    const { stderr } = await runCLI(args);
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
+    });
+  })
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
-  });
+  describe("Supplied collection export file validations", () => {
+    test("Errors with the code `FILE_NOT_FOUND` if the supplied collection export file doesn't exist", async () => {
+      const args = "test notfound.json";
+      const { stderr } = await runCLI(args);
 
-  test("Collection file is invalid JSON.", async () => {
-    const args = `test ${getTestJsonFilePath(
-      "malformed-collection.json"
-    )}`;
-    const { stderr } = await runCLI(args);
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
+    });
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
-  });
+    test("Errors with the code UNKNOWN_ERROR if the supplied collection export file content isn't valid JSON", async () => {
+      const args = `test ${getTestJsonFilePath("malformed-coll.json", "collection")}`;
+      const { stderr } = await runCLI(args);
 
-  test("Malformed collection file.", async () => {
-    const args = `test ${getTestJsonFilePath(
-      "malformed-collection2.json"
-    )}`;
-    const { stderr } = await runCLI(args);
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("UNKNOWN_ERROR");
+    });
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
-  });
+    test("Errors with the code `MALFORMED_COLLECTION` if the supplied collection export file content is malformed", async () => {
+      const args = `test ${getTestJsonFilePath("malformed-coll-2.json", "collection")}`;
+      const { stderr } = await runCLI(args);
 
-  test("Invalid arguement.", async () => {
-    const args = "invalid-arg";
-    const { stderr } = await runCLI(args);
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("MALFORMED_COLLECTION");
+    });
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
-  });
+    test("Errors with the code `INVALID_FILE_TYPE` if the supplied collection export file doesn't end with the `.json` extension", async () => {
+      const args = `test ${getTestJsonFilePath("notjson-coll.txt", "collection")}`;
+      const { stderr } = await runCLI(args);
 
-  test("Collection file not JSON type.", async () => {
-    const args = `test ${getTestJsonFilePath("notjson.txt")}`;
-    const { stderr } = await runCLI(args);
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
+    });
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
+    test("Fails if the collection file includes scripts with incorrect API usage and failed assertions", async () => {
+      const args = `test ${getTestJsonFilePath("fails-coll.json", "collection")}`;
+      const { error } = await runCLI(args);
+
+      expect(error).not.toBeNull();
+      expect(error).toMatchObject(<ExecException>{
+        code: 1,
+      });
+    });
   });
 
-  test("Some errors occured (exit code 1).", async () => {
-    const args = `test ${getTestJsonFilePath("fails.json")}`;
+  test("Successfully processes a supplied collection export file of the expected format", async () => {
+    const args = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
     const { error } = await runCLI(args);
 
-    expect(error).not.toBeNull();
-    expect(error).toMatchObject(<ExecException>{
-      code: 1,
-    });
+    expect(error).toBeNull();
   });
 
-  test("No errors occured (exit code 0).", async () => {
-    const args = `test ${getTestJsonFilePath("passes.json")}`;
+  test("Successfully inherits headers and authorization set at the root collection", async () => {
+    const args = `test ${getTestJsonFilePath(
+      "collection-level-headers-auth-coll.json", "collection"
+    )}`;
     const { error } = await runCLI(args);
 
     expect(error).toBeNull();
   });
 
-  test("Supports inheriting headers and authorization set at the root collection", async () => {
-    const args = `test ${getTestJsonFilePath("collection-level-headers-auth.json")}`;
+  test("Persists environment variables set in the pre-request script for consumption in the test script", async () => {
+    const args = `test ${getTestJsonFilePath(
+      "pre-req-script-env-var-persistence-coll.json", "collection"
+    )}`;
     const { error } = await runCLI(args);
 
     expect(error).toBeNull();
-  })
+  });
 });
 
-describe("Test 'hopp test <file> --env <file>' command:", () => {
-  const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
-    "passes.json"
-  )}`;
+describe("Test `hopp test <file> --env <file>` command:", () => {
+  describe("Supplied environment export file validations", () => {
+    const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
 
-  test("No env file path provided.", async () => {
-    const args = `${VALID_TEST_ARGS} --env`;
-    const { stderr } = await runCLI(args);
+    test("Errors with the code `INVALID_ARGUMENT` if no file is supplied", async () => {
+      const args = `${VALID_TEST_ARGS} --env`;
+      const { stderr } = await runCLI(args);
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
-  });
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
+    });
 
-  test("ENV file not JSON type.", async () => {
-    const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath("notjson.txt")}`;
-    const { stderr } = await runCLI(args);
+    test("Errors with the code `INVALID_FILE_TYPE` if the supplied environment export file doesn't end with the `.json` extension", async () => {
+      const args = `${VALID_TEST_ARGS} --env ${getTestJsonFilePath(
+        "notjson-coll.txt", "collection"
+      )}`;
+      const { stderr } = await runCLI(args);
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
-  });
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("INVALID_FILE_TYPE");
+    });
 
-  test("ENV file not found.", async () => {
-    const args = `${VALID_TEST_ARGS} --env notfound.json`;
-    const { stderr } = await runCLI(args);
+    test("Errors with the code `FILE_NOT_FOUND` if the supplied environment export file doesn't exist", async () => {
+      const args = `${VALID_TEST_ARGS} --env notfound.json`;
+      const { stderr } = await runCLI(args);
 
-    const out = getErrorCode(stderr);
-    expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("FILE_NOT_FOUND");
+    });
+
+    test("Errors with the code `MALFORMED_ENV_FILE` on supplying a malformed environment export file", async () => {
+      const ENV_PATH = getTestJsonFilePath("malformed-envs.json", "environment");
+      const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
+      const { stderr } = await runCLI(args);
+
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("MALFORMED_ENV_FILE");
+    });
+
+    test("Errors with the code `BULK_ENV_FILE` on supplying an environment export file based on the bulk environment export format", async () => {
+      const ENV_PATH = getTestJsonFilePath("bulk-envs.json", "environment");
+      const args = `${VALID_TEST_ARGS} --env ${ENV_PATH}`;
+      const { stderr } = await runCLI(args);
+
+      const out = getErrorCode(stderr);
+      expect(out).toBe<HoppErrorCode>("BULK_ENV_FILE");
+    });
   });
 
-  test("No errors occured (exit code 0).", async () => {
-    const TESTS_PATH = getTestJsonFilePath("env-flag-tests.json");
-    const ENV_PATH = getTestJsonFilePath("env-flag-envs.json");
+  test("Successfully resolves values from the supplied environment export file", async () => {
+    const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
+    const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
     const args = `test ${TESTS_PATH} --env ${ENV_PATH}`;
 
     const { error } = await runCLI(args);
     expect(error).toBeNull();
   });
 
-  test("Correctly resolves environment variables referenced in the request body", async () => {
-    const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json");
-    const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json");
+  test("Successfully resolves environment variables referenced in the request body", async () => {
+    const COLL_PATH = getTestJsonFilePath("req-body-env-vars-coll.json", "collection");
+    const ENVS_PATH = getTestJsonFilePath("req-body-env-vars-envs.json", "environment");
     const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
 
     const { error } = await runCLI(args);
     expect(error).toBeNull();
   });
+
+  test("Works with shorth `-e` flag", async () => {
+    const TESTS_PATH = getTestJsonFilePath("env-flag-tests-coll.json", "collection");
+    const ENV_PATH = getTestJsonFilePath("env-flag-envs.json", "environment");
+    const args = `test ${TESTS_PATH} -e ${ENV_PATH}`;
+
+    const { error } = await runCLI(args);
+    expect(error).toBeNull();
+  });
+
+  describe("Secret environment variables", () => {
+    jest.setTimeout(10000);
+
+    // 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 () => {
+      const env = {
+        ...process.env,
+        secretBearerToken: "test-token",
+        secretBasicAuthUsername: "test-user",
+        secretBasicAuthPassword: "test-pass",
+        secretQueryParamValue: "secret-query-param-value",
+        secretBodyValue: "secret-body-value",
+        secretHeaderValue: "secret-header-value",
+      };
+
+      const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
+      const ENVS_PATH = getTestJsonFilePath("secret-envs.json", "environment");
+      const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
+
+      const { error, stdout } = await runCLI(args, { env });
+
+      expect(stdout).toContain(
+        "https://httpbin.org/basic-auth/*********/*********"
+      );
+      expect(error).toBeNull();
+    });
+
+    // Prefers values specified in the environment export file over values set in the system environment
+    test("Successfully picks the values for secret environment variables set directly in the environment export file and persists the environment variables set from the pre-request script", async () => {
+      const COLL_PATH = getTestJsonFilePath("secret-envs-coll.json", "collection");
+      const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
+      const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
+
+      const { error, stdout } = await runCLI(args);
+
+      expect(stdout).toContain(
+        "https://httpbin.org/basic-auth/*********/*********"
+      );
+      expect(error).toBeNull();
+    });
+
+    // Values set from the scripting context takes the highest precedence
+    test("Setting values for secret environment variables from the pre-request script overrides values set at the supplied environment export file", async () => {
+      const COLL_PATH = getTestJsonFilePath(
+        "secret-envs-persistence-coll.json", "collection"
+      );
+      const ENVS_PATH = getTestJsonFilePath("secret-supplied-values-envs.json", "environment");
+      const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
+
+      const { error, stdout } = await runCLI(args);
+
+      expect(stdout).toContain(
+        "https://httpbin.org/basic-auth/*********/*********"
+      );
+      expect(error).toBeNull();
+    });
+
+    test("Persists secret environment variable values set from the pre-request script for consumption in the request and post-request script context", async () => {
+      const COLL_PATH = getTestJsonFilePath(
+        "secret-envs-persistence-scripting-coll.json", "collection"
+      );
+      const ENVS_PATH = getTestJsonFilePath(
+        "secret-envs-persistence-scripting-envs.json", "environment"
+      );
+      const args = `test ${COLL_PATH} --env ${ENVS_PATH}`;
+
+      const { error } = await runCLI(args);
+      expect(error).toBeNull();
+    });
+  });
 });
 
-describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
-  const VALID_TEST_ARGS = `test ${getTestJsonFilePath(
-    "passes.json"
-  )}`;
+describe("Test `hopp test <file> --delay <delay_in_ms>` command:", () => {
+  const VALID_TEST_ARGS = `test ${getTestJsonFilePath("passes-coll.json", "collection")}`;
 
-  test("No value passed to delay flag.", async () => {
+  test("Errors with the code `INVALID_ARGUMENT` on not supplying a delay value", async () => {
     const args = `${VALID_TEST_ARGS} --delay`;
     const { stderr } = await runCLI(args);
 
@@ -142,7 +251,7 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
     expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
   });
 
-  test("Invalid value passed to delay flag.", async () => {
+  test("Errors with the code `INVALID_ARGUMENT` on supplying an invalid delay value", async () => {
     const args = `${VALID_TEST_ARGS} --delay 'NaN'`;
     const { stderr } = await runCLI(args);
 
@@ -150,10 +259,17 @@ describe("Test 'hopp test <file> --delay <delay_in_ms>' command:", () => {
     expect(out).toBe<HoppErrorCode>("INVALID_ARGUMENT");
   });
 
-  test("Valid value passed to delay flag.", async () => {
+  test("Successfully performs delayed request execution for a valid delay value", async () => {
     const args = `${VALID_TEST_ARGS} --delay 1`;
     const { error } = await runCLI(args);
 
     expect(error).toBeNull();
   });
+
+  test("Works with the short `-d` flag", async () => {
+    const args = `${VALID_TEST_ARGS} -d 1`;
+    const { error } = await runCLI(args);
+
+    expect(error).toBeNull();
+  });
 });

+ 0 - 0
packages/hoppscotch-cli/src/__tests__/samples/collection-level-headers-auth.json → packages/hoppscotch-cli/src/__tests__/samples/collections/collection-level-headers-auth-coll.json


+ 0 - 0
packages/hoppscotch-cli/src/__tests__/samples/env-flag-tests.json → packages/hoppscotch-cli/src/__tests__/samples/collections/env-flag-tests-coll.json


+ 0 - 0
packages/hoppscotch-cli/src/__tests__/samples/fails.json → packages/hoppscotch-cli/src/__tests__/samples/collections/fails-coll.json


+ 0 - 0
packages/hoppscotch-cli/src/__tests__/samples/malformed-collection2.json → packages/hoppscotch-cli/src/__tests__/samples/collections/malformed-coll-2.json


Некоторые файлы не были показаны из-за большого количества измененных файлов