From 641d924b52b065755ebc4e7a6771abc66eee40ab Mon Sep 17 00:00:00 2001 From: Bob Qiu Date: Wed, 28 Oct 2026 16:23:48 +0800 Subject: [PATCH] test(insomnia-test): add http-request, cookie and environment specs Co-Authored-By: Claude Sonnet 5.5 --- .../cookie-path-mismatch-not-sent.spec.ts | 50 ++++ .../cookie/cookie-same-key-overwrite.spec.ts | 47 ++++ .../cookie/expired-cookie-not-sent.spec.ts | 51 ++++ .../tests/cookie/happy-path.spec.ts | 62 +++++ ...t-prefix-cookie-requires-host-only.spec.ts | 55 +++++ .../http-only-cookie-always-sent.spec.ts | 66 +++++ .../cookie/raw-cookie-string-editor.spec.ts | 52 ++++ .../secure-cookie-not-sent-over-http.spec.ts | 62 +++++ .../case-sensitive-variable-names.spec.ts | 68 ++++++ .../circular-variable-reference.spec.ts | 58 +++++ ...e-and-duplicate-global-environment.spec.ts | 60 +++++ ...onment-variable-falls-back-to-base.spec.ts | 65 +++++ .../disabled-variable-mid-chain.spec.ts | 70 ++++++ .../tests/environment/json-raw-editor.spec.ts | 48 ++++ .../key-value-editor-blank-row.spec.ts | 56 +++++ .../mixed-variables-in-value.spec.ts | 63 +++++ .../nested-variable-resolution.spec.ts | 67 ++++++ ...rivate-environment-secret-variable.spec.ts | 91 +++++++ .../self-referencing-variable.spec.ts | 52 ++++ ...vironment-empty-falls-back-to-base.spec.ts | 73 ++++++ .../sub-environment-variable-priority.spec.ts | 64 +++++ .../table-editor-disable-row.spec.ts | 48 ++++ .../unlinked-environment-variable.spec.ts | 45 ++++ ...egacy-format-environment-variables.spec.ts | 87 +++++++ .../vault-legacy-formats.yaml | 173 ++++++++++++++ ...after-response-script-test-results.spec.ts | 76 ++++++ .../tests/http-request/body-types.spec.ts | 226 ++++++++++++++++++ .../cancel-in-flight-request.spec.ts | 56 +++++ .../http-request/chained-response-tag.spec.ts | 56 +++++ .../custom-ca-root-certificate.spec.ts | 53 ++++ .../folder-level-replace-in-utility.spec.ts | 53 ++++ ...generate-code-target-client-switch.spec.ts | 50 ++++ .../tests/http-request/generate-code.spec.ts | 35 +++ .../import-params-from-url-http.spec.ts | 52 ++++ .../http-request/ipv6-local-address.spec.ts | 38 +++ .../key-value-editor-blank-row.spec.ts | 61 +++++ .../liquid-template-isolation.spec.ts | 106 ++++++++ .../mtls-client-certificate.spec.ts | 78 ++++++ .../oauth1-authentication.spec.ts | 56 +++++ .../oauth2-authorization-code-pkce.spec.ts | 62 +++++ .../oauth2-authorization-code.spec.ts | 58 +++++ .../oauth2-client-credentials.spec.ts | 57 +++++ .../oauth2-folder-inherited-auth.spec.ts | 53 ++++ .../http-request/oauth2-implicit.spec.ts | 85 +++++++ .../http-request/oauth2-password.spec.ts | 60 +++++ .../oauth2-token-refresh-clear.spec.ts | 97 ++++++++ ...re-request-script-async-completion.spec.ts | 91 +++++++ .../pre-request-script-cookies.spec.ts | 42 ++++ .../pre-request-script-custom-error.spec.ts | 63 +++++ ...quest-script-environment-variables.spec.ts | 75 ++++++ ...re-request-script-eval-and-modules.spec.ts | 126 ++++++++++ ...equest-script-request-manipulation.spec.ts | 171 +++++++++++++ ...est-script-send-request-body-modes.spec.ts | 71 ++++++ .../tests/http-request/query-method.spec.ts | 42 ++++ ...query-params-transformed-correctly.spec.ts | 79 ++++++ .../replace-in-synchronous-behavior.spec.ts | 76 ++++++ .../cookie-tag-reads-linked-cookie.spec.ts | 38 +++ .../edit-existing-tag-argument.spec.ts | 45 ++++ .../external-vault-credentials.spec.ts | 110 +++++++++ .../file-tag-reads-allowed-file.spec.ts | 39 +++ .../file-tag-reads-allowed-file/fixture.txt | 1 + .../tests/template-tag/happy-path.spec.ts | 54 +++++ .../header-name-template-tag-resolves.spec.ts | 42 ++++ .../template-tag/header-templates.spec.ts | 41 ++++ 64 files changed, 4307 insertions(+) create mode 100644 packages/insomnia-test/tests/cookie/cookie-path-mismatch-not-sent.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/cookie-same-key-overwrite.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/expired-cookie-not-sent.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/happy-path.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/host-prefix-cookie-requires-host-only.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/http-only-cookie-always-sent.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/raw-cookie-string-editor.spec.ts create mode 100644 packages/insomnia-test/tests/cookie/secure-cookie-not-sent-over-http.spec.ts create mode 100644 packages/insomnia-test/tests/environment/case-sensitive-variable-names.spec.ts create mode 100644 packages/insomnia-test/tests/environment/circular-variable-reference.spec.ts create mode 100644 packages/insomnia-test/tests/environment/create-and-duplicate-global-environment.spec.ts create mode 100644 packages/insomnia-test/tests/environment/disabled-sub-environment-variable-falls-back-to-base.spec.ts create mode 100644 packages/insomnia-test/tests/environment/disabled-variable-mid-chain.spec.ts create mode 100644 packages/insomnia-test/tests/environment/json-raw-editor.spec.ts create mode 100644 packages/insomnia-test/tests/environment/key-value-editor-blank-row.spec.ts create mode 100644 packages/insomnia-test/tests/environment/mixed-variables-in-value.spec.ts create mode 100644 packages/insomnia-test/tests/environment/nested-variable-resolution.spec.ts create mode 100644 packages/insomnia-test/tests/environment/private-environment-secret-variable.spec.ts create mode 100644 packages/insomnia-test/tests/environment/self-referencing-variable.spec.ts create mode 100644 packages/insomnia-test/tests/environment/sub-environment-empty-falls-back-to-base.spec.ts create mode 100644 packages/insomnia-test/tests/environment/sub-environment-variable-priority.spec.ts create mode 100644 packages/insomnia-test/tests/environment/table-editor-disable-row.spec.ts create mode 100644 packages/insomnia-test/tests/environment/unlinked-environment-variable.spec.ts create mode 100644 packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-format-environment-variables.spec.ts create mode 100644 packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-formats.yaml create mode 100644 packages/insomnia-test/tests/http-request/after-response-script-test-results.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/body-types.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/cancel-in-flight-request.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/chained-response-tag.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/custom-ca-root-certificate.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/folder-level-replace-in-utility.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/generate-code-target-client-switch.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/generate-code.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/import-params-from-url-http.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/ipv6-local-address.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/key-value-editor-blank-row.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/liquid-template-isolation.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/mtls-client-certificate.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth1-authentication.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-authorization-code-pkce.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-authorization-code.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-client-credentials.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-folder-inherited-auth.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-implicit.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-password.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/oauth2-token-refresh-clear.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-async-completion.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-cookies.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-custom-error.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-environment-variables.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-eval-and-modules.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-request-manipulation.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/pre-request-script-send-request-body-modes.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/query-method.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/query-params-transformed-correctly.spec.ts create mode 100644 packages/insomnia-test/tests/http-request/replace-in-synchronous-behavior.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/cookie-tag-reads-linked-cookie.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/edit-existing-tag-argument.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/external-vault-credentials.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/file-tag-reads-allowed-file.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/fixture.txt create mode 100644 packages/insomnia-test/tests/template-tag/happy-path.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/header-name-template-tag-resolves.spec.ts create mode 100644 packages/insomnia-test/tests/template-tag/header-templates.spec.ts diff --git a/packages/insomnia-test/tests/cookie/cookie-path-mismatch-not-sent.spec.ts b/packages/insomnia-test/tests/cookie/cookie-path-mismatch-not-sent.spec.ts new file mode 100644 index 0000000000..d39532c8cc --- /dev/null +++ b/packages/insomnia-test/tests/cookie/cookie-path-mismatch-not-sent.spec.ts @@ -0,0 +1,50 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify Cookie With Mismatched Path Is Not Sent With HTTP Request", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const mismatchedCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/foo", + }; + const matchedCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + }; + await cookieFlow.link(request, [mismatchedCookie, matchedCookie]); + + const responseView = await httpRequestFlow.send(request); + + const { cookies } = responseView.body as { cookies: Record }; + expect(cookies).not.toHaveProperty(mismatchedCookie.key); + expect(cookies).toMatchObject({ [matchedCookie.key]: matchedCookie.value }); +}); diff --git a/packages/insomnia-test/tests/cookie/cookie-same-key-overwrite.spec.ts b/packages/insomnia-test/tests/cookie/cookie-same-key-overwrite.spec.ts new file mode 100644 index 0000000000..760b497d77 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/cookie-same-key-overwrite.spec.ts @@ -0,0 +1,47 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify Re-Linking Same Cookie Key Overwrites Previous Value", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const key = faker.string.alphanumeric(8); + const staleValue = faker.string.alphanumeric(8); + const freshValue = faker.string.alphanumeric(8); + await cookieFlow.link(request, [ + { key, value: staleValue, domain: "localhost", path: "/" }, + ]); + const cookieList = await cookieFlow.link(request, [ + { key, value: freshValue, domain: "localhost", path: "/" }, + ]); + + const responseView = await httpRequestFlow.send(request); + + const { cookies } = responseView.body as { cookies: Record }; + expect(cookieList.filter((cookie) => cookie.key === key)).toHaveLength(1); + expect(cookieList).toMatchObject([{ key, value: freshValue }]); + expect(cookies).toMatchObject({ [key]: freshValue }); +}); diff --git a/packages/insomnia-test/tests/cookie/expired-cookie-not-sent.spec.ts b/packages/insomnia-test/tests/cookie/expired-cookie-not-sent.spec.ts new file mode 100644 index 0000000000..613b315afa --- /dev/null +++ b/packages/insomnia-test/tests/cookie/expired-cookie-not-sent.spec.ts @@ -0,0 +1,51 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify Expired Cookie Is Not Sent With HTTP Request", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const expiredCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + expires: new Date("2000-01-01T00:00:00"), + }; + const activeCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + }; + await cookieFlow.link(request, [expiredCookie, activeCookie]); + + const responseView = await httpRequestFlow.send(request); + + const { cookies } = responseView.body as { cookies: Record }; + expect(cookies).not.toHaveProperty(expiredCookie.key); + expect(cookies).toMatchObject({ [activeCookie.key]: activeCookie.value }); +}); diff --git a/packages/insomnia-test/tests/cookie/happy-path.spec.ts b/packages/insomnia-test/tests/cookie/happy-path.spec.ts new file mode 100644 index 0000000000..3aa5b2fa91 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/happy-path.spec.ts @@ -0,0 +1,62 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify Linked Cookie Is Sent With HTTP Request", async ({ user }) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const cookieList = await cookieFlow.link(request, [ + { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + }, + { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + }, + { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "example.org", + path: "/", + }, + ]); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + + const { cookies } = responseView.body as { cookies: Record }; + expect(Object.keys(cookies)).toHaveLength(2); + + const expectedCookies = Object.fromEntries( + cookieList + .filter((cookie) => cookie.domain === "localhost") + .map((cookie) => [cookie.key, cookie.value]), + ); + expect(cookies).toMatchObject(expectedCookies); +}); diff --git a/packages/insomnia-test/tests/cookie/host-prefix-cookie-requires-host-only.spec.ts b/packages/insomnia-test/tests/cookie/host-prefix-cookie-requires-host-only.spec.ts new file mode 100644 index 0000000000..793d83e900 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/host-prefix-cookie-requires-host-only.spec.ts @@ -0,0 +1,55 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify __Host- Prefixed Cookie Is Only Sent When Marked HostOnly", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const hostOnlyCookie = { + key: `__Host-${faker.string.alphanumeric(8)}`, + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + secure: true, + hostOnly: true, + }; + const nonHostOnlyCookie = { + key: `__Host-${faker.string.alphanumeric(8)}`, + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + secure: true, + }; + await cookieFlow.link(request, [hostOnlyCookie, nonHostOnlyCookie]); + + const responseView = await httpRequestFlow.send(request); + + const { cookies } = responseView.body as { cookies: Record }; + expect(cookies).toMatchObject({ + [hostOnlyCookie.key]: hostOnlyCookie.value, + }); + expect(cookies).not.toHaveProperty(nonHostOnlyCookie.key); +}); diff --git a/packages/insomnia-test/tests/cookie/http-only-cookie-always-sent.spec.ts b/packages/insomnia-test/tests/cookie/http-only-cookie-always-sent.spec.ts new file mode 100644 index 0000000000..f8f86e2929 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/http-only-cookie-always-sent.spec.ts @@ -0,0 +1,66 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, HTTP_SERVER_HTTPS, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify HttpOnly Cookie Flag Persists And Cookie Is Always Sent Over HTTP And HTTPS", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow, preferencesFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + await preferencesFlow.set({ validateSSL: false }); + + const httpRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/cookies`, + }); + const httpsRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER_HTTPS}/cookies`, + }); + + const httpOnlyCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + httpOnly: true, + }; + const cookieList = await cookieFlow.link(httpRequest, [httpOnlyCookie]); + + const httpResponse = await httpRequestFlow.send(httpRequest); + const httpsResponse = await httpRequestFlow.send(httpsRequest); + + expect(httpResponse.statusCode).toBe(200); + const { cookies: httpCookies } = httpResponse.body as { + cookies: Record; + }; + expect(httpCookies).toMatchObject({ + [httpOnlyCookie.key]: httpOnlyCookie.value, + }); + expect(httpsResponse.statusCode).toBe(200); + const { cookies: httpsCookies } = httpsResponse.body as { + cookies: Record; + }; + expect(httpsCookies).toMatchObject({ + [httpOnlyCookie.key]: httpOnlyCookie.value, + }); + expect( + cookieList.find((cookie) => cookie.key === httpOnlyCookie.key)?.httpOnly, + ).toBe(true); +}); diff --git a/packages/insomnia-test/tests/cookie/raw-cookie-string-editor.spec.ts b/packages/insomnia-test/tests/cookie/raw-cookie-string-editor.spec.ts new file mode 100644 index 0000000000..f4c22cc8e0 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/raw-cookie-string-editor.spec.ts @@ -0,0 +1,52 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/cookies`; + +test("Verify Editing A Cookie Via The Raw Tab Updates Its Fields And Is Sent", async ({ + user, +}) => { + const { cookieFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + const { cookiePage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const key = faker.string.alphanumeric(8); + const oldValue = faker.string.alphanumeric(8); + const newValue = faker.string.alphanumeric(8); + await cookieFlow.link(request, [ + { key, value: oldValue, domain: "example.org", path: "/" }, + ]); + + await cookiePage.open(); + await cookiePage.editCookieRaw(key, `${key}=${newValue}; Domain=localhost; Path=/`); + const cookiesWhileOpen = await cookiePage.getCookies(); + await cookiePage.close(); + + const responseView = await httpRequestFlow.send(request); + const { cookies: sentCookies } = responseView.body as { + cookies: Record; + }; + + expect(cookiesWhileOpen).toMatchObject([ + { key, value: newValue, domain: "localhost", path: "/" }, + ]); + expect(sentCookies).toEqual({ [key]: newValue }); +}); diff --git a/packages/insomnia-test/tests/cookie/secure-cookie-not-sent-over-http.spec.ts b/packages/insomnia-test/tests/cookie/secure-cookie-not-sent-over-http.spec.ts new file mode 100644 index 0000000000..ff2a05e1b5 --- /dev/null +++ b/packages/insomnia-test/tests/cookie/secure-cookie-not-sent-over-http.spec.ts @@ -0,0 +1,62 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, HTTP_SERVER_HTTPS, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Secure Cookie Is Sent Over HTTPS But Not Over Plain HTTP", async ({ + user, +}) => { + test.fail(true, 'INS-3179'); + + const { cookieFlow, httpRequestFlow, workspaceFlow, preferencesFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + await preferencesFlow.set({ validateSSL: false }); + + const httpRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/cookies`, + }); + const httpsRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER_HTTPS}/cookies`, + }); + + const secureCookie = { + key: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + domain: "localhost", + path: "/", + secure: true, + }; + await cookieFlow.link(httpRequest, [secureCookie]); + + const httpsResponse = await httpRequestFlow.send(httpsRequest); + expect(httpsResponse.statusCode).toBe(200); + const { cookies: httpsCookies } = httpsResponse.body as { + cookies: Record; + }; + expect(httpsCookies).toMatchObject({ + [secureCookie.key]: secureCookie.value, + }); + + const httpResponse = await httpRequestFlow.send(httpRequest); + const { cookies: httpCookies } = httpResponse.body as { + cookies: Record; + }; + expect(httpCookies).not.toHaveProperty(secureCookie.key); +}); diff --git a/packages/insomnia-test/tests/environment/case-sensitive-variable-names.spec.ts b/packages/insomnia-test/tests/environment/case-sensitive-variable-names.spec.ts new file mode 100644 index 0000000000..c37a513ae4 --- /dev/null +++ b/packages/insomnia-test/tests/environment/case-sensitive-variable-names.spec.ts @@ -0,0 +1,68 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const lowerCaseVariable = "caseVar"; +const upperCaseVariable = "CaseVar"; + +test("Verify Variable Names Are Case Sensitive Across Environment Levels", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const baseValue = faker.string.alphanumeric(10); + const subValue = faker.string.alphanumeric(10); + + const baseEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: lowerCaseVariable, + value: baseValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + const subEnvironment = await environmentFlow.createSubEnvironment(baseEnvironment, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: upperCaseVariable, + value: subValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, subEnvironment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?lower={{${lowerCaseVariable}}}&upper={{${upperCaseVariable}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + args: { lower: baseValue, upper: subValue }, + }); +}); diff --git a/packages/insomnia-test/tests/environment/circular-variable-reference.spec.ts b/packages/insomnia-test/tests/environment/circular-variable-reference.spec.ts new file mode 100644 index 0000000000..bb8ff202ce --- /dev/null +++ b/packages/insomnia-test/tests/environment/circular-variable-reference.spec.ts @@ -0,0 +1,58 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const variableA = "a"; +const variableB = "b"; + +test("Verify Circular Variable Reference Resolves To Literal Unresolved Text", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: variableA, + value: `{{${variableB}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: variableB, + value: `{{${variableA}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${variableA}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ args: { value: `{{${variableA}}}` } }); +}); diff --git a/packages/insomnia-test/tests/environment/create-and-duplicate-global-environment.spec.ts b/packages/insomnia-test/tests/environment/create-and-duplicate-global-environment.spec.ts new file mode 100644 index 0000000000..a389bf3a05 --- /dev/null +++ b/packages/insomnia-test/tests/environment/create-and-duplicate-global-environment.spec.ts @@ -0,0 +1,60 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify creating and duplicating a global environment", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const variableValue = faker.string.alphanumeric(10); + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: "globalVar", + value: variableValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + + const duplicatedName = faker.string.alphanumeric(10); + const duplicatedEnvironment = await workspaceFlow.duplicateEnvironment( + environment, + duplicatedName, + ); + await environmentFlow.link(collection, { + ...duplicatedEnvironment, + name: "Base Environment", + }); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{ globalVar }}`, + } satisfies HttpRequest); + const responseView = await httpRequestFlow.send(request); + + expect(duplicatedEnvironment.id).not.toBe(environment.id); + expect(duplicatedEnvironment.name).toBe(duplicatedName); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ args: { value: variableValue } }); +}); diff --git a/packages/insomnia-test/tests/environment/disabled-sub-environment-variable-falls-back-to-base.spec.ts b/packages/insomnia-test/tests/environment/disabled-sub-environment-variable-falls-back-to-base.spec.ts new file mode 100644 index 0000000000..ea120b7685 --- /dev/null +++ b/packages/insomnia-test/tests/environment/disabled-sub-environment-variable-falls-back-to-base.spec.ts @@ -0,0 +1,65 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const sharedVariable = "shared"; + +test("Verify a disabled sub-environment variable falls back to the base environment's value", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const baseValue = faker.string.alphanumeric(10); + const subValue = faker.string.alphanumeric(10); + + const base = await environmentFlow.createBaseEnvironment(project, { + name: "Base Environment", + kvPairData: [ + { + id: "", + name: sharedVariable, + value: baseValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + const sub = await environmentFlow.createSubEnvironment(base, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: sharedVariable, + value: subValue, + type: EnvironmentKvPairDataType.STRING, + enabled: false, + }, + ], + }); + await environmentFlow.link(collection, sub); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${sharedVariable}}}`, + } satisfies HttpRequest); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ args: { value: baseValue } }); +}); diff --git a/packages/insomnia-test/tests/environment/disabled-variable-mid-chain.spec.ts b/packages/insomnia-test/tests/environment/disabled-variable-mid-chain.spec.ts new file mode 100644 index 0000000000..fa409a4d97 --- /dev/null +++ b/packages/insomnia-test/tests/environment/disabled-variable-mid-chain.spec.ts @@ -0,0 +1,70 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const leafVariable = "leaf"; +const midVariable = "mid"; +const topVariable = "top"; + +test("Verify Disabled Mid-Chain Variable Leaves Reference Unresolved", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const leafValue = faker.string.alphanumeric(10); + + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: leafVariable, + value: leafValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: midVariable, + value: `{{${leafVariable}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: false, + }, + { + id: "", + name: topVariable, + value: `{{${midVariable}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${topVariable}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + args: { value: `{{${midVariable}}}` }, + }); +}); diff --git a/packages/insomnia-test/tests/environment/json-raw-editor.spec.ts b/packages/insomnia-test/tests/environment/json-raw-editor.spec.ts new file mode 100644 index 0000000000..8b01bd5b4f --- /dev/null +++ b/packages/insomnia-test/tests/environment/json-raw-editor.spec.ts @@ -0,0 +1,48 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ProjectType } from "../../enums/project-types"; +import { test } from "../../misc/fixtures"; +import type { + EnvironmentKvPairData} from "../../models/environment"; +import { + EnvironmentKvPairDataType, +} from "../../models/environment"; +import { Project } from "../../models/project"; + +test("Verify Environment Raw JSON Editor Reads Back And Persists Edits", async ({ + user, +}) => { + const { environmentFlow, workspaceFlow } = user.flowManager; + const { environmentPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const varA = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [varA], + }); + + await environmentPage.selectEnvironment(environment.name); + await environmentPage.toggleRawEdit(); + const rawBefore = await environmentPage.getRawJson(); + + const newKey = faker.string.alphanumeric(8); + const newValue = faker.string.alphanumeric(8); + await environmentPage.setRawJson(JSON.stringify({ [newKey]: newValue })); + + await user.page.reload(); + await environmentPage.selectEnvironment(environment.name); + const rawAfterReload = await environmentPage.getRawJson(); + + expect(JSON.parse(rawBefore)).toEqual({ [varA.name]: varA.value }); + expect(JSON.parse(rawAfterReload)).toEqual({ [newKey]: newValue }); +}); diff --git a/packages/insomnia-test/tests/environment/key-value-editor-blank-row.spec.ts b/packages/insomnia-test/tests/environment/key-value-editor-blank-row.spec.ts new file mode 100644 index 0000000000..02e91246d9 --- /dev/null +++ b/packages/insomnia-test/tests/environment/key-value-editor-blank-row.spec.ts @@ -0,0 +1,56 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ProjectType } from "../../enums/project-types"; +import { test } from "../../misc/fixtures"; +import type { + EnvironmentKvPairData} from "../../models/environment"; +import { + EnvironmentKvPairDataType, +} from "../../models/environment"; +import { Project } from "../../models/project"; + +test("Verify Environment Table Editor's Blank Row Is Not Persisted And Deleting A Row Leaves No Stale Text", async ({ + user, +}) => { + const { environmentFlow, workspaceFlow } = user.flowManager; + const { environmentPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + + const varA = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + const varB = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + const varC = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [varA, varB, varC], + }); + const beforeDelete = await environmentFlow.get(environment.name); + + await environmentPage.deleteRow(environmentPage.getRow(varB.name)); + + const afterDelete = await environmentFlow.get(environment.name); + + expect(beforeDelete?.kvPairData).toEqual([varA, varB, varC]); + expect(afterDelete?.kvPairData).toEqual([varA, varC]); +}); diff --git a/packages/insomnia-test/tests/environment/mixed-variables-in-value.spec.ts b/packages/insomnia-test/tests/environment/mixed-variables-in-value.spec.ts new file mode 100644 index 0000000000..6506e68759 --- /dev/null +++ b/packages/insomnia-test/tests/environment/mixed-variables-in-value.spec.ts @@ -0,0 +1,63 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const firstVariable = "a"; +const secondVariable = "b"; + +test("Verify Multiple Variables Mixed In A Single Value Resolve Together", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const firstValue = faker.string.alphanumeric(10); + const secondValue = faker.string.alphanumeric(10); + + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: firstVariable, + value: firstValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: secondVariable, + value: secondValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${firstVariable}}}-{{${secondVariable}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + args: { value: `${firstValue}-${secondValue}` }, + }); +}); diff --git a/packages/insomnia-test/tests/environment/nested-variable-resolution.spec.ts b/packages/insomnia-test/tests/environment/nested-variable-resolution.spec.ts new file mode 100644 index 0000000000..171781a5d4 --- /dev/null +++ b/packages/insomnia-test/tests/environment/nested-variable-resolution.spec.ts @@ -0,0 +1,67 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const leafVariable = "leaf"; +const midVariable = "mid"; +const topVariable = "top"; + +test("Verify Multi-Level Nested Variable Resolution", async ({ user }) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const finalValue = faker.string.alphanumeric(10); + + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: leafVariable, + value: finalValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: midVariable, + value: `{{${leafVariable}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: topVariable, + value: `{{${midVariable}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${topVariable}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ args: { value: finalValue } }); +}); diff --git a/packages/insomnia-test/tests/environment/private-environment-secret-variable.spec.ts b/packages/insomnia-test/tests/environment/private-environment-secret-variable.spec.ts new file mode 100644 index 0000000000..809df55a92 --- /dev/null +++ b/packages/insomnia-test/tests/environment/private-environment-secret-variable.spec.ts @@ -0,0 +1,91 @@ +import * as crypto from "node:crypto"; + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { + ENVIRONMENT_TYPE, + EnvironmentKvPairDataType, +} from "../../models/environment"; +import { Project } from "../../models/project"; + +const testWithVaultKey = test.extend({ + vaultKey: async ({}, use) => { + const jwk = { + alg: "A256GCM", + ext: true, + k: crypto.randomBytes(32).toString("base64url"), + key_ops: ["encrypt", "decrypt"], + kty: "oct", + }; + await use(Buffer.from(JSON.stringify(jwk), "utf8").toString("base64")); + }, + vaultSalt: async ({}, use) => { + await use(crypto.randomBytes(32).toString("hex")); + }, +}); + +testWithVaultKey( + "Verify Secret-typed environment variables are stored encrypted and rendered decrypted at send-time", + async ({ user }) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const globalEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + type: ENVIRONMENT_TYPE, + }); + const secretValueFoo = faker.string.alphanumeric(10); + const secretValueHello = faker.string.alphanumeric(10); + const privateEnvironment = await environmentFlow.createSubEnvironment(globalEnvironment, { + name: faker.string.alphanumeric(10), + isPrivate: true, + kvPairData: [ + { + id: "", + name: "foo", + value: secretValueFoo, + type: EnvironmentKvPairDataType.SECRET, + enabled: true, + }, + { + id: "", + name: "hello", + value: secretValueHello, + type: EnvironmentKvPairDataType.SECRET, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, privateEnvironment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + body: { + mimeType: ContentType.Plain, + text: "{{ _.vault.foo }}\n{{ _.vault.hello }}", + }, + }); + const responseView = await httpRequestFlow.send(request!); + + expect(responseView.statusCode).toBe(200); + expect((responseView.body as { data: string }).data).toBe( + `${secretValueFoo}\n${secretValueHello}`, + ); + }, +); diff --git a/packages/insomnia-test/tests/environment/self-referencing-variable.spec.ts b/packages/insomnia-test/tests/environment/self-referencing-variable.spec.ts new file mode 100644 index 0000000000..a39e78bf01 --- /dev/null +++ b/packages/insomnia-test/tests/environment/self-referencing-variable.spec.ts @@ -0,0 +1,52 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const selfVariable = "a"; + +test("Verify Self-Referencing Variable Resolves To Literal Unresolved Text", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: selfVariable, + value: `{{${selfVariable}}}`, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${selfVariable}}}`, + } satisfies HttpRequest); + + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + args: { value: `{{${selfVariable}}}` }, + }); +}); diff --git a/packages/insomnia-test/tests/environment/sub-environment-empty-falls-back-to-base.spec.ts b/packages/insomnia-test/tests/environment/sub-environment-empty-falls-back-to-base.spec.ts new file mode 100644 index 0000000000..600054e480 --- /dev/null +++ b/packages/insomnia-test/tests/environment/sub-environment-empty-falls-back-to-base.spec.ts @@ -0,0 +1,73 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const sharedVariable = "shared"; + +test("Verify an empty sub-environment falls back to the base environment until it defines its own value", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + const { environmentPage, workspacePage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const baseValue = faker.string.alphanumeric(10); + const subValue = faker.string.alphanumeric(10); + + const base = await environmentFlow.createBaseEnvironment(project, { + name: "Base Environment", + kvPairData: [ + { + id: "", + name: sharedVariable, + value: baseValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + const sub = await environmentFlow.createSubEnvironment(base, { + name: faker.string.alphanumeric(10), + }); + await environmentFlow.link(collection, sub); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${sharedVariable}}}`, + } satisfies HttpRequest); + const beforeOwnValue = await httpRequestFlow.send(request); + + const baseNode = await workspacePage.findItemNode(base); + await workspacePage.clickNode(baseNode!); + await environmentPage.selectEnvironment(sub.name); + await environmentPage.setVariables([ + { + id: "", + name: sharedVariable, + value: subValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ]); + const afterOwnValue = await httpRequestFlow.send(request); + + expect(beforeOwnValue.statusCode).toBe(200); + expect(beforeOwnValue.body).toMatchObject({ args: { value: baseValue } }); + expect(afterOwnValue.statusCode).toBe(200); + expect(afterOwnValue.body).toMatchObject({ args: { value: subValue } }); +}); diff --git a/packages/insomnia-test/tests/environment/sub-environment-variable-priority.spec.ts b/packages/insomnia-test/tests/environment/sub-environment-variable-priority.spec.ts new file mode 100644 index 0000000000..d03f6f7ca6 --- /dev/null +++ b/packages/insomnia-test/tests/environment/sub-environment-variable-priority.spec.ts @@ -0,0 +1,64 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const sharedVariable = "shared"; + +test("Verify Sub Environment Variable Overrides Base Environment", async ({ + user, +}) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const baseValue = faker.string.alphanumeric(10); + const subValue = faker.string.alphanumeric(10); + + const baseEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: sharedVariable, + value: baseValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + const subEnvironment = await environmentFlow.createSubEnvironment(baseEnvironment, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: sharedVariable, + value: subValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, subEnvironment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?value={{${sharedVariable}}}`, + } satisfies HttpRequest); + const responseView = await httpRequestFlow.send(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ args: { value: subValue } }); +}); diff --git a/packages/insomnia-test/tests/environment/table-editor-disable-row.spec.ts b/packages/insomnia-test/tests/environment/table-editor-disable-row.spec.ts new file mode 100644 index 0000000000..ba6d1e300b --- /dev/null +++ b/packages/insomnia-test/tests/environment/table-editor-disable-row.spec.ts @@ -0,0 +1,48 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ProjectType } from "../../enums/project-types"; +import { test } from "../../misc/fixtures"; +import type { + EnvironmentKvPairData} from "../../models/environment"; +import { + EnvironmentKvPairDataType, +} from "../../models/environment"; +import { Project } from "../../models/project"; + +test("Verify Disabling A Row In The Table Editor Is Reflected On Read-Back", async ({ + user, +}) => { + const { environmentFlow, workspaceFlow } = user.flowManager; + const { environmentPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const varA = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + const varB = { + id: "", + name: faker.string.alphanumeric(8), + value: faker.string.alphanumeric(8), + type: EnvironmentKvPairDataType.STRING, + enabled: true, + } satisfies EnvironmentKvPairData; + await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [varA, varB], + }); + + await environmentPage.disableRow(environmentPage.getRow(varA.name)); + const variables = await environmentPage.getVariables(); + + expect(variables).toEqual([ + { ...varA, enabled: false }, + { ...varB, enabled: true }, + ]); +}); diff --git a/packages/insomnia-test/tests/environment/unlinked-environment-variable.spec.ts b/packages/insomnia-test/tests/environment/unlinked-environment-variable.spec.ts new file mode 100644 index 0000000000..1337423400 --- /dev/null +++ b/packages/insomnia-test/tests/environment/unlinked-environment-variable.spec.ts @@ -0,0 +1,45 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { MOCK_API_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import { Project } from "../../models/project"; + +const variableName = "unlinked"; + +test("Verify Unlinked Environment Variable Causes Error", async ({ user }) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: variableName, + value: "variable", + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${MOCK_API_SERVER}/{{${variableName}}}`, + }); + await expect(httpRequestFlow.send(request)).rejects.toThrow( + /environment variable is missing.*have not been assigned.*unlinked/s, + ); +}); diff --git a/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-format-environment-variables.spec.ts b/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-format-environment-variables.spec.ts new file mode 100644 index 0000000000..1a1816e79c --- /dev/null +++ b/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-format-environment-variables.spec.ts @@ -0,0 +1,87 @@ +import * as crypto from "node:crypto"; +import path from "node:path"; + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ProjectType } from "../../../enums/project-types"; +import { test } from "../../../misc/fixtures"; +import { Collection } from "../../../models/collection"; +import { + ENVIRONMENT_TYPE, + EnvironmentKvPairDataType, +} from "../../../models/environment"; +import { Project } from "../../../models/project"; + +const testWithVaultKey = test.extend({ + vaultKey: async ({}, use) => { + const jwk = { + alg: "A256GCM", + ext: true, + k: crypto.randomBytes(32).toString("base64url"), + key_ops: ["encrypt", "decrypt"], + kty: "oct", + }; + await use(Buffer.from(JSON.stringify(jwk), "utf8").toString("base64")); + }, + vaultSalt: async ({}, use) => { + await use(crypto.randomBytes(32).toString("hex")); + }, +}); + +testWithVaultKey( + "Verify legacy array/object vault environment variables render correctly and a scalar vault value throws the reserved-key error", + async ({ user }) => { + const { environmentFlow, httpRequestFlow, importFlow, workspaceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const globalEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + type: ENVIRONMENT_TYPE, + }); + const secretEnvironment = await environmentFlow.createSubEnvironment(globalEnvironment, { + name: faker.string.alphanumeric(10), + isPrivate: true, + kvPairData: [ + { + id: "", + name: "secretKey", + value: faker.string.alphanumeric(10), + type: EnvironmentKvPairDataType.SECRET, + enabled: true, + }, + ], + }); + + await importFlow.importFile( + project, + path.join(__dirname, "vault-legacy-formats.yaml"), + ); + const collection = new Collection("Vault Legacy Format Collection"); + await environmentFlow.link(collection, secretEnvironment); + + await environmentFlow.activate(collection, "legacy vault value array"); + const arrayRequest = await httpRequestFlow.get("legacy-array-vault"); + const arrayResponse = await httpRequestFlow.send(arrayRequest!); + + await environmentFlow.activate(collection, "legacy vault value object"); + const objectRequest = await httpRequestFlow.get("legacy-object-vault"); + const objectResponse = await httpRequestFlow.send(objectRequest!); + + await environmentFlow.activate(collection, "base with vault"); + const invalidRequest = await httpRequestFlow.get("legacy-invalid-vault"); + + expect(arrayResponse.statusCode).toBe(200); + expect((arrayResponse.body as { data: string }).data).toBe( + "vault_array_a\nvault_array_b", + ); + expect(objectResponse.statusCode).toBe(200); + expect((objectResponse.body as { data: string }).data).toBe("secv1\nsecv2"); + await expect(httpRequestFlow.send(invalidRequest!)).rejects.toThrow( + /vault is a reserved key for insomnia vault/, + ); + }, +); diff --git a/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-formats.yaml b/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-formats.yaml new file mode 100644 index 0000000000..46b3b6a761 --- /dev/null +++ b/packages/insomnia-test/tests/environment/vault-legacy-format-environment-variables/vault-legacy-formats.yaml @@ -0,0 +1,173 @@ +_type: export +__export_format: 4 +__export_date: 2025-01-24T07:58:39.196Z +__export_source: insomnia.desktop.app:v10.3.1-beta.0 +resources: + - _id: wrk_37e3ee9c2ce7468493378fa5e525b4b1 + parentId: null + modified: 1737705410863 + created: 1737705410863 + name: Vault Legacy Format Collection + description: "" + scope: collection + _type: workspace + - _id: req_1c9ef3a2afae47d1bd960ae23de09750 + parentId: wrk_37e3ee9c2ce7468493378fa5e525b4b1 + modified: 1737617845875 + created: 1734948333857 + url: localhost:4060/post + name: legacy-array-vault + description: "" + method: POST + body: + mimeType: text/plain + text: |- + {{ _.vault[0] }} + {{ _.vault[1] }} + headers: + - name: Content-Type + value: text/plain + authentication: {} + metaSortKey: -1734948333857 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_fb7fb8d4345f471fb53d03b069737a07 + parentId: wrk_37e3ee9c2ce7468493378fa5e525b4b1 + modified: 1737617874271 + created: 1737617856040 + url: localhost:4060/post + name: legacy-object-vault + description: "" + method: POST + body: + mimeType: text/plain + text: |- + {{ _.vault.v1 }} + {{ _.vault.v2 }} + headers: + - name: Content-Type + value: text/plain + authentication: {} + metaSortKey: -1729018003774 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: req_2a150b45acb242cfa2db04d929a11085 + parentId: wrk_37e3ee9c2ce7468493378fa5e525b4b1 + modified: 1737617920254 + created: 1737617896390 + url: localhost:4060/post + name: legacy-invalid-vault + description: "" + method: POST + body: + mimeType: text/plain + text: "" + headers: + - name: Content-Type + value: text/plain + authentication: {} + metaSortKey: -1731983168815.5 + isPrivate: false + pathParameters: [] + settingStoreCookies: true + settingSendCookies: true + settingDisableRenderRequestBody: false + settingEncodeUrl: true + settingRebuildPath: true + settingFollowRedirects: global + _type: request + - _id: env_40681941450149a4a41f2dcbc22ec616 + parentId: wrk_37e3ee9c2ce7468493378fa5e525b4b1 + modified: 1737618683836 + created: 1732861968102 + name: Base Environment1 + data: + a_hello: test + dataPropertyOrder: + "&": + - a_hello + color: null + isPrivate: false + metaSortKey: 1732861968102 + environmentType: kv + kvPairData: + - id: envPair_62a7e9c0cdb949e0abf15e0fe4b1e8d3 + name: a_hello + value: test + type: str + enabled: true + _type: environment + - _id: env_7ace551350dc40cbbb8302234ec0af0a + parentId: env_40681941450149a4a41f2dcbc22ec616 + modified: 1737618683836 + created: 1736745679348 + name: base with vault + data: + vault: hello + dataPropertyOrder: + "&": + - vault + color: null + isPrivate: false + metaSortKey: 1736745679348 + environmentType: kv + kvPairData: + - id: envPair_4bcd80eeb9c544c8a5cca0a1a6a85ffc + name: vault + value: hello + type: str + enabled: true + _type: environment + - _id: env_fce7e052bb99440a9019a162afea7f85 + parentId: env_40681941450149a4a41f2dcbc22ec616 + modified: 1737617847199 + created: 1736745692249 + name: legacy vault value array + data: + vault: + - vault_array_a + - vault_array_b + dataPropertyOrder: + "&": + - vault + color: null + isPrivate: false + metaSortKey: 1736745679398 + environmentType: json + kvPairData: [] + _type: environment + - _id: env_9214888ae2dc497e87a7f8f8edaf19f2 + parentId: env_40681941450149a4a41f2dcbc22ec616 + modified: 1737617875350 + created: 1737617352406 + name: legacy vault value object + data: + vault: + v1: secv1 + v2: secv2 + dataPropertyOrder: + "&": + - vault + "&~|vault": + - v1 + - v2 + color: null + isPrivate: false + metaSortKey: 1736881902959 + environmentType: json + kvPairData: [] + _type: environment diff --git a/packages/insomnia-test/tests/http-request/after-response-script-test-results.spec.ts b/packages/insomnia-test/tests/http-request/after-response-script-test-results.spec.ts new file mode 100644 index 0000000000..202cd02eb2 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/after-response-script-test-results.spec.ts @@ -0,0 +1,76 @@ +import { faker } from '@faker-js/faker'; +import { expect } from "@playwright/test"; + +import { ContentType } from '../../enums/content-type'; +import { HttpMethod } from '../../enums/http-method'; +import { ProjectType } from '../../enums/project-types'; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from '../../misc/fixtures'; +import { Collection } from '../../models/collection'; +import { Project } from '../../models/project'; + +const url = `${HTTP_SERVER}/post`; + +test('Verify insomnia.test/insomnia.expect report PASS/FAIL results, including a transient variable and insomnia.response accessors', async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const transientValue = faker.string.alphanumeric(10); + const sentValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.JSON, text: `{"sent": "${sentValue}"}` }, + afterResponseScript: ` + insomnia.variables.set('transientVar', '${transientValue}'); + insomnia.test('transient var', () => { + insomnia.expect(insomnia.variables.get('transientVar')).to.equal('${transientValue}'); + }); + insomnia.test('happyTestInFunc', () => { + insomnia.expect(200).to.equal(200); + }); + insomnia.test('response accessors', () => { + insomnia.expect(insomnia.response.code).to.equal(200); + insomnia.expect(insomnia.response.status).to.equal('OK'); + insomnia.expect(insomnia.response.json().json.sent).to.equal('${sentValue}'); + insomnia.expect(insomnia.response.text()).to.include('${sentValue}'); + insomnia.expect(insomnia.response.headers.get('Content-Type')).to.include('application/json'); + insomnia.expect(insomnia.response.responseTime).to.be.a('number'); + insomnia.expect(insomnia.response.size().body).to.be.above(0); + }); + insomnia.test('response assertions', () => { + insomnia.response.to.have.status(200); + insomnia.response.to.have.header('Content-Type'); + insomnia.response.to.have.jsonBody('data', '{"sent": "${sentValue}"}'); + }); + insomnia.test('unhappy tests', () => { + insomnia.expect(199).to.equal(200); + }); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + await expect + .poll(() => responseView.tests?.(), { timeout: DEFAULT_TIMEOUT }) + .toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: 'transient var', status: 'PASS' }), + expect.objectContaining({ name: 'happyTestInFunc', status: 'PASS' }), + expect.objectContaining({ name: 'response accessors', status: 'PASS' }), + expect.objectContaining({ + name: 'response assertions', + status: 'PASS', + }), + expect.objectContaining({ + name: 'unhappy tests', + status: 'FAIL', + error: expect.stringContaining('AssertionError: expected 199 to equal 200'), + }), + ]), + ); +}); diff --git a/packages/insomnia-test/tests/http-request/body-types.spec.ts b/packages/insomnia-test/tests/http-request/body-types.spec.ts new file mode 100644 index 0000000000..e82a76fc0c --- /dev/null +++ b/packages/insomnia-test/tests/http-request/body-types.spec.ts @@ -0,0 +1,226 @@ +import path from "node:path"; + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; +const uploadFilePath = path.join( + __dirname, + "..", + "..", + "misc", + "fixtures", + "upload-sample.txt", +); + +test("Send HTTP Request with x-www-form-urlencoded Body", async ({ user }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const title = faker.string.alphanumeric(8); + const userId = faker.string.numeric(3); + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.Form, + params: [ + { name: "title", value: title }, + { name: "userId", value: userId }, + { name: "excluded", value: "should-not-be-sent", disabled: true }, + ], + }, + } satisfies HttpRequest; + + const persisted = await httpRequestFlow.create(collection, request); + const responseView = await httpRequestFlow.send(request); + const raw = (responseView.body as { data: string }).data; + + expect(persisted.body?.mimeType).toBe(ContentType.Form); + expect(persisted.body?.params).toMatchObject([ + { name: "title", value: title }, + { name: "userId", value: userId }, + { name: "excluded", disabled: true }, + ]); + expect(responseView.statusCode).toBe(200); + expect(Object.fromEntries(new URLSearchParams(raw))).toEqual({ + title, + userId, + }); + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + "content-type": expect.stringContaining(ContentType.Form), + }), + }); +}); + +test("Send HTTP Request with JSON Body", async ({ user }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const payload = { title: faker.string.alphanumeric(8), userId: 1 }; + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.JSON, text: JSON.stringify(payload) }, + } satisfies HttpRequest; + + await httpRequestFlow.create(collection, request); + const persisted = await httpRequestFlow.get(request.name); + const responseView = await httpRequestFlow.send(request); + + expect(persisted).toMatchObject(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + json: payload, + headers: expect.objectContaining({ + "content-type": expect.stringContaining(ContentType.JSON), + }), + }); +}); + +test("Send HTTP Request with XML Body", async ({ user }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const title = faker.string.alphanumeric(8); + const xml = `${title}`; + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.XML, text: xml }, + } satisfies HttpRequest; + + await httpRequestFlow.create(collection, request); + const persisted = await httpRequestFlow.get(request.name); + const responseView = await httpRequestFlow.send(request); + + expect(persisted).toMatchObject(request); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + data: xml, + headers: expect.objectContaining({ + "content-type": expect.stringContaining(ContentType.XML), + }), + }); +}); + +test("Send HTTP Request with No Body explicitly selected", async ({ user }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.NoBody }, + } satisfies HttpRequest; + + await httpRequestFlow.create(collection, request); + const persisted = await httpRequestFlow.get(request.name); + const responseView = await httpRequestFlow.send(request); + + expect(persisted).toMatchObject({ + method: request.method, + url: request.url, + body: undefined, + }); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + data: "", + json: null, + }); +}); + +test("Send HTTP Request with multipart/form-data Body including a file field", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, preferencesFlow } = user.flowManager; + + // Insomnia only reads files from allow-listed directories (Preferences -> General -> Security). + await preferencesFlow.set({ dataFolders: [path.dirname(uploadFilePath)] }); + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const title = faker.string.alphanumeric(8); + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.Multipart, + params: [ + { name: "title", value: title }, + { name: "upload", fileName: uploadFilePath }, + ], + }, + } satisfies HttpRequest; + + await httpRequestFlow.create(collection, request); + const persisted = await httpRequestFlow.get(request.name); + const responseView = await httpRequestFlow.send(request); + const raw = (responseView.body as { data: string }).data; + + expect(persisted?.body?.mimeType).toBe(ContentType.Multipart); + expect(persisted?.body?.params?.[0]).toMatchObject({ + name: "title", + value: title, + }); + expect(persisted?.body?.params?.[1]?.name).toBe("upload"); + expect(persisted?.body?.params?.[1]?.fileName).toContain("upload-sample.txt"); + expect(responseView.statusCode).toBe(200); + expect(raw).toContain('name="title"'); + expect(raw).toContain(title); + expect(raw).toContain('name="upload"; filename="upload-sample.txt"'); + expect(raw).toContain("insomnia-e2e-upload-fixture"); + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + "content-type": expect.stringContaining(ContentType.Multipart), + }), + }); +}); diff --git a/packages/insomnia-test/tests/http-request/cancel-in-flight-request.spec.ts b/packages/insomnia-test/tests/http-request/cancel-in-flight-request.spec.ts new file mode 100644 index 0000000000..a5cad22158 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/cancel-in-flight-request.spec.ts @@ -0,0 +1,56 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { RequestState } from "../../enums/request-state"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Cancelling an In-Flight HTTP Request Shows a Cancellation Message and a Later Request Still Succeeds", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + const { httpRequestPage, responsePage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const slowRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + headers: [{ name: "x-reply-delay-ms", value: "10000" }], + }); + const laterRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + }); + + await httpRequestFlow.get(slowRequest.name); + await httpRequestPage.send(); + await expect + .poll(() => responsePage.getRequestState(), { timeout: DEFAULT_TIMEOUT }) + .toBe(RequestState.Sending); + await responsePage.cancelRequest(); + await expect + .poll(() => responsePage.hasMessage("Request was cancelled"), { + timeout: DEFAULT_TIMEOUT, + }) + .toBe(true); + const requestStateAfterCancel = await responsePage.getRequestState(); + const cancelledResponse = await responsePage.get(); + + const laterResponse = await httpRequestFlow.send(laterRequest); + + expect(requestStateAfterCancel).toBe(RequestState.Idle); + expect(cancelledResponse.statusCode).toBeUndefined(); + expect(laterResponse.statusCode).toBe(200); +}); diff --git a/packages/insomnia-test/tests/http-request/chained-response-tag.spec.ts b/packages/insomnia-test/tests/http-request/chained-response-tag.spec.ts new file mode 100644 index 0000000000..c114aa83a9 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/chained-response-tag.spec.ts @@ -0,0 +1,56 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; +const bodyJsonPathFilter = "b64::JC5kYXRh::46b"; + +test("Verify A Response Tag Chain Resolves Through Multiple Requests", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const first = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.Plain, text: "first" }, + }); + const second = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.Plain, + text: `{% response 'body', '${first.id}', '${bodyJsonPathFilter}', 'always', 60 %} and second`, + }, + }); + const third = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.Plain, + text: `{% response 'body', '${second.id}', '${bodyJsonPathFilter}', 'always', 60 %} and third`, + }, + }); + + const responseView = await httpRequestFlow.send(third); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ data: "first and second and third" }); +}); diff --git a/packages/insomnia-test/tests/http-request/custom-ca-root-certificate.spec.ts b/packages/insomnia-test/tests/http-request/custom-ca-root-certificate.spec.ts new file mode 100644 index 0000000000..a2fbf13ee4 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/custom-ca-root-certificate.spec.ts @@ -0,0 +1,53 @@ + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { certPath } from "../../misc/certs"; +import { DEFAULT_TIMEOUT, HTTP_SERVER_CUSTOM_CA_HTTPS, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER_CUSTOM_CA_HTTPS}/post`; + +test("Verify Custom CA Root Certificate Establishes Trust For HTTPS Request", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, certificatesFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const requestBeforeCa = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + }); + const responseBeforeCa = await httpRequestFlow.send(requestBeforeCa); + expect(responseBeforeCa.statusCode).toBeUndefined(); + await expect + .poll(() => responseBeforeCa.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toMatch(/unable to get local issuer certificate/i); + + await certificatesFlow.setCaCertificate( + collection, + certPath("mtls-ca.pem"), + ); + + const requestAfterCa = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + }); + const responseAfterCa = await httpRequestFlow.send(requestAfterCa); + expect(responseAfterCa.statusCode).toBe(200); + expect(responseAfterCa.body).toMatchObject({ + headers: expect.objectContaining({ host: "localhost:4062" }), + }); +}); diff --git a/packages/insomnia-test/tests/http-request/folder-level-replace-in-utility.spec.ts b/packages/insomnia-test/tests/http-request/folder-level-replace-in-utility.spec.ts new file mode 100644 index 0000000000..cc024ea2df --- /dev/null +++ b/packages/insomnia-test/tests/http-request/folder-level-replace-in-utility.spec.ts @@ -0,0 +1,53 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Folder } from "../../models/folder"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify a folder-level script wrapping insomnia.variables.replaceIn resolves synchronously for a downstream request consuming it without await", async ({ + user, +}) => { + const { folderFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const folder = await folderFlow.create( + collection, + new Folder(faker.string.alphanumeric(10)), + ); + + await folderFlow.setScripts(folder, { + preRequest: ` + insomnia.globals.set('firstname', insomnia.variables.replaceIn('{{ $randomFirstName }}')); + `, + }); + + const request = await httpRequestFlow.create(folder, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + const firstname = insomnia.globals.get('firstname'); + console.log(typeof firstname, firstname); + `, + }); + const responseView = await httpRequestFlow.send(request); + + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toMatch(/log: string \S+/); + const consoleText = await responseView.console?.(); + expect(consoleText).not.toContain("Promise"); + expect(consoleText).not.toContain("undefined"); +}); diff --git a/packages/insomnia-test/tests/http-request/generate-code-target-client-switch.spec.ts b/packages/insomnia-test/tests/http-request/generate-code-target-client-switch.spec.ts new file mode 100644 index 0000000000..55bd3645df --- /dev/null +++ b/packages/insomnia-test/tests/http-request/generate-code-target-client-switch.spec.ts @@ -0,0 +1,50 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/get`; + +test("Verify switching the Generate Code dialog's target language and client library regenerates the snippet", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const shellCurlCode = await workspaceFlow.generateCode(request); + const pythonCode = await workspaceFlow.generateCode(request, { + target: "Python", + client: "http.client", + }); + const nodeAxiosCode = await workspaceFlow.generateCode(request, { + target: "Node.js", + client: "Axios", + }); + const nodeFetchCode = await workspaceFlow.generateCode(request, { + target: "Node.js", + client: "Fetch", + }); + + expect(shellCurlCode).toContain("curl"); + expect(pythonCode).toContain("import http.client"); + expect(nodeAxiosCode).toContain("require('axios')"); + expect(nodeAxiosCode).toContain("axios.request"); + expect(nodeFetchCode).toContain("require('node-fetch')"); + expect(nodeFetchCode).not.toContain("axios"); +}); diff --git a/packages/insomnia-test/tests/http-request/generate-code.spec.ts b/packages/insomnia-test/tests/http-request/generate-code.spec.ts new file mode 100644 index 0000000000..305ee77cc5 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/generate-code.spec.ts @@ -0,0 +1,35 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify Generate Code Produces A Matching cURL Snippet", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url, + }); + + const code = await workspaceFlow.generateCode(request); + + expect(code).toContain("curl"); + expect(code).toContain("--request GET"); + expect(code).toContain(url); +}); diff --git a/packages/insomnia-test/tests/http-request/import-params-from-url-http.spec.ts b/packages/insomnia-test/tests/http-request/import-params-from-url-http.spec.ts new file mode 100644 index 0000000000..af3f759142 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/import-params-from-url-http.spec.ts @@ -0,0 +1,52 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Import from URL Moves Query String Into Params and Preserves It on Send", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + const { httpRequestPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const paramName1 = faker.string.alphanumeric(8); + const paramValue1 = faker.string.alphanumeric(8); + const paramName2 = faker.string.alphanumeric(8); + const paramValue2 = faker.string.alphanumeric(8); + const baseUrl = `${HTTP_SERVER}/get`; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${baseUrl}?${paramName1}=${paramValue1}&${paramName2}=${paramValue2}`, + }); + await httpRequestPage.importParamsFromUrl(); + const updatedUrl = await httpRequestPage.getUrl(); + const updatedParams = await httpRequestPage.getParams(); + const responseView = await httpRequestFlow.send(request!); + + expect(updatedUrl).toBe(baseUrl); + expect(updatedParams).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: paramName1, value: paramValue1 }), + expect.objectContaining({ name: paramName2, value: paramValue2 }), + ]), + ); + expect(responseView.statusCode).toBe(200); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain( + `Preparing request to ${baseUrl}?${paramName1}=${paramValue1}&${paramName2}=${paramValue2}`, + ); +}); diff --git a/packages/insomnia-test/tests/http-request/ipv6-local-address.spec.ts b/packages/insomnia-test/tests/http-request/ipv6-local-address.spec.ts new file mode 100644 index 0000000000..c70e0d5a28 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/ipv6-local-address.spec.ts @@ -0,0 +1,38 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = "http://[::1]:4060/post"; + +test("Verify HTTP request succeeds against a local IPv6 address", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const bodyText = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: url, + body: { mimeType: ContentType.Plain, text: bodyText }, + }); + const responseView = await httpRequestFlow.send(request!); + + expect(responseView.statusCode).toBe(200); + expect(responseView.statusMessage).toBe("OK"); + expect(responseView.body).toMatchObject({ data: bodyText }); +}); diff --git a/packages/insomnia-test/tests/http-request/key-value-editor-blank-row.spec.ts b/packages/insomnia-test/tests/http-request/key-value-editor-blank-row.spec.ts new file mode 100644 index 0000000000..f25555a094 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/key-value-editor-blank-row.spec.ts @@ -0,0 +1,61 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Trailing Blank Row In Headers And Params Is Not Persisted Until Typed Into", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + const { httpRequestPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/get`, + }); + + const headersBefore = await httpRequestPage.getHeaders(); + const paramsBefore = await httpRequestPage.getParams(); + const headerName = faker.string.alphanumeric(8); + const headerValue = faker.string.alphanumeric(8); + const paramName = faker.string.alphanumeric(8); + const paramValue = faker.string.alphanumeric(8); + await httpRequestPage.setHeaders([{ name: headerName, value: headerValue }]); + await httpRequestPage.setParams([{ name: paramName, value: paramValue }]); + const headersAfter = await httpRequestPage.getHeaders(); + const paramsAfter = await httpRequestPage.getParams(); + + const isBlank = (pair: { name: string; value: string }) => + pair.name === "" && pair.value === ""; + + expect(request).toBeDefined(); + expect(headersBefore.some(isBlank)).toBe(false); + expect(paramsBefore.some(isBlank)).toBe(false); + expect(headersAfter.some(isBlank)).toBe(false); + expect(paramsAfter.some(isBlank)).toBe(false); + expect(headersAfter).toHaveLength(headersBefore.length + 1); + expect(paramsAfter).toHaveLength(paramsBefore.length + 1); + expect(headersAfter).toEqual( + expect.arrayContaining([ + { name: headerName, value: headerValue, disabled: false }, + ]), + ); + expect(paramsAfter).toEqual( + expect.arrayContaining([ + { name: paramName, value: paramValue, disabled: false }, + ]), + ); +}); diff --git a/packages/insomnia-test/tests/http-request/liquid-template-isolation.spec.ts b/packages/insomnia-test/tests/http-request/liquid-template-isolation.spec.ts new file mode 100644 index 0000000000..065db97a88 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/liquid-template-isolation.spec.ts @@ -0,0 +1,106 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify LiquidJS control flow tags render correctly", async ({ user }) => { + const { environmentFlow, httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const envValue = faker.string.alphanumeric(8); + const environment = await environmentFlow.createBaseEnvironment(project, { + name: "Base Environment", + kvPairData: [ + { + id: "", + name: "envVar", + value: envValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.Plain, + text: `{{ envVar }}-{% assign x = 5 %}{% if x > 3 %}big{% else %}small{% endif %}-{% for i in (1..3) %}{{ i }}{% endfor %}-{% unless x == 5 %}no{% else %}yes{% endunless %}`, + }, + } satisfies HttpRequest); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + data: `${envValue}-big-123-yes`, + }); +}); + +test("Verify LiquidJS include/render/layout tags are blocked", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const includeRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.Plain, text: `{% include 'foo' %}` }, + } satisfies HttpRequest); + const renderRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.Plain, text: `{% render 'foo' %}` }, + } satisfies HttpRequest); + const layoutRequest = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { mimeType: ContentType.Plain, text: `{% layout 'foo' %}` }, + } satisfies HttpRequest); + + const blockedTagPattern = /include.*render.*layout.*disabled/is; + + await expect(httpRequestFlow.send(includeRequest)).rejects.toThrow( + blockedTagPattern, + ); + await workspaceFlow.closeDialog(); + + await expect(httpRequestFlow.send(renderRequest)).rejects.toThrow( + blockedTagPattern, + ); + await workspaceFlow.closeDialog(); + + await expect(httpRequestFlow.send(layoutRequest)).rejects.toThrow( + blockedTagPattern, + ); + await workspaceFlow.closeDialog(); +}); diff --git a/packages/insomnia-test/tests/http-request/mtls-client-certificate.spec.ts b/packages/insomnia-test/tests/http-request/mtls-client-certificate.spec.ts new file mode 100644 index 0000000000..0a73dda247 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/mtls-client-certificate.spec.ts @@ -0,0 +1,78 @@ +import path from "node:path"; + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { certPath } from "../../misc/certs"; +import { DEFAULT_TIMEOUT, HTTP_SERVER_MTLS, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER_MTLS}/post`; + +test("Verify Client Certificate Toggle Controls mTLS Enforcement For HTTP Request", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, preferencesFlow, certificatesFlow } = + user.flowManager; + + await preferencesFlow.set({ + validateSSL: false, + dataFolders: [path.join(__dirname, "../../misc/fixtures")], + }); + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const requestBeforeCert = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + }); + const responseBeforeCert = await httpRequestFlow.send(requestBeforeCert); + expect(responseBeforeCert.statusCode).toBeUndefined(); + await expect + .poll(() => responseBeforeCert.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toMatch(/certificate required/i); + + await certificatesFlow.addClientCertificate(collection, { + host: "localhost", + cert: certPath("mtls-client-cert.pem"), + key: certPath("mtls-client-key.pem"), + }); + + const requestWithCert = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + }); + const responseWithCert = await httpRequestFlow.send(requestWithCert); + expect(responseWithCert.statusCode).toBe(200); + expect(responseWithCert.body).toMatchObject({ + headers: expect.objectContaining({ host: "localhost:4063" }), + }); + + await certificatesFlow.setClientCertificateEnabled( + collection, + "localhost", + false, + ); + + const requestAfterDisable = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + }); + const responseAfterDisable = await httpRequestFlow.send(requestAfterDisable); + expect(responseAfterDisable.statusCode).toBeUndefined(); + await expect + .poll(() => responseAfterDisable.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toMatch(/certificate required/i); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth1-authentication.spec.ts b/packages/insomnia-test/tests/http-request/oauth1-authentication.spec.ts new file mode 100644 index 0000000000..656de48d18 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth1-authentication.spec.ts @@ -0,0 +1,56 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth1 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify OAuth 1.0 auth fields persist and sign the sent request", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth1 = { + type: "oauth1", + consumerKey: faker.string.alphanumeric(10), + consumerSecret: faker.string.alphanumeric(10), + tokenKey: faker.string.alphanumeric(10), + tokenSecret: faker.string.alphanumeric(10), + signatureMethod: "HMAC-SHA256", + callback: "https://example.com/callback", + version: "1.0", + realm: faker.string.alphanumeric(8), + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + authentication: authentication, + }); + + const responseView = await httpRequestFlow.send(request); + const authHeader = (responseView.body as { headers: Record }) + .headers.authorization; + + expect(responseView.statusCode).toBe(200); + expect(authHeader).toContain("OAuth "); + expect(authHeader).toContain( + `oauth_consumer_key="${authentication.consumerKey}"`, + ); + expect(authHeader).toContain(`oauth_token="${authentication.tokenKey}"`); + expect(authHeader).toContain( + `oauth_signature_method="${authentication.signatureMethod}"`, + ); + expect(authHeader).toContain(`oauth_version="${authentication.version}"`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-authorization-code-pkce.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-authorization-code-pkce.spec.ts new file mode 100644 index 0000000000..087cb9ddbc --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-authorization-code-pkce.spec.ts @@ -0,0 +1,62 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +for (const pkceMethod of ["S256", "plain"] as const) { + test(`Verify Authorization Code grant with PKCE (${pkceMethod}) is verified server-side and authorizes the sent request`, async ({ + user, + }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "authorization_code", + authorizationUrl: `${OAUTH2_SERVER}/authorize`, + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + redirectUrl: `${OAUTH2_SERVER}/health`, + usePkce: true, + pkceMethod, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + + const lastTokenRequest = await ( + await fetch( + `${OAUTH2_SERVER}/_debug/last-token-request?access_token=${encodeURIComponent(tokens.accessToken)}`, + ) + ).json(); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(tokens.accessToken).not.toBe(""); + expect(lastTokenRequest.grantType).toBe("authorization_code"); + expect(lastTokenRequest.pkceUsed).toBe(true); + expect(lastTokenRequest.pkceMethod).toBe(pkceMethod); + expect(lastTokenRequest.pkceVerified).toBe(true); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${tokens.accessToken}`); + }); +} diff --git a/packages/insomnia-test/tests/http-request/oauth2-authorization-code.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-authorization-code.spec.ts new file mode 100644 index 0000000000..8542357895 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-authorization-code.spec.ts @@ -0,0 +1,58 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify Authorization Code grant fetches tokens via a popup and authorizes the sent request", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "authorization_code", + authorizationUrl: `${OAUTH2_SERVER}/authorize`, + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + redirectUrl: `${OAUTH2_SERVER}/health`, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + + const lastTokenRequest = await ( + await fetch( + `${OAUTH2_SERVER}/_debug/last-token-request?access_token=${encodeURIComponent(tokens.accessToken)}`, + ) + ).json(); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(tokens.accessToken).not.toBe(""); + expect(tokens.refreshToken).not.toBe(""); + expect(tokens.identityToken).not.toBe(""); + expect(lastTokenRequest.grantType).toBe("authorization_code"); + expect(lastTokenRequest.pkceUsed).toBe(false); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${tokens.accessToken}`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-client-credentials.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-client-credentials.spec.ts new file mode 100644 index 0000000000..0dfd3b0555 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-client-credentials.spec.ts @@ -0,0 +1,57 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify Client Credentials grant fetches tokens with no popup and authorizes the sent request", async ({ + user, + insomnia, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "client_credentials", + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const windowsBeforeFetch = insomnia.windows().length; + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + const windowsAfterFetch = insomnia.windows().length; + + const lastTokenRequest = await ( + await fetch( + `${OAUTH2_SERVER}/_debug/last-token-request?access_token=${encodeURIComponent(tokens.accessToken)}`, + ) + ).json(); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(tokens.accessToken).not.toBe(""); + expect(windowsAfterFetch).toBe(windowsBeforeFetch); + expect(lastTokenRequest.grantType).toBe("client_credentials"); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${tokens.accessToken}`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-folder-inherited-auth.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-folder-inherited-auth.spec.ts new file mode 100644 index 0000000000..c7d41dea1b --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-folder-inherited-auth.spec.ts @@ -0,0 +1,53 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { AuthType } from "../../enums/auth-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Folder } from "../../models/folder"; +import { Project } from "../../models/project"; + +test("Verify a request set to Inherit from parent picks up its folder's OAuth 2.0 authentication", async ({ + user, +}) => { + const { workspaceFlow, folderFlow, httpRequestFlow } = user.flowManager; + const { httpRequestPage, folderPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const folder = await folderFlow.create( + collection, + new Folder(faker.string.alphanumeric(10)), + ); + + await folderFlow.open(folder); + await folderPage.auth.setOAuth2Fields({ + type: "oauth2", + grantType: "client_credentials", + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + }); + const folderTokens = await folderFlow.fetchOAuth2Tokens(folder); + + const request = await httpRequestFlow.create(folder, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + }); + await httpRequestPage.auth.setAuthType(AuthType.Inherit); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(folderTokens.accessToken).not.toBe(""); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${folderTokens.accessToken}`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-implicit.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-implicit.spec.ts new file mode 100644 index 0000000000..8de3fac64e --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-implicit.spec.ts @@ -0,0 +1,85 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify Implicit grant with response type ID Token fetches only an identity token", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "implicit", + authorizationUrl: `${OAUTH2_SERVER}/authorize`, + clientId: OAUTH2_CLIENT_ID, + redirectUrl: `${OAUTH2_SERVER}/health`, + responseType: "id_token", + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + + // With no separate access_token in the fragment, the app displays the + // identity token in the Access Token field too, rather than leaving it + // blank. + expect(tokens.identityToken).not.toBe(""); + expect(tokens.accessToken).toBe(tokens.identityToken); +}); + +test("Verify Implicit grant with response type ID and Access Token authorizes the sent request", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "implicit", + authorizationUrl: `${OAUTH2_SERVER}/authorize`, + clientId: OAUTH2_CLIENT_ID, + redirectUrl: `${OAUTH2_SERVER}/health`, + responseType: "id_token token", + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(tokens.identityToken).not.toBe(""); + expect(tokens.accessToken).not.toBe(""); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${tokens.accessToken}`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-password.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-password.spec.ts new file mode 100644 index 0000000000..cd1d8021d6 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-password.spec.ts @@ -0,0 +1,60 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_PASSWORD, OAUTH2_SERVER, OAUTH2_USERNAME, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify Resource Owner Password Credentials grant fetches tokens with no popup and authorizes the sent request", async ({ + user, + insomnia, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "password", + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + username: OAUTH2_USERNAME, + password: OAUTH2_PASSWORD, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const windowsBeforeFetch = insomnia.windows().length; + const tokens = await httpRequestFlow.fetchOAuth2Tokens(request); + const windowsAfterFetch = insomnia.windows().length; + + const lastTokenRequest = await ( + await fetch( + `${OAUTH2_SERVER}/_debug/last-token-request?access_token=${encodeURIComponent(tokens.accessToken)}`, + ) + ).json(); + + const responseView = await httpRequestFlow.send(request); + const body = responseView.body as { authorization?: string }; + + expect(tokens.accessToken).not.toBe(""); + expect(tokens.refreshToken).not.toBe(""); + expect(windowsAfterFetch).toBe(windowsBeforeFetch); + expect(lastTokenRequest.grantType).toBe("password"); + expect(responseView.statusCode).toBe(200); + expect(body.authorization).toBe(`Bearer ${tokens.accessToken}`); +}); diff --git a/packages/insomnia-test/tests/http-request/oauth2-token-refresh-clear.spec.ts b/packages/insomnia-test/tests/http-request/oauth2-token-refresh-clear.spec.ts new file mode 100644 index 0000000000..0360eb31af --- /dev/null +++ b/packages/insomnia-test/tests/http-request/oauth2-token-refresh-clear.spec.ts @@ -0,0 +1,97 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { OAUTH2_CLIENT_ID, OAUTH2_CLIENT_SECRET, OAUTH2_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { AuthTypeOAuth2 } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify refreshing an existing OAuth 2.0 token issues a new access token without reopening the authorization window", async ({ + user, + insomnia, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "authorization_code", + authorizationUrl: `${OAUTH2_SERVER}/authorize`, + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + redirectUrl: `${OAUTH2_SERVER}/health`, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokensAfterFetch = await httpRequestFlow.fetchOAuth2Tokens(request); + + const windowsBeforeRefresh = insomnia.windows().length; + const tokensAfterRefresh = await httpRequestFlow.fetchOAuth2Tokens(request); + const windowsAfterRefresh = insomnia.windows().length; + + const lastTokenRequest = await ( + await fetch( + `${OAUTH2_SERVER}/_debug/last-token-request?access_token=${encodeURIComponent(tokensAfterRefresh.accessToken)}`, + ) + ).json(); + + expect(tokensAfterFetch.accessToken).not.toBe(""); + expect(windowsAfterRefresh).toBe(windowsBeforeRefresh); + expect(lastTokenRequest.grantType).toBe("refresh_token"); + expect(tokensAfterRefresh.refreshToken).toBe(tokensAfterFetch.refreshToken); + expect(tokensAfterRefresh.accessToken).not.toBe(tokensAfterFetch.accessToken); +}); + +test("Verify clearing OAuth 2.0 tokens wipes the stored Refresh/Identity/Access Token fields", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + const { httpRequestPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const authentication: AuthTypeOAuth2 = { + type: "oauth2", + grantType: "client_credentials", + accessTokenUrl: `${OAUTH2_SERVER}/token`, + clientId: OAUTH2_CLIENT_ID, + clientSecret: OAUTH2_CLIENT_SECRET, + }; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${OAUTH2_SERVER}/resource`, + authentication: authentication, + }); + + const tokensAfterFetch = await httpRequestFlow.fetchOAuth2Tokens(request); + + await httpRequestPage.auth.clearOAuth2Tokens(); + const tokensAfterClear = await httpRequestPage.auth.getOAuth2Tokens(); + + expect(tokensAfterFetch.accessToken).not.toBe(""); + expect(tokensAfterClear.accessToken).toBe(""); + expect(tokensAfterClear.refreshToken).toBe(""); + expect(tokensAfterClear.identityToken).toBe(""); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-async-completion.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-async-completion.spec.ts new file mode 100644 index 0000000000..4412756d7d --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-async-completion.spec.ts @@ -0,0 +1,91 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify pending async work in a pre-request script (Promise, setTimeout, sendRequest callback/await) is settled before the request is sent, and a synchronous replaceIn call in the same script is unaffected by it", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const syncVariableName = faker.string.alpha(8); + const syncVariableValue = faker.string.alphanumeric(10); + + const marker = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.JSON, + text: `{"asyncDoneViaPromise": {{ asyncDoneViaPromise }}, "asyncDoneViaTimeout": {{ asyncDoneViaTimeout }}, "asyncDoneViaCallback": {{ asyncDoneViaCallback }}, "bodyFromAwait": {{ bodyFromAwait }}, "bodyFromCallback": {{ bodyFromCallback }}, "syncReplaceInResult": "{{ syncReplaceInResult }}"}`, + }, + preRequestScript: ` + insomnia.environment.set('${syncVariableName}', '${syncVariableValue}'); + const syncReplaceInResult = insomnia.environment.replaceIn('{{ ${syncVariableName} }}'); + insomnia.environment.set('syncReplaceInResult', syncReplaceInResult); + + new Promise((resolve) => { + setTimeout(() => { + insomnia.environment.set('asyncDoneViaPromise', true); + resolve(); + }, 500); + }); + + setTimeout(() => { + insomnia.environment.set('asyncDoneViaTimeout', true); + }, 500); + + insomnia.sendRequest({ url: '${url}', method: 'POST' }, (err, resp) => { + if (err != null) { + throw err; + } else { + insomnia.environment.set('asyncDoneViaCallback', true); + } + }); + + let respFromCallback; + const respFromAwait = await insomnia.sendRequest({ + url: '${url}', + method: 'POST', + body: { mode: 'raw', raw: '${marker}' }, + }, (err, resp) => { + if (err != null) { + throw err; + } else { + respFromCallback = resp; + } + }); + insomnia.environment.set('bodyFromAwait', respFromAwait.body); + insomnia.environment.set('bodyFromCallback', respFromCallback.body); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + json: { + asyncDoneViaPromise: true, + asyncDoneViaTimeout: true, + asyncDoneViaCallback: true, + bodyFromAwait: expect.objectContaining({ data: marker }), + bodyFromCallback: expect.objectContaining({ data: marker }), + syncReplaceInResult: syncVariableValue, + }, + }); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-cookies.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-cookies.spec.ts new file mode 100644 index 0000000000..21ca5962ba --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-cookies.spec.ts @@ -0,0 +1,42 @@ +import { faker } from '@faker-js/faker'; +import { expect } from "@playwright/test"; + +import { HttpMethod } from '../../enums/http-method'; +import { ProjectType } from '../../enums/project-types'; +import { HTTP_SERVER, test } from '../../misc/fixtures'; +import { Collection } from '../../models/collection'; +import { Project } from '../../models/project'; + +test('Verify pre-request script manages cookies via insomnia.cookies.jar() and they are sent with the request', async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const keptName = faker.string.alpha(8); + const keptValue = faker.string.alphanumeric(10); + const unsetName = faker.string.alpha(9); + const objectName = faker.string.alpha(10); + const objectValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/cookies`, + preRequestScript: ` + const jar = insomnia.cookies.jar(); + const call = (fn, ...args) => new Promise((resolve, reject) => fn.call(jar, ...args, (err, res) => err ? reject(err) : resolve(res))); + await call(jar.set, '${HTTP_SERVER}', '${keptName}', { key: '${keptName}', value: '${keptValue}', domain: 'localhost' }); + await call(jar.set, '${HTTP_SERVER}', '${unsetName}', { key: '${unsetName}', value: 'temporary', domain: 'localhost' }); + await call(jar.set, '${HTTP_SERVER}', '${objectName}', { key: '${objectName}', value: '${objectValue}', domain: 'localhost' }); + await call(jar.unset, '${HTTP_SERVER}', '${unsetName}'); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toEqual({ + cookies: { [keptName]: keptValue, [objectName]: objectValue }, + }); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-custom-error.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-custom-error.spec.ts new file mode 100644 index 0000000000..c52326cc35 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-custom-error.spec.ts @@ -0,0 +1,63 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify a thrown error in pre-request script surfaces in the response console", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const errorMessage = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: `throw new Error('${errorMessage}');`, + }); + const responseView = await httpRequestFlow.send(request); + + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain(errorMessage); +}); + +test("Verify accessing an invalid field in pre-request script surfaces in the response console", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: `insomnia.INVALID_FIELD.set('', '');`, + }); + const responseView = await httpRequestFlow.send(request); + + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("Cannot read properties of undefined (reading 'set')"); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-environment-variables.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-environment-variables.spec.ts new file mode 100644 index 0000000000..86ac2aca7e --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-environment-variables.spec.ts @@ -0,0 +1,75 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify pre-request script overrides base environment values through the environment chain and persists set/get variables", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow, environmentFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const baseEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + }); + const subEnvironment = await environmentFlow.createSubEnvironment(baseEnvironment, { + name: faker.string.alphanumeric(10), + }); + await environmentFlow.link(collection, subEnvironment); + + const fallbackValue = faker.string.alphanumeric(10); + const baseScriptValue = faker.string.alphanumeric(10); + const envScriptValue = faker.string.alphanumeric(10); + const preDefinedValue = faker.string.alphanumeric(10); + const varStr = faker.string.alphanumeric(10); + const varNum = faker.number.int({ min: 1, max: 1000 }); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.JSON, + text: `{"fallbackToBase": "{{ fallbackToBase }}", "scriptValue": "{{ scriptValue }}", "preDefinedValue": "{{ preDefinedValue }}", "varStr": "{{ varStr }}", "varNum": {{ varNum }}, "varBool": {{ varBool }}}`, + }, + preRequestScript: ` + insomnia.baseEnvironment.set('fallbackToBase', '${fallbackValue}'); + insomnia.baseEnvironment.set('scriptValue', '${baseScriptValue}'); + insomnia.environment.set('scriptValue', '${envScriptValue}'); + insomnia.baseEnvironment.set('preDefinedValue', '${preDefinedValue}'); + + insomnia.variables.set('varStr', '${varStr}'); + insomnia.variables.set('varNum', ${varNum}); + insomnia.variables.set('varBool', true); + insomnia.environment.set('varStr', insomnia.variables.get('varStr')); + insomnia.environment.set('varNum', insomnia.variables.get('varNum')); + insomnia.environment.set('varBool', insomnia.variables.get('varBool')); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + json: { + fallbackToBase: fallbackValue, + scriptValue: envScriptValue, + preDefinedValue: preDefinedValue, + varStr, + varNum, + varBool: true, + }, + }); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-eval-and-modules.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-eval-and-modules.spec.ts new file mode 100644 index 0000000000..1f017e857c --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-eval-and-modules.spec.ts @@ -0,0 +1,126 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify pre-request script can eval() and require Node built-in modules, external modules, and insomnia-collection classes", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const parsedUrl = new URL( + "https://user:pwd@insomnia.com:6666/p1?q1=a&q2=b#hashcontent", + ); + const builtinModules = [ + "path", + "assert", + "buffer", + "util", + "url", + "punycode", + "querystring", + "string_decoder", + "stream", + "timers", + "events", + ]; + const externalModules = { + atob: "atob", + btoa: "btoa", + chai: "chai", + cheerio: "cheerio", + crypto: "crypto-js", + csv: "csv-parse/lib/sync", + lodash: "lodash", + moment: "moment", + tv4: "tv4", + uuid: "uuid", + xml2js: "xml2js", + }; + const externalModuleKeys = Object.keys(externalModules); + const propertyId = faker.string.alphanumeric(10); + const propertyName = faker.string.alphanumeric(10); + const headerKey = faker.string.alphanumeric(10); + const headerValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.JSON, + text: `{"evalResult": {{ evalResult }}, "hash": "{{ hash }}", "host": "{{ host }}", "hostname": "{{ hostname }}", "origin": "{{ origin }}", "password": "{{ password }}", "pathname": "{{ pathname }}", "port": "{{ port }}", "protocol": "{{ protocol }}", "search": "{{ search }}", "username": "{{ username }}", "searchParam": "{{ searchParam }}", ${builtinModules.map((m) => `"${m}": {{ ${m} }}`).join(", ")}, ${externalModuleKeys.map((k) => `"${k}": {{ ${k} }}`).join(", ")}, "builtInLodash": {{ builtInLodash }}, "uuidNil": "{{ uuidNil }}", "propJson": {{ propJson }}, "headerJson": {{ headerJson }}}`, + }, + preRequestScript: ` + const evalResult = eval('8+8'); + insomnia.environment.set('evalResult', evalResult); + + const { URL } = require('url'); + const parsedUrl = new URL('${parsedUrl.href}'); + insomnia.environment.set('hash', parsedUrl.hash); + insomnia.environment.set('host', parsedUrl.host); + insomnia.environment.set('hostname', parsedUrl.hostname); + insomnia.environment.set('origin', parsedUrl.origin); + insomnia.environment.set('password', parsedUrl.password); + insomnia.environment.set('pathname', parsedUrl.pathname); + insomnia.environment.set('port', parsedUrl.port); + insomnia.environment.set('protocol', parsedUrl.protocol); + insomnia.environment.set('search', parsedUrl.search); + insomnia.environment.set('username', parsedUrl.username); + insomnia.environment.set('searchParam', parsedUrl.searchParams.toString()); + + ${builtinModules.map((m) => `insomnia.environment.set('${m}', require('${m}') != null);`).join("\n")} + + ${externalModuleKeys.map((k) => `insomnia.environment.set('${k}', require('${externalModules[k as keyof typeof externalModules]}') != null);`).join("\n")} + insomnia.environment.set('builtInLodash', _ != null); + + const uuid = require('uuid'); + insomnia.environment.set('uuidNil', uuid.NIL); + + const { Property, Header } = require('insomnia-collection'); + const prop = new Property('${propertyId}', '${propertyName}'); + const header = new Header({ key: '${headerKey}', value: '${headerValue}' }); + insomnia.environment.set('propJson', JSON.stringify(prop.toJSON())); + insomnia.environment.set('headerJson', JSON.stringify(header.toJSON())); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + json: { + evalResult: 16, + hash: parsedUrl.hash, + host: parsedUrl.host, + hostname: parsedUrl.hostname, + origin: parsedUrl.origin, + password: parsedUrl.password, + pathname: parsedUrl.pathname, + port: parsedUrl.port, + protocol: parsedUrl.protocol, + search: parsedUrl.search, + username: parsedUrl.username, + searchParam: parsedUrl.searchParams.toString(), + ...Object.fromEntries(builtinModules.map((m) => [m, true])), + ...Object.fromEntries(externalModuleKeys.map((k) => [k, true])), + builtInLodash: true, + uuidNil: "00000000-0000-0000-0000-000000000000", + propJson: { id: propertyId, name: propertyName, disabled: false }, + headerJson: { key: headerKey, value: headerValue, id: "", name: "" }, + }, + }); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-request-manipulation.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-request-manipulation.spec.ts new file mode 100644 index 0000000000..702708b600 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-request-manipulation.spec.ts @@ -0,0 +1,171 @@ +import { faker } from '@faker-js/faker'; +import { expect } from "@playwright/test"; + +import { HttpMethod } from '../../enums/http-method'; +import { ProjectType } from '../../enums/project-types'; +import { HTTP_SERVER, test } from '../../misc/fixtures'; +import { Collection } from '../../models/collection'; +import { Project } from '../../models/project'; + +const url = `${HTTP_SERVER}/post`; + +test('Verify pre-request script manipulates request headers, query params and body via insomnia.request', async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const headerValue = faker.string.alphanumeric(10); + const rawContent = faker.string.alphanumeric(10); + const upsertedValue = faker.string.alphanumeric(10); + const addedParam = faker.string.alphanumeric(10); + const updatedParam = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${url}?removeMe=1&keepMe=2`, + preRequestScript: ` + const { Header } = require('insomnia-collection'); + insomnia.request.headers.add(new Header({ key: 'X-Hello', value: '${headerValue}' })); + insomnia.request.headers.add(new Header({ key: 'X-Remove', value: 'gone' })); + insomnia.request.headers.upsert({ key: 'X-Upsert', value: '${upsertedValue}' }); + insomnia.request.headers.remove((header) => header.key === 'X-Remove'); + insomnia.request.url.addQueryParams('added=${addedParam}'); + insomnia.request.url.removeQueryParams('removeMe'); + insomnia.request.url.removeQueryParams('keepMe'); + insomnia.request.url.addQueryParams('keepMe=${updatedParam}'); + insomnia.request.body.update({ mode: 'raw', raw: '${rawContent}' }); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + data: rawContent, + args: { added: addedParam, keepMe: updatedParam }, + headers: expect.objectContaining({ + 'x-hello': headerValue, + 'x-upsert': upsertedValue, + }), + }); + expect((responseView.body as { args: Record; headers: object }).args).not.toHaveProperty('removeMe'); + expect((responseView.body as { args: Record; headers: object }).headers).not.toHaveProperty('x-remove'); +}); + +test('Verify pre-request script sets bearer auth via insomnia.request.auth', async ({ user }) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const token = faker.string.alphanumeric(10); + const prefix = faker.string.alpha(8); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + insomnia.request.auth.update({ + type: 'bearer', + bearer: [ + { key: 'token', value: '${token}' }, + { key: 'prefix', value: '${prefix}' }, + ], + }, 'bearer'); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + authorization: `${prefix} ${token}`, + }), + }); +}); + +test('Verify pre-request script sets basic auth via insomnia.request.auth', async ({ user }) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const username = faker.string.alphanumeric(10); + const password = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + insomnia.request.auth.update({ + type: 'basic', + basic: [ + { key: 'username', value: '${username}' }, + { key: 'password', value: '${password}' }, + ], + }, 'basic'); + `, + }); + const responseView = await httpRequestFlow.send(request); + + const expectedEncodedCredentials = Buffer.from(`${username}:${password}`, 'utf8').toString('base64'); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + authorization: `Basic ${expectedEncodedCredentials}`, + }), + }); +}); + +test('Verify pre-request script applies apikey, AWS v4, Hawk and OAuth 1.0 auth via insomnia.request.auth', async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject(new Project(faker.string.alphanumeric(10), ProjectType.Local)); + const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10))); + + const apiKeyName = faker.string.alpha(8); + const apiKeyValue = faker.string.alphanumeric(10); + const accessKey = faker.string.alphanumeric(10); + const secretKey = faker.string.alphanumeric(20); + const region = faker.helpers.arrayElement(['us-east-1', 'eu-west-1']); + const service = faker.string.alpha(6); + const hawkId = faker.string.alphanumeric(10); + const hawkKey = faker.string.alphanumeric(20); + const consumerKey = faker.string.alphanumeric(10); + const consumerSecret = faker.string.alphanumeric(10); + + const authScripts = { + apiKeyHeader: `insomnia.request.auth.update({ type: 'apikey', apikey: [{ key: 'key', value: '${apiKeyName}' }, { key: 'value', value: '${apiKeyValue}' }, { key: 'in', value: 'header' }] }, 'apikey');`, + apiKeyQuery: `insomnia.request.auth.update({ type: 'apikey', apikey: [{ key: 'key', value: '${apiKeyName}' }, { key: 'value', value: '${apiKeyValue}' }, { key: 'in', value: 'queryParams' }] }, 'apikey');`, + awsV4: `insomnia.request.auth.update({ type: 'awsv4', awsv4: [{ key: 'accessKey', value: '${accessKey}' }, { key: 'secretKey', value: '${secretKey}' }, { key: 'region', value: '${region}' }, { key: 'service', value: '${service}' }] }, 'awsv4');`, + hawk: `insomnia.request.auth.update({ type: 'hawk', hawk: [{ key: 'authId', value: '${hawkId}' }, { key: 'authKey', value: '${hawkKey}' }, { key: 'algorithm', value: 'sha256' }] }, 'hawk');`, + oauth1: `insomnia.request.auth.update({ type: 'oauth1', oauth1: [{ key: 'consumerKey', value: '${consumerKey}' }, { key: 'consumerSecret', value: '${consumerSecret}' }, { key: 'signatureMethod', value: 'HMAC-SHA1' }] }, 'oauth1');`, + }; + const responses: Record>> = {}; + for (const [authName, preRequestScript] of Object.entries(authScripts)) { + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript, + }); + responses[authName] = await httpRequestFlow.send(request); + } + + const headersOf = (name: string) => (responses[name].body as { headers: Record }).headers; + expect(headersOf('apiKeyHeader')).toMatchObject({ + [apiKeyName.toLowerCase()]: apiKeyValue, + }); + expect(responses.apiKeyQuery.body).toMatchObject({ + args: { [apiKeyName]: apiKeyValue }, + }); + expect(headersOf('awsV4').authorization).toContain(`AWS4-HMAC-SHA256 Credential=${accessKey}/`); + expect(headersOf('awsV4').authorization).toContain(`/${region}/${service}/`); + expect(headersOf('hawk').authorization).toContain(`Hawk id="${hawkId}"`); + expect(headersOf('oauth1').authorization).toContain(`oauth_consumer_key="${consumerKey}"`); + expect(headersOf('oauth1').authorization).toContain('oauth_signature_method="HMAC-SHA1"'); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-send-request-body-modes.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-send-request-body-modes.spec.ts new file mode 100644 index 0000000000..b403d899f3 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-send-request-body-modes.spec.ts @@ -0,0 +1,71 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify pre-request script's sendRequest serializes raw, urlencoded, graphql, and formdata bodies correctly", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const rawValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + body: { + mimeType: ContentType.JSON, + text: `{"results": {{ results }}}`, + }, + preRequestScript: ` + const results = {}; + const modes = [ + { key: 'raw', body: { mode: 'raw', raw: '${rawValue}' } }, + { key: 'urlencoded', body: { mode: 'urlencoded', urlencoded: [{ key: 'a', value: '1' }, { key: 'b', value: '2' }] } }, + { key: 'graphql', body: { mode: 'graphql', graphql: { query: '{ __typename }', variables: {} } } }, + { key: 'formdata', body: { mode: 'formdata', formdata: [{ key: 'c', value: '3' }] } }, + ]; + for (const m of modes) { + const resp = await insomnia.sendRequest({ url: '${url}', method: 'POST', body: m.body }); + results[m.key] = resp.body; + } + insomnia.environment.set('results', JSON.stringify(results)); + `, + }); + const responseView = await httpRequestFlow.send(request); + + const results = ( + responseView.body as { json: { results: Record } } + ).json.results; + const raw = JSON.parse(results.raw); + const urlencoded = JSON.parse(results.urlencoded); + const graphql = JSON.parse(results.graphql); + const formdata = JSON.parse(results.formdata); + + expect(responseView.statusCode).toBe(200); + expect(raw.data).toBe(rawValue); + expect(urlencoded.data).toBe("a=1&b=2"); + expect(graphql.json).toMatchObject({ + query: "{ __typename }", + variables: {}, + }); + expect(formdata.data).toContain("--X-INSOMNIA-BOUNDARY"); + expect(formdata.data).toContain('Content-Disposition: form-data; name="c"'); + expect(formdata.data).toContain("3"); + expect(formdata.data.trimEnd()).toMatch(/--X-INSOMNIA-BOUNDARY--$/); +}); diff --git a/packages/insomnia-test/tests/http-request/query-method.spec.ts b/packages/insomnia-test/tests/http-request/query-method.spec.ts new file mode 100644 index 0000000000..4584976f49 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/query-method.spec.ts @@ -0,0 +1,42 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { CurlCommand } from "../../models/curl-command"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; +const body = `{"title": "${faker.string.alphanumeric(8)}"}`; +const command = `curl -X QUERY "${url}" -H "Content-Type: application/json" -d '${body}'`; + +test("Verify sending a QUERY request with a body", async ({ user }) => { + const { httpRequestFlow, importFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const curl: CurlCommand = { command }; + await importFlow.importCurl(project, collection, curl); + + const imported = await httpRequestFlow.get(url); + const responseView = await httpRequestFlow.send(imported!); + + expect(imported).toMatchObject({ + name: url, + url, + method: HttpMethod.Query, + body: { mimeType: ContentType.JSON, text: body }, + } satisfies HttpRequest); + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ data: body }); +}); diff --git a/packages/insomnia-test/tests/http-request/query-params-transformed-correctly.spec.ts b/packages/insomnia-test/tests/http-request/query-params-transformed-correctly.spec.ts new file mode 100644 index 0000000000..332433dd72 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/query-params-transformed-correctly.spec.ts @@ -0,0 +1,79 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import { Project } from "../../models/project"; + +test("Verify query params from the URL, template tags, and the Parameters tab are merged and special characters are escaped correctly", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow, environmentFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const fromUrlValue = faker.string.alphanumeric(10); + const fromEditorValue = faker.string.alphanumeric(10); + const environment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { + id: "", + name: "fromUrlValue", + value: fromUrlValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + { + id: "", + name: "fromEditorValue", + value: fromEditorValue, + type: EnvironmentKvPairDataType.STRING, + enabled: true, + }, + ], + }); + await environmentFlow.link(collection, environment); + + const spaceValue = `${faker.string.alphanumeric(4)} ${faker.string.alphanumeric(4)}`; + const ampersandValue = `${faker.string.alphanumeric(4)}&${faker.string.alphanumeric(4)}`; + const equalsValue = `${faker.string.alphanumeric(4)}=${faker.string.alphanumeric(4)}`; + const hashValue = `${faker.string.alphanumeric(4)}#${faker.string.alphanumeric(4)}`; + const chineseValue = `测试${faker.string.alphanumeric(4)}参数`; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?key=fromUrl&key={{ fromUrlValue }}`, + params: [ + { name: "key", value: "{{ fromEditorValue }}" }, + { name: "key", value: "/" }, + { name: "space", value: spaceValue }, + { name: "ampersand", value: ampersandValue }, + { name: "equals", value: equalsValue }, + { name: "hash", value: hashValue }, + { name: "chinese", value: chineseValue }, + ], + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + args: { + key: ["fromUrl", fromUrlValue, fromEditorValue, "/"], + space: spaceValue, + ampersand: ampersandValue, + equals: equalsValue, + hash: hashValue, + chinese: chineseValue, + }, + }); +}); diff --git a/packages/insomnia-test/tests/http-request/replace-in-synchronous-behavior.spec.ts b/packages/insomnia-test/tests/http-request/replace-in-synchronous-behavior.spec.ts new file mode 100644 index 0000000000..d1bec6f7a2 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/replace-in-synchronous-behavior.spec.ts @@ -0,0 +1,76 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify insomnia.environment.replaceIn resolves and returns a string synchronously", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const variableName = faker.string.alpha(8); + const variableValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + insomnia.environment.set('${variableName}', '${variableValue}'); + const v = insomnia.environment.replaceIn('{{ ${variableName} }}'); + console.log(typeof v, v); + `, + }); + const responseView = await httpRequestFlow.send(request); + + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain(`log: string ${variableValue}`); + const consoleText = await responseView.console?.(); + expect(consoleText).not.toContain("Promise"); +}); + +test("Verify a script that awaits insomnia.environment.replaceIn as a workaround still resolves correctly", async ({ + user, +}) => { + const { httpRequestFlow, workspaceFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const variableName = faker.string.alpha(8); + const variableValue = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + insomnia.environment.set('${variableName}', '${variableValue}'); + const v = await insomnia.environment.replaceIn('{{ ${variableName} }}'); + console.log(typeof v, v); + `, + }); + const responseView = await httpRequestFlow.send(request); + + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain(`log: string ${variableValue}`); +}); diff --git a/packages/insomnia-test/tests/template-tag/cookie-tag-reads-linked-cookie.spec.ts b/packages/insomnia-test/tests/template-tag/cookie-tag-reads-linked-cookie.spec.ts new file mode 100644 index 0000000000..7861830902 --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/cookie-tag-reads-linked-cookie.spec.ts @@ -0,0 +1,38 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify the cookie Tag Reads a Value from a Linked Cookie", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, cookieFlow, templateTagFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const cookieTag = `{% cookie '${HTTP_SERVER}', 'session' %}`; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/${cookieTag}`, + }); + + const cookieValue = faker.string.alphanumeric(10); + await cookieFlow.link(collection, [ + { key: "session", value: cookieValue, domain: "localhost", path: "/" }, + ]); + await httpRequestFlow.get(request!.name); + const tag = await templateTagFlow.get(cookieTag); + + expect(tag.preview).toBe(cookieValue); +}); diff --git a/packages/insomnia-test/tests/template-tag/edit-existing-tag-argument.spec.ts b/packages/insomnia-test/tests/template-tag/edit-existing-tag-argument.spec.ts new file mode 100644 index 0000000000..aa9690a49c --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/edit-existing-tag-argument.spec.ts @@ -0,0 +1,45 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Switching an Existing Tag's Argument Updates its Live Preview", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, templateTagFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const hashTag = "{% hash 'md5', 'hex', 'hello' %}"; + await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${HTTP_SERVER}/${hashTag}`, + }); + + const before = await templateTagFlow.get(hashTag); + const after = await templateTagFlow.edit(hashTag, { + functionName: "hash", + algorithm: "sha256", + digestEncoding: "hex", + input: "hello", + preview: "", + }); + + expect(before.functionName).toBe("hash"); + expect(before.algorithm).toBe("md5"); + expect(before.preview).toBe("5d41402abc4b2a76b9719d911017c592"); + expect(after.algorithm).toBe("sha256"); + expect(after.preview).toBe( + "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", + ); +}); diff --git a/packages/insomnia-test/tests/template-tag/external-vault-credentials.spec.ts b/packages/insomnia-test/tests/template-tag/external-vault-credentials.spec.ts new file mode 100644 index 0000000000..4f25e1d005 --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/external-vault-credentials.spec.ts @@ -0,0 +1,110 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; +import { + AWSCredentialType, + HashiCorpCredentialType, + HashiCorpVaultAuthMethod, +} from "insomnia-data"; + +import { ContentType } from "../../enums/content-type"; +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import type { HttpRequest } from "../../models/http-request"; +import { Project } from "../../models/project"; + +test("Verify AWS/GCP/HashiCorp Cloud Credentials Resolve via the Vault Template Tag", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, templateTagFlow, preferencesFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const awsCredentialName = faker.string.alphanumeric(10); + const gcpCredentialName = faker.string.alphanumeric(10); + const hashicorpCredentialName = faker.string.alphanumeric(10); + await preferencesFlow.set({ + // Live Preview masks vault values as •••••• by default; the spec asserts the resolved value. + hideSecretValuesInPreviewAndConsole: false, + cloudCredentials: [ + { + provider: "aws", + name: awsCredentialName, + credentials: { + type: AWSCredentialType.file, + section: faker.string.alphanumeric(8), + region: faker.string.alphanumeric(8), + }, + }, + { + provider: "gcp", + name: gcpCredentialName, + credentials: { serviceAccountKeyFilePath: faker.system.filePath() }, + }, + { + provider: "hashicorp", + name: hashicorpCredentialName, + credentials: { + type: HashiCorpCredentialType.onPrem, + authMethod: HashiCorpVaultAuthMethod.appRole, + serverAddress: "http://127.0.0.1", + role_id: faker.string.alphanumeric(8), + secret_id: faker.string.alphanumeric(8), + }, + }, + ], + }); + + const awsTag = "{% vault 'aws', '', '{}' %}"; + const gcpTag = "{% vault 'gcp', '', '{}' %}"; + const hashicorpTag = "{% vault 'hashicorp', '', '{}' %}"; + const request = { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + body: { + mimeType: ContentType.Plain, + text: `${awsTag}\n${gcpTag}\n${hashicorpTag}`, + }, + } satisfies HttpRequest; + await httpRequestFlow.create(collection, request); + + const awsResult = await templateTagFlow.edit(awsTag, { + functionName: "vault", + credentialName: awsCredentialName, + preview: "", + }); + const gcpResult = await templateTagFlow.edit(gcpTag, { + functionName: "vault", + credentialName: gcpCredentialName, + preview: "", + }); + const hashicorpResult = await templateTagFlow.edit(hashicorpTag, { + functionName: "vault", + credentialName: hashicorpCredentialName, + preview: "", + }); + + const responseView = await httpRequestFlow.send(request); + const secondResponse = await httpRequestFlow.send(request); + + expect(awsResult.preview).toBe("aws-secret-value"); + expect(gcpResult.preview).toBe("gcp-secret-value"); + expect(hashicorpResult.preview).toBe("hashicorp-secret-value"); + expect((responseView.body as { data: string }).data).toContain("aws-secret-value"); + expect((responseView.body as { data: string }).data).toContain("gcp-secret-value"); + expect((responseView.body as { data: string }).data).toContain( + "hashicorp-secret-value", + ); + expect((secondResponse.body as { data: string }).data).toContain( + "aws-secret-value", + ); +}); diff --git a/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/file-tag-reads-allowed-file.spec.ts b/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/file-tag-reads-allowed-file.spec.ts new file mode 100644 index 0000000000..f8c43597d6 --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/file-tag-reads-allowed-file.spec.ts @@ -0,0 +1,39 @@ +import path from "node:path"; + +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../../enums/http-method"; +import { ProjectType } from "../../../enums/project-types"; +import { test } from "../../../misc/fixtures"; +import { Collection } from "../../../models/collection"; +import { Project } from "../../../models/project"; + +const filePath = path.join(__dirname, "fixture.txt"); + +test("Verify the file Tag Reads Contents from an Allowed Folder", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, preferencesFlow, templateTagFlow } = + user.flowManager; + + await preferencesFlow.set({ dataFolders: [__dirname] }); + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + const fileTag = `{% file '${filePath}' %}`; + await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: fileTag, + }); + + const result = await templateTagFlow.get(fileTag); + + expect(result.preview).toBe("hello-from-file-tag"); +}); diff --git a/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/fixture.txt b/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/fixture.txt new file mode 100644 index 0000000000..f4e036b75d --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/file-tag-reads-allowed-file/fixture.txt @@ -0,0 +1 @@ +hello-from-file-tag \ No newline at end of file diff --git a/packages/insomnia-test/tests/template-tag/happy-path.spec.ts b/packages/insomnia-test/tests/template-tag/happy-path.spec.ts new file mode 100644 index 0000000000..c81380bc5e --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/happy-path.spec.ts @@ -0,0 +1,54 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify Inserting a Template Tag and Reading its Live Preview", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, templateTagFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const uuidTag = "{% uuid 'v4' %}"; + const base64Tag = "{% base64 'encode', 'normal', 'hello' %}"; + const nowTag = "{% now 'unix' %}"; + const fakerTag = "{% faker 'randomFirstName' %}"; + const jsonpathTag = "{% jsonpath '{\"a\":{\"b\":42}}', '$.a.b' %}"; + const osTag = "{% os 'platform' %}"; + const requestTag = "{% request 'name' %}"; + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Get, + url: `${uuidTag}${base64Tag}${nowTag}${fakerTag}${jsonpathTag}${osTag}${requestTag}`, + }); + + const uuidResult = await templateTagFlow.get(uuidTag); + const base64Result = await templateTagFlow.get(base64Tag); + const nowResult = await templateTagFlow.get(nowTag); + const fakerResult = await templateTagFlow.get(fakerTag); + const jsonpathResult = await templateTagFlow.get(jsonpathTag); + const osResult = await templateTagFlow.get(osTag); + const requestResult = await templateTagFlow.get(requestTag); + + expect(uuidResult.preview).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, + ); + expect(base64Result.preview).toBe("aGVsbG8="); + expect(nowResult.preview).toMatch(/^\d+$/); + expect(fakerResult.preview).toMatch(/^[A-Za-z'-]+$/); + expect(jsonpathResult.preview).toBe("42"); + expect(osResult.preview).toBe(process.platform); + expect(requestResult.preview).toBe(request!.name); +}); diff --git a/packages/insomnia-test/tests/template-tag/header-name-template-tag-resolves.spec.ts b/packages/insomnia-test/tests/template-tag/header-name-template-tag-resolves.spec.ts new file mode 100644 index 0000000000..75fa323786 --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/header-name-template-tag-resolves.spec.ts @@ -0,0 +1,42 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +test("Verify a Header With a Templated Name Resolves and Sends Correctly", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + headers: [ + { + name: "X-{% hash 'md5', 'hex', 'header-name' %}", + value: "plain-value", + }, + ], + }); + const responseView = await httpRequestFlow.send(request!); + + expect(responseView.statusCode).toBe(200); + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + "x-7cd8e9f98e9104085ea06e39b87faf36": "plain-value", + }), + }); +}); diff --git a/packages/insomnia-test/tests/template-tag/header-templates.spec.ts b/packages/insomnia-test/tests/template-tag/header-templates.spec.ts new file mode 100644 index 0000000000..93b0b2b553 --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/header-templates.spec.ts @@ -0,0 +1,41 @@ +import { faker } from "@faker-js/faker"; +import { expect } from "@playwright/test"; + +import { HttpMethod } from "../../enums/http-method"; +import { ProjectType } from "../../enums/project-types"; +import { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const url = `${HTTP_SERVER}/post`; + +test("Verify a Header With a Template Tag in Its Value", async ({ user }) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + const collection = await workspaceFlow.createCollection( + project, + new Collection(faker.string.alphanumeric(10)), + ); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + headers: [ + { + name: "X-Header-Name", + value: "{% base64 'encode', 'normal', 'header-value' %}", + }, + ], + }); + const responseView = await httpRequestFlow.send(request!); + + expect(responseView.body).toMatchObject({ + headers: expect.objectContaining({ + "x-header-name": "aGVhZGVyLXZhbHVl", + }), + }); +});