mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 03:41:28 +00:00
184 lines
7.5 KiB
TypeScript
184 lines
7.5 KiB
TypeScript
import { expect, Page, test } from '../../../playwright';
|
|
import { openCollection, openRequestInFolder, sendRequest, setUrlEncoding } from '../../utils/page';
|
|
|
|
const COLLECTION = 'generate-code-encoding';
|
|
const FOLDER = 'requests';
|
|
|
|
test.describe('Send Request — every fixture, ON and OFF', () => {
|
|
const fixtures = [
|
|
// query-side
|
|
'query-spaces',
|
|
'query-preencoded',
|
|
'query-redirect-url',
|
|
'query-pipe',
|
|
'query-unicode',
|
|
'query-equals',
|
|
'query-email-plus',
|
|
'query-commas-colons',
|
|
'query-double-encode',
|
|
'query-hash',
|
|
'query-arrays',
|
|
'fragment-preserved',
|
|
// path-side
|
|
'path-spaces',
|
|
'path-brackets',
|
|
'path-unicode',
|
|
'path-idempotent',
|
|
'path-odata',
|
|
'path-fragment',
|
|
'path-issues-fragment',
|
|
'path-spa-route',
|
|
'oauth-callback-fragment',
|
|
// params:path
|
|
'params-path-odata',
|
|
'params-path-space',
|
|
'path-param-slash',
|
|
'path-param-hash',
|
|
'path-param-hash-trailing',
|
|
'path-param-space',
|
|
'path-param-ampersand',
|
|
'path-param-equals',
|
|
'path-param-plus',
|
|
'path-param-question',
|
|
'path-param-at',
|
|
'path-param-colon',
|
|
'path-param-comma',
|
|
'path-param-unicode',
|
|
'path-param-brackets',
|
|
'path-param-braces',
|
|
'path-param-pipe'
|
|
];
|
|
|
|
const expectEchoResponded = async (page: Page) => {
|
|
const texts = await page
|
|
.getByTestId('response-preview-container')
|
|
.locator('.CodeMirror-scroll')
|
|
.allInnerTexts();
|
|
// Echo server returns `{ "url": "/path/..." }`. Asserting the `"url":`
|
|
// marker is present is enough to confirm we hit the echo route (not a
|
|
// 404 / error page) without pinning the encoded byte-form, which is
|
|
// mode-dependent and the actual point of inspection here.
|
|
expect(texts.some((t: string) => t.includes('"url":'))).toBe(true);
|
|
};
|
|
|
|
for (const file of fixtures) {
|
|
test(`${file} — send with toggle ON then OFF`, async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, file);
|
|
|
|
// ON
|
|
await setUrlEncoding(page, true);
|
|
await sendRequest(page, 200);
|
|
await expectEchoResponded(page);
|
|
|
|
// OFF
|
|
await setUrlEncoding(page, false);
|
|
await sendRequest(page, 200);
|
|
await expectEchoResponded(page);
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Tests against the local Bruno echo server (`/api/echo/anything/*`) — covers
|
|
* the # encoding decision-tree scenarios from fixings/snippet-vs-sendrequest.md.
|
|
*
|
|
* The echo server returns the request shape (args/data/headers/method/url) in
|
|
* the JSON body, mimicking httpbin.org/anything. Each test sends and asserts
|
|
* the substring that proves the right URL reached the server. Both ON and OFF
|
|
* return 200 (the route matches any path); the distinction is in *what URL*
|
|
* the server saw.
|
|
*
|
|
* Fixtures used:
|
|
* - docs-fragment-external → Scenario 1: page anchor (#authentication)
|
|
* - path-issues-fragment → Scenario 4: issue tracker (/issues/#1234)
|
|
* - path-spa-route → Scenario 5: SPA hash route (/#/dashboard)
|
|
* - oauth-callback-fragment → Scenario 8: OAuth implicit-flow callback
|
|
*
|
|
* Switched from httpbin.org → local echo because the public httpbin was
|
|
* returning 502/503 under load and making this suite flaky.
|
|
*/
|
|
const expectEchoReceived = async (page: Page, expectedUrlSubstring: string) => {
|
|
const texts = await page
|
|
.getByTestId('response-preview-container')
|
|
.locator('.CodeMirror-scroll')
|
|
.allInnerTexts();
|
|
// /api/echo/anything/* returns `{ "url": "http://localhost:8081/api/echo/anything/..." }`.
|
|
// We assert the expected URL substring appears in the response — that
|
|
// confirms what the server actually received on the wire.
|
|
expect(texts.some((t: string) => t.includes(expectedUrlSubstring))).toBe(true);
|
|
};
|
|
|
|
// Negative-case helper. Asserts the response body does NOT contain the
|
|
// forbidden substring — used to prove that the fragment was stripped on wire
|
|
// for OFF-mode tests (otherwise an `includes` on the path-only prefix would
|
|
// pass even if Bruno wrongly leaked the fragment through).
|
|
const expectEchoDidNotReceive = async (page: Page, forbiddenSubstring: string) => {
|
|
const texts = await page
|
|
.getByTestId('response-preview-container')
|
|
.locator('.CodeMirror-scroll')
|
|
.allInnerTexts();
|
|
expect(texts.some((t: string) => t.includes(forbiddenSubstring))).toBe(false);
|
|
};
|
|
|
|
test.describe('Send Request — # encoding scenarios (local echo)', () => {
|
|
test('Scenario 1: page anchor — OFF strips #authentication on wire', async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'docs-fragment-external');
|
|
await setUrlEncoding(page, false);
|
|
await sendRequest(page, 200);
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/docs/api');
|
|
await expectEchoDidNotReceive(page, '#authentication');
|
|
});
|
|
|
|
test('Scenario 1 (ON variant): page anchor — # encoded as data reaches server', async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'docs-fragment-external');
|
|
await setUrlEncoding(page, true);
|
|
await sendRequest(page, 200);
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/docs/api#authentication');
|
|
});
|
|
|
|
test('Scenario 4a: issue tracker — OFF strips #1234 on wire', async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'path-issues-fragment');
|
|
await setUrlEncoding(page, false);
|
|
await sendRequest(page, 200);
|
|
// Fixture URL is /anything/issues#1234 (not /anything/issues/#1234 from
|
|
// the docs table) — we use a non-trailing-slash shape that worked across
|
|
// both the public httpbin (older runs) and the local echo (current).
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/issues');
|
|
await expectEchoDidNotReceive(page, '#1234');
|
|
});
|
|
|
|
test('Scenario 4b: issue tracker — ON encodes #1234, server sees full path', async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'path-issues-fragment');
|
|
await setUrlEncoding(page, true);
|
|
await sendRequest(page, 200);
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/issues#1234');
|
|
});
|
|
|
|
test('Scenario 5: SPA hash-route — OFF strips everything after #', async ({ pageWithUserData: page }) => {
|
|
// Fixture URL is /anything/spa#/dashboard/settings (added the /spa segment
|
|
// to keep the URL non-trailing-slash, same reason as Scenario 4a).
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'path-spa-route');
|
|
await setUrlEncoding(page, false);
|
|
await sendRequest(page, 200);
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/spa');
|
|
await expectEchoDidNotReceive(page, 'dashboard');
|
|
});
|
|
|
|
test('Scenario 8: OAuth callback — OFF strips token payload on wire', async ({ pageWithUserData: page }) => {
|
|
await openCollection(page, COLLECTION);
|
|
await openRequestInFolder(page, FOLDER, 'oauth-callback-fragment');
|
|
await setUrlEncoding(page, false);
|
|
await sendRequest(page, 200);
|
|
// OFF: fragment (incl. access_token) never reaches the server — that's the
|
|
// OAuth implicit-flow security property.
|
|
await expectEchoReceived(page, 'http://localhost:8081/api/echo/anything/callback');
|
|
await expectEchoDidNotReceive(page, 'access_token');
|
|
});
|
|
});
|