diff --git a/packages/insomnia-test/tests/environment/environment-pickers-and-creation.spec.ts b/packages/insomnia-test/tests/environment/environment-pickers-and-creation.spec.ts new file mode 100644 index 0000000000..02d87f6c76 --- /dev/null +++ b/packages/insomnia-test/tests/environment/environment-pickers-and-creation.spec.ts @@ -0,0 +1,72 @@ +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 { Project } from "../../models/project"; + +test("Verify the project and collection environment pickers open and close independently", async ({ + user, +}) => { + const { 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 openAfterCollection = await environmentFlow.getOpenPickersAfterOpening(collection, "collection"); + const openAfterProject = await environmentFlow.getOpenPickersAfterOpening(collection, "project"); + + expect(openAfterCollection).toEqual(["collection"]); + expect(openAfterProject).toEqual(["project"]); +}); + +test("Verify Add Project Environment from the picker lists the new environment without selecting it", async ({ + user, +}) => { + const { 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 environmentName = faker.string.alphanumeric(10); + const pickerState = await environmentFlow.addProjectEnvironmentFromPicker(collection, environmentName); + + expect(pickerState.selected).toContain("No Project Environment"); + expect(pickerState.options.some((option) => option.includes(environmentName))).toBe(true); +}); + +test("Verify Add Sub Environment and Add Private Sub Environment create environments with the correct privacy", async ({ + user, +}) => { + const { 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 shared = await environmentFlow.createCollectionSubEnvironment(collection, { + name: faker.string.alphanumeric(10), + }); + const privateEnvironment = await environmentFlow.createCollectionSubEnvironment(collection, { + name: faker.string.alphanumeric(10), + isPrivate: true, + }); + + expect(shared.isPrivate).toBe(false); + expect(privateEnvironment.isPrivate).toBe(true); +}); diff --git a/packages/insomnia-test/tests/environment/folder-environment-overrides-sub-and-base.spec.ts b/packages/insomnia-test/tests/environment/folder-environment-overrides-sub-and-base.spec.ts new file mode 100644 index 0000000000..0dc992b9f5 --- /dev/null +++ b/packages/insomnia-test/tests/environment/folder-environment-overrides-sub-and-base.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 { HTTP_SERVER, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { EnvironmentKvPairDataType } from "../../models/environment"; +import { Folder } from "../../models/folder"; +import { Project } from "../../models/project"; + +const sharedVariable = faker.string.alpha(8); +const subOnlyVariable = faker.string.alpha(9); + +test("Verify a folder environment overrides sub and base environments for its requests while other variables fall through", async ({ + user, +}) => { + const { workspaceFlow, environmentFlow, folderFlow, 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 baseShared = faker.string.alphanumeric(10); + const baseSubOnly = faker.string.alphanumeric(10); + const subShared = faker.string.alphanumeric(10); + const subSubOnly = faker.string.alphanumeric(10); + const folderShared = faker.string.alphanumeric(10); + + const baseEnvironment = await environmentFlow.createBaseEnvironment(project, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { id: "", name: sharedVariable, value: baseShared, type: EnvironmentKvPairDataType.STRING, enabled: true }, + { id: "", name: subOnlyVariable, value: baseSubOnly, type: EnvironmentKvPairDataType.STRING, enabled: true }, + ], + }); + const subEnvironment = await environmentFlow.createSubEnvironment(baseEnvironment, { + name: faker.string.alphanumeric(10), + kvPairData: [ + { id: "", name: sharedVariable, value: subShared, type: EnvironmentKvPairDataType.STRING, enabled: true }, + { id: "", name: subOnlyVariable, value: subSubOnly, type: EnvironmentKvPairDataType.STRING, enabled: true }, + ], + }); + await environmentFlow.link(collection, subEnvironment); + + const folder = await folderFlow.create(collection, new Folder(faker.string.alphanumeric(10))); + await folderFlow.setEnvironmentVariables(folder, { [sharedVariable]: folderShared }); + + const requestUrl = `${HTTP_SERVER}/post?shared={{ ${sharedVariable} }}&subOnly={{ ${subOnlyVariable} }}`; + const requestInFolder = await httpRequestFlow.create(folder, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: requestUrl, + }); + const requestOutsideFolder = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: requestUrl, + }); + const responseInFolder = await httpRequestFlow.send(requestInFolder); + const responseOutsideFolder = await httpRequestFlow.send(requestOutsideFolder); + + expect(responseInFolder.statusCode).toBe(200); + expect(responseInFolder.body).toMatchObject({ + args: { shared: folderShared, subOnly: subSubOnly }, + }); + expect(responseOutsideFolder.statusCode).toBe(200); + expect(responseOutsideFolder.body).toMatchObject({ + args: { shared: subShared, subOnly: subSubOnly }, + }); +}); diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/collection-for-global-environments.yaml b/packages/insomnia-test/tests/environment/variable-source-live-preview/collection-for-global-environments.yaml new file mode 100644 index 0000000000..d29f8b9b2e --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/collection-for-global-environments.yaml @@ -0,0 +1,106 @@ +type: collection.insomnia.rest/5.0 +schema_version: "5.1" +name: collection-for-global-environments +meta: + id: wrk_711bb959b80944d793e93df7c237ac56 + created: 1736618909373 + modified: 1736618909373 +collection: + - url: localhost:4060/post + name: New Request + meta: + id: req_180c79263db64fd8a01a2579cdcb5778 + created: 1670587797191 + modified: 1728549768587 + isPrivate: false + method: GET + body: + mimeType: text/plain + text: |- + {{ _.exampleObject.anotherString }} + {{ _.exampleString }} + {{ _.exampleNumber }} + {{ _.exampleObject.anotherNumber }} + {{ _['global-base'] }} + {{ _['global-sub'] }} + headers: + - name: Content-Type + value: text/plain + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true + - url: http://localhost:4060/post + name: Global Var Request + meta: + id: req_5f4e6d7c8b9a0f1e2d3c4b5a69788779 + created: 1736618909374 + modified: 1736618909374 + isPrivate: false + method: POST + body: + mimeType: application/json + text: |- + { + "global-base": "{{ _['global-base'] }}" + } + headers: + - name: Content-Type + value: application/json + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true +cookieJar: + name: Default Jar + meta: + id: jar_11119dfd3e8649b2a6195f90e0d4133a + created: 1670587793248 + modified: 1670587793248 +environments: + name: Base Environment + meta: + id: env_6395586a9463467eb7c2b0d2a5a47bea + created: 1670587793246 + modified: 1670587811762 + isPrivate: false + data: + exampleString: baseenv0 + exampleNumber: 1001 + exampleObject: + anotherString: baseenv1 + anotherNumber: 1001 + subEnvironments: + - name: ExampleA + meta: + id: env_0e6c9e63f01e48a1b2d64f269dea4c87 + created: 1670587812382 + modified: 1670587818649 + isPrivate: false + data: + exampleString: subenvA0 + exampleNumber: 1111 + globalSourcedVar: "{{ _['global-base'] }}" + exampleObject: + anotherString: subenvA1 + anotherNumber: 1111 + - name: ExampleB + meta: + id: env_bbf062fb34d94777841fb1f2ed60cb7d + created: 1670587826837 + modified: 1670587831933 + isPrivate: false + data: + exampleString: subenvB0 + exampleNumber: 2222 + exampleObject: + anotherString: subenvB1 + anotherNumber: 2222 diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/environment-cascade-collection.yaml b/packages/insomnia-test/tests/environment/variable-source-live-preview/environment-cascade-collection.yaml new file mode 100644 index 0000000000..9ae7141b2d --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/environment-cascade-collection.yaml @@ -0,0 +1,145 @@ +type: collection.insomnia.rest/5.0 +schema_version: "5.1" +name: Environment Cascade +meta: + id: wrk_7c1e4a8f9b2d4c6e8f1a3b5c7d9e0f11 + created: 1739887154405 + modified: 1739887154405 +collection: + - url: http://localhost:4060/post + name: cascade request no folder + meta: + id: req_4c6e8a0b2d4f6a8c0e2b4d6f8a0c2e18 + created: 1636141014552 + modified: 1636707449231 + isPrivate: false + sortKey: -1636141014554 + method: POST + body: + mimeType: application/json + text: |- + { + "cascadeVar": "{{ _.cascadeVar }}" + } + headers: + - name: Content-Type + value: application/json + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true + - name: cascadeFolder + meta: + id: fld_5b2c9e1a7d3f4a6b8c0d2e4f6a8b0c12 + created: 1668533312225 + modified: 1668533312225 + sortKey: -1668533312225 + children: + - url: http://localhost:4060/post + name: cascade request + meta: + id: req_1f3e5a7c9b1d3f5a7c9e1b3d5f7a9c13 + created: 1636141014552 + modified: 1636707449231 + isPrivate: false + sortKey: -1636141014553 + method: POST + body: + mimeType: application/json + text: |- + { + "cascadeVar": "{{ _.cascadeVar }}", + "subOnlyVar": "{{ _.subOnlyVar }}" + } + headers: + - name: Content-Type + value: application/json + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true + - name: innerFolder + meta: + id: fld_9d4e8f0a1b2c3d4e5f6a7b8c9d0e1f22 + created: 1668533312226 + modified: 1668533312226 + sortKey: -1668533312224 + children: + - url: http://localhost:4060/post + name: inner request + meta: + id: req_0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c23 + created: 1636141014554 + modified: 1636707449232 + isPrivate: false + sortKey: -1636141014554 + method: POST + body: + mimeType: application/json + text: |- + { + "cascadeVar": "{{ _.cascadeVar }}", + "folderOverride": "{{ _.folderOverride }}" + } + headers: + - name: Content-Type + value: application/json + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true + environment: + folderOverride: fromInnerFolder + environment: + cascadeVar: fromFolder + folderOverride: fromOuterFolder +cookieJar: + name: Default Jar + meta: + id: jar_8e2f4a6c1b3d5f7a9c1e3b5d7f9a1c14 + created: 1707808692807 + modified: 1707808692807 +environments: + name: Base Environment + meta: + id: env_9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c15 + created: 1670587793246 + modified: 1670587811762 + isPrivate: false + data: + cascadeVar: fromBase + subOnlyVar: subOnly_base + baseOnlyVar: baseOnlyValue + subEnvironments: + - name: SubEnvA + meta: + id: env_2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e16 + created: 1670587812382 + modified: 1670587818649 + isPrivate: false + data: + cascadeVar: fromSubA + subOnlyVar: subOnly_A + baseSourcedVar: '{{ _.baseOnlyVar }}' + folderSourcedVar: '{{ _.folderOverride }}' + - name: SubEnvB + meta: + id: env_3b5d7f9c1e3a5c7e9b1d3f5a7c9e1f17 + created: 1670587826837 + modified: 1736605621292 + isPrivate: false + data: + cascadeVar: fromSubB + subOnlyVar: subOnly_B diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/global-environment.yaml b/packages/insomnia-test/tests/environment/variable-source-live-preview/global-environment.yaml new file mode 100644 index 0000000000..d368740df5 --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/global-environment.yaml @@ -0,0 +1,28 @@ +type: environment.insomnia.rest/5.0 +schema_version: "5.1" +name: global-environment +meta: + id: wrk_7a6debc2484d48db88897609ab21f0eb + created: 1736618596041 + modified: 1736618596041 +environments: + name: Base Environment + meta: + id: env_aace0582bc8c4445be6689f340a08c20 + created: 1728549301585 + modified: 1728549418314 + isPrivate: false + data: + global-base: 4444 + exampleString: globalenv0 + subEnvironments: + - name: New Environment + meta: + id: env_a9aa1b50ed15403aacc30d50be6a11a7 + created: 1728549316977 + modified: 1728549426890 + isPrivate: true + data: + global-sub: 55555 + exampleString: globalenv1 + baseSourcedVar: "{{ _['global-base'] }}" diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/project-environment-source.yaml b/packages/insomnia-test/tests/environment/variable-source-live-preview/project-environment-source.yaml new file mode 100644 index 0000000000..30c3c26815 --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/project-environment-source.yaml @@ -0,0 +1,25 @@ +type: environment.insomnia.rest/5.0 +schema_version: "5.1" +name: project-environment-source +meta: + id: wrk_5c1e0a7d93f24b64a1f0e2d3c4b5a697 + created: 1736618596041 + modified: 1736618596041 +environments: + name: Base Environment + meta: + id: env_6c1e0a7d93f24b64a1f0e2d3c4b5a698 + created: 1728549301585 + modified: 1728549418314 + isPrivate: false + data: + text_base: fromProjectBase + subEnvironments: + - name: Source Sub Environment + meta: + id: env_7c1e0a7d93f24b64a1f0e2d3c4b5a699 + created: 1728549316977 + modified: 1728549426890 + isPrivate: false + data: + text_ref: '{{ _.text_base }}' diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-levels.spec.ts b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-levels.spec.ts new file mode 100644 index 0000000000..804ca1e703 --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-levels.spec.ts @@ -0,0 +1,192 @@ +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 { Project } from "../../../models/project"; +import type { FlowManager } from "../../../flows/flow-manager"; +import type { VariableSourceLanding } from "../../../flows/variable-source.flow"; + +const cascadeFixture = path.join(__dirname, "environment-cascade-collection.yaml"); +const globalCollectionFixture = path.join(__dirname, "collection-for-global-environments.yaml"); +const globalEnvironmentFixture = path.join(__dirname, "global-environment.yaml"); + +interface Scenario { + id: string; + collection: string; + fixtures: string[]; + request: string; + environment: string; + linkedProjectEnvironment?: string; + field: string; + snippet: string; + chipName: string; + value: string; + source: string; + landing: VariableSourceLanding; + landingRow?: string; +} + +const scenarios: Scenario[] = [ + { + id: "collection base environment", + collection: "Environment Cascade", + fixtures: [cascadeFixture], + request: "cascade request no folder", + environment: "Base Environment", + field: "cascadeVar", + snippet: "cascadeVar", + chipName: "_.cascadeVar", + value: "fromBase", + source: "Collection Environment : Base Environment", + landing: "environments-modal", + landingRow: "Base Environment", + }, + { + id: "collection sub environment overriding base", + collection: "Environment Cascade", + fixtures: [cascadeFixture], + request: "cascade request no folder", + environment: "SubEnvA", + field: "cascadeVar", + snippet: "cascadeVar", + chipName: "_.cascadeVar", + value: "fromSubA", + source: "Collection Environment : SubEnvA", + landing: "environments-modal", + landingRow: "SubEnvA", + }, + { + id: "folder environment winning over base and sub", + collection: "Environment Cascade", + fixtures: [cascadeFixture], + request: "cascade request", + environment: "Base Environment", + field: "cascadeVar", + snippet: "cascadeVar", + chipName: "_.cascadeVar", + value: "fromFolder", + source: "Folder Environment : cascadeFolder", + landing: "folder-pane", + }, + { + id: "nested folder environment overriding the outer folder", + collection: "Environment Cascade", + fixtures: [cascadeFixture], + request: "inner request", + environment: "Base Environment", + field: "folderOverride", + snippet: "folderOverride", + chipName: "_.folderOverride", + value: "fromInnerFolder", + source: "Folder Environment : innerFolder", + landing: "folder-pane", + }, + { + id: "project environment", + collection: "collection-for-global-environments", + fixtures: [globalCollectionFixture, globalEnvironmentFixture], + request: "Global Var Request", + environment: "Base Environment", + linkedProjectEnvironment: "global-environment", + field: "global-base", + snippet: "global-base", + chipName: "_['global-base']", + value: "4444", + source: "Project Environment : Base Environment", + landing: "environment-page", + landingRow: "Base Environment", + }, +]; + +async function selectScenarioEnvironment( + flowManager: FlowManager, + collection: Collection, + scenario: Scenario, +): Promise { + const { environmentFlow, variableSourceFlow } = flowManager; + if (scenario.linkedProjectEnvironment) { + await variableSourceFlow.selectProjectEnvironment( + collection, + scenario.linkedProjectEnvironment, + ); + return; + } + await environmentFlow.activate(collection, scenario.environment); +} + +for (const scenario of scenarios) { + test(`Verify ${scenario.id} is sent, previewed in the tag modal, and opens its source from the modal`, async ({ + user, + }) => { + const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + for (const fixture of scenario.fixtures) { + await importFlow.importFile(project, fixture); + } + const collection = new Collection(scenario.collection); + await selectScenarioEnvironment(user.flowManager, collection, scenario); + + const request = await httpRequestFlow.get(scenario.request); + const response = await httpRequestFlow.send(request!); + const modal = await variableSourceFlow.inspectVariableModal( + scenario.snippet, + scenario.value, + ); + await variableSourceFlow.openSourceFromModal(); + const navigation = await variableSourceFlow.getNavigation( + scenario.landing, + scenario.landingRow, + ); + + expect(response.statusCode).toBe(200); + expect( + JSON.parse((response.body as { data: string }).data)[scenario.field], + ).toBe(scenario.value); + expect(modal.source).toContain(scenario.source); + expect(modal.preview).toBe(scenario.value); + expect(navigation.selectedEnvironmentRow).toBe( + scenario.landingRow === undefined ? undefined : true, + ); + expect(navigation.environmentTabSelected).toBe( + scenario.landing === "folder-pane" ? true : undefined, + ); + expect(navigation.url).toMatch( + scenario.landing === "folder-pane" + ? /debug\/request-group\// + : scenario.landing === "environment-page" + ? /\/environment\/?$/ + : /.*/, + ); + }); + + test(`Verify hovering the ${scenario.id} tag shows its value and source in the tooltip`, async ({ + user, + }) => { + const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + for (const fixture of scenario.fixtures) { + await importFlow.importFile(project, fixture); + } + const collection = new Collection(scenario.collection); + await selectScenarioEnvironment(user.flowManager, collection, scenario); + + await httpRequestFlow.get(scenario.request); + const hovered = await variableSourceFlow.hoverVariable(scenario.snippet); + + expect(hovered.tooltip).toContain(scenario.value); + expect(hovered.tooltip).toContain(scenario.source); + expect(hovered.chip).toBe(scenario.chipName); + }); +} diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-modal-editing.spec.ts b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-modal-editing.spec.ts new file mode 100644 index 0000000000..aa5a5b15f6 --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-modal-editing.spec.ts @@ -0,0 +1,50 @@ +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 { Project } from "../../../models/project"; + +test("Verify the tag modal's source follows the edited template and hides for non-variable content", async ({ + user, +}) => { + const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile( + project, + path.join(__dirname, "environment-cascade-collection.yaml"), + ); + const collection = new Collection("Environment Cascade"); + await environmentFlow.activate(collection, "Base Environment"); + + await httpRequestFlow.get("cascade request"); + const initial = await variableSourceFlow.inspectVariableModal("cascadeVar", "fromFolder"); + const switched = await variableSourceFlow.selectModalVariable( + "{{ _.subOnlyVar }}", + "subOnly_base", + ); + await variableSourceFlow.selectModalVariable({ label: "-- Custom --" }); + const plainText = await variableSourceFlow.setModalTemplate("plain text", "plain text"); + const inheritedKey = await variableSourceFlow.setModalTemplate("{{ constructor }}"); + const padded = await variableSourceFlow.setModalTemplate("{{ _.subOnlyVar }}", "subOnly_base"); + await variableSourceFlow.openSourceFromModal(); + const navigation = await variableSourceFlow.getNavigation( + "environments-modal", + "Base Environment", + ); + + expect(initial.source).toContain("Folder Environment : cascadeFolder"); + expect(switched.source).toContain("Collection Environment : Base Environment"); + expect(plainText.source).toBeUndefined(); + expect(inheritedKey.source).toBeUndefined(); + expect(padded.source).toContain("Collection Environment : Base Environment"); + expect(navigation.selectedEnvironmentRow).toBe(true); + expect(navigation.manageEnvironmentsModalCount).toBe(1); +}); diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-behavior.spec.ts b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-behavior.spec.ts new file mode 100644 index 0000000000..93cc0bb78f --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-behavior.spec.ts @@ -0,0 +1,57 @@ +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 { Project } from "../../../models/project"; + +test("Verify hovering an undefined variable swaps the tooltip to the render error instead of vanishing", async ({ + user, +}) => { + const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile( + project, + path.join(__dirname, "collection-for-global-environments.yaml"), + ); + const collection = new Collection("collection-for-global-environments"); + await environmentFlow.activate(collection, "ExampleA"); + + await httpRequestFlow.get("New Request"); + const tooltips = await variableSourceFlow.hoverBetweenVariables( + "exampleString", + "subenvA0", + "global-base", + "Failed to render environment variables", + ); + + expect(tooltips.first).toContain("subenvA0"); + expect(tooltips.second).toContain("Failed to render environment variables"); +}); + +test("Verify hovering the tooltip's own content keeps it visible", async ({ user }) => { + const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile( + project, + path.join(__dirname, "environment-cascade-collection.yaml"), + ); + const collection = new Collection("Environment Cascade"); + await environmentFlow.activate(collection, "Base Environment"); + + await httpRequestFlow.get("cascade request no folder"); + const stillVisible = await variableSourceFlow.hoverTooltipContent("cascadeVar"); + + expect(stillVisible).toBe(true); +}); diff --git a/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-open.spec.ts b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-open.spec.ts new file mode 100644 index 0000000000..4df8d8502b --- /dev/null +++ b/packages/insomnia-test/tests/environment/variable-source-live-preview/variable-source-tooltip-open.spec.ts @@ -0,0 +1,154 @@ +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 { Project } from "../../../models/project"; + +const cascadeFixture = path.join(__dirname, "environment-cascade-collection.yaml"); +const globalCollectionFixture = path.join(__dirname, "collection-for-global-environments.yaml"); +const globalEnvironmentFixture = path.join(__dirname, "global-environment.yaml"); +const projectEnvironmentSourceFixture = path.join(__dirname, "project-environment-source.yaml"); + +test("Verify the tooltip's Open inside the Manage Environments modal selects the source environment, and works again after selecting another", async ({ + user, +}) => { + const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile(project, cascadeFixture); + const collection = new Collection("Environment Cascade"); + await environmentFlow.activate(collection, "SubEnvA"); + + await httpRequestFlow.get("cascade request no folder"); + await variableSourceFlow.openManageEnvironmentsModal(); + const hovered = await variableSourceFlow.hoverVariableInModal("baseOnlyVar"); + await variableSourceFlow.openSourceFromTooltip(); + const firstOpen = await variableSourceFlow.getNavigation("environments-modal", "Base Environment"); + await variableSourceFlow.selectModalEnvironment("SubEnvA"); + await variableSourceFlow.hoverVariableInModal("baseOnlyVar"); + await variableSourceFlow.openSourceFromTooltip(); + const secondOpen = await variableSourceFlow.getNavigation("environments-modal", "Base Environment"); + + expect(hovered.tooltip).toContain("baseOnlyValue"); + expect(firstOpen.selectedEnvironmentRow).toBe(true); + expect(firstOpen.manageEnvironmentsModalCount).toBe(1); + expect(secondOpen.selectedEnvironmentRow).toBe(true); + expect(secondOpen.manageEnvironmentsModalCount).toBe(1); +}); + +test("Verify the tooltip's Open closes the Manage Environments modal when it navigates to a folder environment", async ({ + user, +}) => { + const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile(project, cascadeFixture); + const collection = new Collection("Environment Cascade"); + await environmentFlow.activate(collection, "SubEnvA"); + + await httpRequestFlow.get("cascade request"); + await variableSourceFlow.openManageEnvironmentsModal(); + const hovered = await variableSourceFlow.hoverVariableInModal("folderOverride"); + await variableSourceFlow.openSourceFromTooltip(); + await variableSourceFlow.waitForManageEnvironmentsModalClosed(); + const navigation = await variableSourceFlow.getNavigation("folder-pane"); + + expect(hovered.tooltip).toContain("Folder Environment : cascadeFolder"); + expect(navigation.url).toMatch(/debug\/request-group\//); + expect(navigation.environmentTabSelected).toBe(true); + expect(navigation.manageEnvironmentsModalCount).toBe(0); +}); + +test("Verify the tooltip's Open navigates to a project environment source from the Manage Environments modal and from the request body", async ({ + user, +}) => { + const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } = + user.flowManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile(project, globalCollectionFixture); + await importFlow.importFile(project, globalEnvironmentFixture); + const collection = new Collection("collection-for-global-environments"); + await variableSourceFlow.selectProjectEnvironment(collection, "global-environment"); + + await httpRequestFlow.get("Global Var Request"); + await variableSourceFlow.openManageEnvironmentsModal(); + await variableSourceFlow.selectModalEnvironment("ExampleA"); + const hoveredInModal = await variableSourceFlow.hoverVariableInModal("global-base"); + await variableSourceFlow.openSourceFromTooltip(); + await variableSourceFlow.waitForManageEnvironmentsModalClosed(); + const fromModal = await variableSourceFlow.getNavigation("environment-page", "Base Environment"); + + await httpRequestFlow.get("Global Var Request"); + const hoveredInBody = await variableSourceFlow.hoverVariable("global-base"); + await variableSourceFlow.openSourceFromTooltip(); + const fromBody = await variableSourceFlow.getNavigation("environment-page", "Base Environment"); + + expect(hoveredInModal.tooltip).toContain("Project Environment : Base Environment"); + expect(fromModal.url).toMatch(/\/environment/); + expect(fromModal.manageEnvironmentsModalCount).toBe(0); + expect(fromModal.selectedEnvironmentRow).toBe(true); + expect(hoveredInBody.tooltip).toContain("Project Environment : Base Environment"); + expect(fromBody.url).toMatch(/\/environment\/?$/); + expect(fromBody.selectedEnvironmentRow).toBe(true); +}); + +test("Verify the tooltip's Open selects the source environment on the project environment page", async ({ + user, +}) => { + const { workspaceFlow, importFlow, variableSourceFlow } = + user.flowManager; + const { workspacePage, environmentPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile(project, globalEnvironmentFixture); + await workspacePage.clickNode( + await workspacePage.resolveNode({ name: "global-environment" }), + ); + await environmentPage.selectEnvironment("New Environment"); + + const hovered = await variableSourceFlow.hoverEnvironmentPageVariable("global-base"); + await variableSourceFlow.openSourceFromTooltip(); + const navigation = await variableSourceFlow.getNavigation("environment-page", "Base Environment"); + + expect(hovered.tooltip).toContain("Project Environment : Base Environment"); + expect(navigation.selectedEnvironmentRow).toBe(true); +}); + +test("Verify the project environment page attributes its own environments to the project", async ({ + user, +}) => { + const { workspaceFlow, importFlow, variableSourceFlow } = user.flowManager; + const { workspacePage, environmentPage } = user.pageManager; + + const project = await workspaceFlow.createProject( + new Project(faker.string.alphanumeric(10), ProjectType.Local), + ); + await importFlow.importFile(project, projectEnvironmentSourceFixture); + await workspacePage.clickNode( + await workspacePage.resolveNode({ name: "project-environment-source" }), + ); + await environmentPage.selectEnvironment("Source Sub Environment"); + + const modal = await variableSourceFlow.inspectEnvironmentPageVariable( + "text_base", + "fromProjectBase", + ); + + expect(modal.source).toContain("Project Environment : Base Environment"); + expect(modal.preview).toBe("fromProjectBase"); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-client-certificate-update.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-client-certificate-update.spec.ts new file mode 100644 index 0000000000..5186319b0d --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-client-certificate-update.spec.ts @@ -0,0 +1,67 @@ +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 pre-request script reads and replaces the client certificate via insomnia.request.certificate", 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)), + ); + + await certificatesFlow.addClientCertificate(collection, { + host: "localhost", + cert: certPath("mtls-client-cert.pem"), + key: certPath("mtls-client-key.pem"), + }); + + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url, + preRequestScript: ` + console.log('original cert:', JSON.stringify(insomnia.request.certificate.cert)); + console.log('original key:', JSON.stringify(insomnia.request.certificate.key)); + insomnia.request.certificate.update({ + disabled: false, + key: { src: '${certPath("mtls-client-key.pem")}' }, + cert: { src: '${certPath("mtls-client-cert.pem")}' }, + passphrase: '', + }); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBe(200); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("Adding SSL PEM certificate"); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("Adding SSL KEY certificate"); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("original cert:"); +}); diff --git a/packages/insomnia-test/tests/http-request/pre-request-script-proxy-update.spec.ts b/packages/insomnia-test/tests/http-request/pre-request-script-proxy-update.spec.ts new file mode 100644 index 0000000000..a4c3183321 --- /dev/null +++ b/packages/insomnia-test/tests/http-request/pre-request-script-proxy-update.spec.ts @@ -0,0 +1,59 @@ +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 pre-request script reads the configured proxy and overrides it via insomnia.request.proxy", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow, preferencesFlow } = user.flowManager; + + await preferencesFlow.set({ + timeout: 5000, + proxyEnabled: true, + httpProxy: "localhost:1111", + httpsProxy: "localhost:2222", + noProxy: "http://a.com,https://b.com", + }); + + 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: ` + console.log(insomnia.request.proxy.getProxyUrl()); + insomnia.request.proxy.update({ + host: '127.0.0.1', + match: '', + port: 8888, + tunnel: false, + authenticate: false, + username: '', + password: '', + }); + `, + }); + const responseView = await httpRequestFlow.send(request); + + expect(responseView.statusCode).toBeUndefined(); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("localhost:1111"); + await expect + .poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT }) + .toContain("Trying 127.0.0.1:8888"); +}); diff --git a/packages/insomnia-test/tests/http-request/response-downloads.spec.ts b/packages/insomnia-test/tests/http-request/response-downloads.spec.ts new file mode 100644 index 0000000000..5a90cb3d4f --- /dev/null +++ b/packages/insomnia-test/tests/http-request/response-downloads.spec.ts @@ -0,0 +1,105 @@ +import fs from "node:fs"; +import os from "node:os"; +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 { ResponseExportOption } from "../../enums/response-export-option"; +import { DEFAULT_TIMEOUT, HTTP_SERVER, LONG_TIMEOUT, test } from "../../misc/fixtures"; +import { Collection } from "../../models/collection"; +import { Project } from "../../models/project"; + +const LARGE_RESPONSE_BYTES = 5 * 1024 * 1024; + +test("Verify exporting a small JSON response as raw, prettified and HTTP debug files", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const exportDir = fs.mkdtempSync(path.join(os.tmpdir(), "insomnia-export-")); + const rawPath = path.join(exportDir, "response-raw.json"); + const prettifiedPath = path.join(exportDir, "response-pretty.json"); + const debugPath = path.join(exportDir, "response-debug.txt"); + + 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 marker = faker.string.alphanumeric(10); + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post?marker=${marker}`, + }); + await httpRequestFlow.send(request); + await httpRequestFlow.exportResponse(ResponseExportOption.Raw, rawPath); + await httpRequestFlow.exportResponse(ResponseExportOption.Prettified, prettifiedPath); + await httpRequestFlow.exportResponse(ResponseExportOption.HttpDebug, debugPath); + + await expect + .poll(() => [rawPath, prettifiedPath, debugPath].every((file) => fs.existsSync(file) && fs.statSync(file).size > 0), { + timeout: DEFAULT_TIMEOUT, + }) + .toBe(true); + const raw = fs.readFileSync(rawPath, "utf8"); + const prettified = fs.readFileSync(prettifiedPath, "utf8"); + const debug = fs.readFileSync(debugPath, "utf8"); + fs.rmSync(exportDir, { recursive: true, force: true }); + + expect(JSON.parse(raw)).toMatchObject({ args: { marker } }); + expect(JSON.parse(prettified)).toEqual(JSON.parse(raw)); + expect(prettified.length).toBeGreaterThan(raw.length); + expect(debug).toContain("Content-Type"); + expect(debug).toContain(marker); +}); + +test("Verify a large JSON response can be exported raw or saved to file, and prettified export is disabled", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + + const exportDir = fs.mkdtempSync(path.join(os.tmpdir(), "insomnia-export-")); + const rawPath = path.join(exportDir, "response-large.json"); + const savedPath = path.join(exportDir, "response-saved.json"); + + 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}/large-json`, + }); + await httpRequestFlow.send(request); + const prettifiedState = await httpRequestFlow.getResponseExportOptionState( + ResponseExportOption.Prettified, + ); + await httpRequestFlow.exportResponse(ResponseExportOption.Raw, rawPath); + await httpRequestFlow.saveResponseToFile(savedPath); + + await expect + .poll(() => [rawPath, savedPath].every((file) => fs.existsSync(file) && fs.statSync(file).size > LARGE_RESPONSE_BYTES), { + timeout: LONG_TIMEOUT, + }) + .toBe(true); + const rawSize = fs.statSync(rawPath).size; + const savedHead = fs.readFileSync(savedPath).subarray(0, 16).toString("utf8"); + fs.rmSync(exportDir, { recursive: true, force: true }); + + expect(rawSize).toBeGreaterThan(LARGE_RESPONSE_BYTES); + expect(savedHead).toContain('{"items"'); + expect(prettifiedState.text).toContain("must be <5MB"); + expect(prettifiedState.disabled).toBe(true); +}); diff --git a/packages/insomnia-test/tests/template-tag/prompt-tag-caches-value-until-cleared.spec.ts b/packages/insomnia-test/tests/template-tag/prompt-tag-caches-value-until-cleared.spec.ts new file mode 100644 index 0000000000..b845c5215a --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/prompt-tag-caches-value-until-cleared.spec.ts @@ -0,0 +1,58 @@ +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"; + +test("Verify a prompt Tag Caches its Value Under the Storage Key Until it is Cleared", 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 label = faker.string.alpha(12); + const storageKey = faker.string.alphanumeric(10); + const promptTag = `{% prompt '${label}', 'Enter secret', '', '${storageKey}', true, false %}`; + const request = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + body: { mimeType: ContentType.Plain, text: promptTag }, + }); + + const firstValue = faker.string.alphanumeric(12); + const secondValue = faker.string.alphanumeric(12); + const previewBeforeSend = await templateTagFlow.get(promptTag); + const first = await templateTagFlow.sendAnsweringPrompt( + request, + label, + firstValue, + ); + const cached = await httpRequestFlow.send(request); + await templateTagFlow.clearCachedPrompt(promptTag); + const second = await templateTagFlow.sendAnsweringPrompt( + request, + label, + secondValue, + ); + + expect(previewBeforeSend.functionName).toBe("prompt"); + expect(first.promptInputType).toBe("password"); + expect(first.response.statusCode).toBe(200); + expect(first.response.body).toMatchObject({ data: firstValue }); + expect(cached.statusCode).toBe(200); + expect(cached.body).toMatchObject({ data: firstValue }); + expect(second.response.statusCode).toBe(200); + expect(second.response.body).toMatchObject({ data: secondValue }); +}); diff --git a/packages/insomnia-test/tests/template-tag/response-tag-trigger-behaviors.spec.ts b/packages/insomnia-test/tests/template-tag/response-tag-trigger-behaviors.spec.ts new file mode 100644 index 0000000000..d37cb504ac --- /dev/null +++ b/packages/insomnia-test/tests/template-tag/response-tag-trigger-behaviors.spec.ts @@ -0,0 +1,63 @@ +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"; + +test("Verify the response Tag's Trigger Behavior Controls When its Dependency Request is Resent", async ({ + user, +}) => { + const { workspaceFlow, httpRequestFlow } = user.flowManager; + const { page } = user; + + 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 marker = faker.string.alphanumeric(10); + const dependency = await httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + body: { mimeType: ContentType.JSON, text: JSON.stringify({ marker }) }, + }); + + const filter = `b64::${Buffer.from("$.json.marker").toString("base64")}::46b`; + const createDependent = (trigger: string, maxAge: number) => + httpRequestFlow.create(collection, { + name: faker.string.alphanumeric(10), + method: HttpMethod.Post, + url: `${HTTP_SERVER}/post`, + body: { + mimeType: ContentType.Plain, + text: `{% response 'body', '${dependency.id}', '${filter}', '${trigger}', ${maxAge} %}`, + }, + }); + const neverRequest = await createDependent("never", 60); + const noHistoryRequest = await createDependent("no-history", 60); + const whenExpiredRequest = await createDependent("when-expired", 0); + const alwaysRequest = await createDependent("always", 60); + + const neverFailure = httpRequestFlow.send(neverRequest); + await neverFailure.catch(() => {}); + await page.keyboard.press("Escape"); + const noHistory = await httpRequestFlow.send(noHistoryRequest); + const whenExpired = await httpRequestFlow.send(whenExpiredRequest); + const always = await httpRequestFlow.send(alwaysRequest); + + await expect(neverFailure).rejects.toThrow(/No responses for request/); + expect(noHistory.statusCode).toBe(200); + expect(noHistory.body).toMatchObject({ data: marker }); + expect(whenExpired.statusCode).toBe(200); + expect(whenExpired.body).toMatchObject({ data: marker }); + expect(always.statusCode).toBe(200); + expect(always.body).toMatchObject({ data: marker }); +});