From 0b3c443e4e41fd4ed5bb969419467befea3ea932 Mon Sep 17 00:00:00 2001 From: Ryan Willis Date: Tue, 25 Mar 2025 09:08:53 -0700 Subject: [PATCH] fix: remove use of node headers to fix variable template usage (#8514) --- .../fixtures/header-templates.yaml | 51 +++++++++++++++++++ .../tests/smoke/header-templates.test.ts | 33 ++++++++++++ .../src/common/__tests__/sorting.test.ts | 11 ++++ packages/insomnia/src/common/sorting.ts | 14 ++--- packages/insomnia/src/network/network.ts | 11 ++-- 5 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 packages/insomnia-smoke-test/fixtures/header-templates.yaml create mode 100644 packages/insomnia-smoke-test/tests/smoke/header-templates.test.ts diff --git a/packages/insomnia-smoke-test/fixtures/header-templates.yaml b/packages/insomnia-smoke-test/fixtures/header-templates.yaml new file mode 100644 index 0000000000..105e6f28b4 --- /dev/null +++ b/packages/insomnia-smoke-test/fixtures/header-templates.yaml @@ -0,0 +1,51 @@ +type: collection.insomnia.rest/5.0 +name: headerTemplates +meta: + id: wrk_bbe65db250e94e31a7d88d8f779c9dd5 + created: 1741133210975 + modified: 1742840304001 +collection: + - url: "{{ _.baseUrl }}/pets/2" + name: pet2 + meta: + id: req_eaa7211a4c2e4fd492a38db5c7c926fd + created: 1741133214878 + modified: 1742840346014 + isPrivate: false + sortKey: -1741133214878 + method: GET + headers: + - name: User-Agent + value: insomnia/11.0.0-beta.3 + id: pair_b4cf2c942ed84dd28857d2ccfff55907 + disabled: true + - id: pair_a16a834b625d453183898c8896abfde7 + name: "{{ _.xHeaderExample }}" + value: baz + disabled: false + authentication: + type: none + settings: + renderRequestBody: true + encodeUrl: true + followRedirects: global + cookies: + send: true + store: true + rebuildPath: true +cookieJar: + name: Default Jar + meta: + id: jar_0e0f846c6042e206736fb4edf8d2b90ed218362d + created: 1741133210977 + modified: 1742840291561 +environments: + name: example + meta: + id: env_0e0f846c6042e206736fb4edf8d2b90ed218362d + created: 1741133210976 + modified: 1742840291562 + isPrivate: false + data: + xHeaderExample: X-Foo-Bar + baseUrl: http://localhost:4010 diff --git a/packages/insomnia-smoke-test/tests/smoke/header-templates.test.ts b/packages/insomnia-smoke-test/tests/smoke/header-templates.test.ts new file mode 100644 index 0000000000..82bb916eb6 --- /dev/null +++ b/packages/insomnia-smoke-test/tests/smoke/header-templates.test.ts @@ -0,0 +1,33 @@ +import { expect } from '@playwright/test'; + +import { loadFixture } from '../../playwright/paths'; +import { test } from '../../playwright/test'; + +test('can requests that contain templated header keys and values', async ({ app, page }) => { + test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms'); + + const statusTag = page.locator('[data-testid="response-status-tag"]:visible'); + const responsePaneContents = page.locator('[data-testid="response-pane"] >> [data-testid="CodeEditor"]:visible', { + has: page.locator('.CodeMirror-activeline'), + }); + + const collectionText = await loadFixture('header-templates.yaml'); + await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), collectionText); + + await page.getByLabel('Import').click(); + await page.locator('[data-test-id="import-from-clipboard"]').click(); + await page.getByRole('button', { name: 'Scan' }).click(); + await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click(); + await page.getByLabel('headerTemplates').click(); + + await page.getByLabel('Request Collection').getByTestId('pet2').press('Enter'); + + await page.getByRole('button', { name: 'Send' }).click(); + + await expect(statusTag).toContainText('200 OK'); + await page.getByRole('button', { name: 'Preview' }).click(); + await page.getByRole('menuitem', { name: 'Raw Data' }).click(); + await expect(responsePaneContents).toContainText('{"id":"2"}'); + await page.getByRole('tab', { name: 'Console' }).click(); + await expect(responsePaneContents).toContainText('X-Foo-Bar: baz'); +}); diff --git a/packages/insomnia/src/common/__tests__/sorting.test.ts b/packages/insomnia/src/common/__tests__/sorting.test.ts index 28e21f2b69..179f5c8c5e 100644 --- a/packages/insomnia/src/common/__tests__/sorting.test.ts +++ b/packages/insomnia/src/common/__tests__/sorting.test.ts @@ -18,6 +18,7 @@ import { SORT_TYPE_DESC, } from '../constants'; import { + ascendingFirstIndexStringSort, ascendingNumberSort, descendingNumberSort, metaSortKeySort, @@ -1033,4 +1034,14 @@ describe('Sorting methods', () => { expect(descendingNumberSort(2, 1)).toBe(-1); expect(descendingNumberSort(1, -2)).toBe(-1); }); + + it('sorts the first string value in an array', () => { + expect(ascendingFirstIndexStringSort(['a'], ['b'])).toBe(-1); + expect(ascendingFirstIndexStringSort(['b'], ['a'])).toBe(1); + expect(ascendingFirstIndexStringSort(['ab'], ['abb'])).toBe(-1); + expect(ascendingFirstIndexStringSort(['abb'], ['ab'])).toBe(1); + expect(ascendingFirstIndexStringSort(['Abb'], ['bbb'])).toBe(-1); + expect(ascendingFirstIndexStringSort(['bbb'], ['Abb'])).toBe(1); + expect(ascendingFirstIndexStringSort(['x'], ['x'])).toBe(0); + }); }); diff --git a/packages/insomnia/src/common/sorting.ts b/packages/insomnia/src/common/sorting.ts index 8d419bc2fc..cb8c4d7b8f 100644 --- a/packages/insomnia/src/common/sorting.ts +++ b/packages/insomnia/src/common/sorting.ts @@ -18,15 +18,15 @@ import { type SortableModel = Request | RequestGroup | GrpcRequest; type SortFunction = (a: SortableType, b: SortableType) => number; -export const ascendingNameSort: SortFunction<{name: string}> = (a, b) => { +export const ascendingNameSort: SortFunction<{ name: string }> = (a, b) => { return a.name.localeCompare(b.name); }; -export const descendingNameSort: SortFunction<{name: string}> = (a, b) => { +export const descendingNameSort: SortFunction<{ name: string }> = (a, b) => { return b.name.localeCompare(a.name); }; -export const createdFirstSort: SortFunction<{created: number}> = (a, b) => { +export const createdFirstSort: SortFunction<{ created: number }> = (a, b) => { if (a.created === b.created) { return 0; } @@ -34,7 +34,7 @@ export const createdFirstSort: SortFunction<{created: number}> = (a, b) => { return a.created < b.created ? -1 : 1; }; -export const createdLastSort: SortFunction<{created: number}> = (a, b) => { +export const createdLastSort: SortFunction<{ created: number }> = (a, b) => { if (a.created === b.created) { return 0; } @@ -42,7 +42,7 @@ export const createdLastSort: SortFunction<{created: number}> = (a, b) => { return a.created > b.created ? -1 : 1; }; -export const ascendingModifiedSort: SortFunction<{lastModifiedTimestamp: number}> = (a, b) => { +export const ascendingModifiedSort: SortFunction<{ lastModifiedTimestamp: number }> = (a, b) => { if (a.lastModifiedTimestamp === b.lastModifiedTimestamp) { return 0; } @@ -50,7 +50,7 @@ export const ascendingModifiedSort: SortFunction<{lastModifiedTimestamp: number} return a.lastModifiedTimestamp < b.lastModifiedTimestamp ? -1 : 1; }; -export const descendingModifiedSort: SortFunction<{lastModifiedTimestamp: number}> = (a, b) => { +export const descendingModifiedSort: SortFunction<{ lastModifiedTimestamp: number }> = (a, b) => { if (a.lastModifiedTimestamp === b.lastModifiedTimestamp) { return 0; } @@ -125,6 +125,8 @@ export const descendingNumberSort: SortFunction = (a, b) => { return ascendingNumberSort(b, a); }; +export const ascendingFirstIndexStringSort: SortFunction = (a, b) => a[0].localeCompare(b[0]); + export const sortMethodMap = { [SORT_NAME_ASC]: ascendingNameSort, [SORT_NAME_DESC]: descendingNameSort, diff --git a/packages/insomnia/src/network/network.ts b/packages/insomnia/src/network/network.ts index 54a02b1967..1b50626856 100644 --- a/packages/insomnia/src/network/network.ts +++ b/packages/insomnia/src/network/network.ts @@ -16,6 +16,7 @@ import { import { getRenderedRequestAndContext, } from '../common/render'; +import { ascendingFirstIndexStringSort } from '../common/sorting'; import type { HeaderResult, ResponsePatch, ResponseTimelineEntry } from '../main/network/libcurl-promise'; import * as models from '../models'; import type { CaCertificate } from '../models/ca-certificate'; @@ -66,7 +67,7 @@ export const getOrInheritAuthentication = ({ request, requestGroups }: { request return { type: 'none' }; }; export function getOrInheritHeaders({ request, requestGroups }: { request: Pick; requestGroups: Pick[] }): RequestHeader[] { - const httpHeaders = new Headers(); + const httpHeaders = new Map(); const originalCaseMap = new Map(); // parent folders, then child folders, then request const headerContexts = [...requestGroups.reverse(), request]; @@ -84,9 +85,13 @@ export function getOrInheritHeaders({ request, requestGroups }: { request: Pick< return; } // appending will join matching header values with a comma - httpHeaders.append(normalizedCase, value); + if (httpHeaders.has(normalizedCase)) { + httpHeaders.set(normalizedCase, `${httpHeaders.get(normalizedCase)}, ${value}`); + return; + } + httpHeaders.set(normalizedCase, value); }); - return Array.from(httpHeaders.entries()).map(([name, value]) => ({ name: originalCaseMap.get(name)!, value })); + return Array.from(httpHeaders.entries()).sort(ascendingFirstIndexStringSort).map(([name, value]) => ({ name: originalCaseMap.get(name)!, value })); } // (only used for getOAuth2 token) Intended to gather all required database objects and initialize ids export const fetchRequestGroupData = async (requestGroupId: string) => {