mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-04 01:48:33 +00:00
Compare commits
6 Commits
dependabot
...
bugfix/inc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa1498e2a8 | ||
|
|
045141efaf | ||
|
|
c997b91698 | ||
|
|
986d5b0b2a | ||
|
|
a2a521477a | ||
|
|
8e70adcbf9 |
2
.github/workflows/npm-bru-cli.yml
vendored
2
.github/workflows/npm-bru-cli.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: v22.11.x
|
||||
- name: Install dependencies
|
||||
|
||||
1
package-lock.json
generated
1
package-lock.json
generated
@@ -31986,6 +31986,7 @@
|
||||
"cheerio": "^1.0.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"json-query": "^2.2.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "3.3.8",
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as jsonlint from '@prantlf/jsonlint';
|
||||
import { JSHINT } from 'jshint';
|
||||
import stripJsonComments from 'strip-json-comments';
|
||||
import { getAllVariables } from 'utils/collections';
|
||||
import CustomSearch from './CustomSearch';
|
||||
import CodeMirrorSearch from 'components/CodeMirrorSearch';
|
||||
|
||||
const CodeMirror = require('codemirror');
|
||||
window.jsonlint = jsonlint;
|
||||
@@ -267,7 +267,7 @@ export default class CodeEditor extends React.Component {
|
||||
font={this.props.font}
|
||||
fontSize={this.props.fontSize}
|
||||
>
|
||||
<CustomSearch
|
||||
<CodeMirrorSearch
|
||||
visible={this.state.searchBarVisible}
|
||||
editor={this.editor}
|
||||
onClose={() => this.setState({ searchBarVisible: false })}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { IconRegex, IconArrowUp, IconArrowDown, IconX, IconLetterCase, IconLetterW } from '@tabler/icons';
|
||||
import ToolHint from 'components/ToolHint';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -9,7 +8,7 @@ function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');
|
||||
}
|
||||
|
||||
const CustomSearch = ({ visible, editor, onClose }) => {
|
||||
const CodeMirrorSearch = ({ visible, editor, onClose }) => {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [regex, setRegex] = useState(false);
|
||||
const [caseSensitive, setCaseSensitive] = useState(false);
|
||||
@@ -199,4 +198,4 @@ const CustomSearch = ({ visible, editor, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomSearch;
|
||||
export default CodeMirrorSearch;
|
||||
@@ -10,7 +10,6 @@ import { findCollectionByItemUid, getGlobalEnvironmentVariables } from 'utils/co
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { generateSnippet } from '../utils/snippet-generator';
|
||||
|
||||
const CodeView = ({ language, item }) => {
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
@@ -33,7 +32,12 @@ const CodeView = ({ language, item }) => {
|
||||
}, [collectionOriginal, globalEnvironments, activeGlobalEnvironmentUid]);
|
||||
|
||||
const snippet = useMemo(() => {
|
||||
return generateSnippet({ language, item, collection, shouldInterpolate: generateCodePrefs.shouldInterpolate });
|
||||
return generateSnippet({
|
||||
language,
|
||||
item,
|
||||
collection,
|
||||
shouldInterpolate: generateCodePrefs.shouldInterpolate
|
||||
});
|
||||
}, [language, item, collection, generateCodePrefs.shouldInterpolate]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import get from 'lodash/get';
|
||||
import filter from 'lodash/filter';
|
||||
import { Inspector } from 'react-inspector';
|
||||
import { Inspector, chromeDark, chromeLight } from 'react-inspector';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { findEnvironmentInCollection, maskInputValue } from 'utils/collections';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
@@ -84,9 +84,12 @@ const RuntimeVariables = ({ collection, theme }) => {
|
||||
};
|
||||
|
||||
const VariablesEditor = ({ collection }) => {
|
||||
const { storedTheme } = useTheme();
|
||||
const { displayedTheme, theme } = useTheme();
|
||||
|
||||
const reactInspectorTheme = storedTheme === 'light' ? 'chromeLight' : 'chromeDark';
|
||||
const reactInspectorTheme
|
||||
= displayedTheme === 'light'
|
||||
? { ...chromeLight, OBJECT_VALUE_STRING_COLOR: theme.variables.runtime.color }
|
||||
: { ...chromeDark, OBJECT_VALUE_STRING_COLOR: theme.variables.runtime.color };
|
||||
|
||||
return (
|
||||
<StyledWrapper className="px-4 py-4 overflow-auto">
|
||||
|
||||
@@ -32,6 +32,10 @@ const darkTheme = {
|
||||
|
||||
name: {
|
||||
color: '#569cd6'
|
||||
},
|
||||
|
||||
runtime: {
|
||||
color: 'rgb(255, 255, 255)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -36,6 +36,10 @@ const lightTheme = {
|
||||
|
||||
name: {
|
||||
color: '#546de5'
|
||||
},
|
||||
|
||||
runtime: {
|
||||
color: 'rgb(0, 0, 0)'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -131,9 +131,16 @@ const createPostData = (body) => {
|
||||
};
|
||||
|
||||
export const buildHarRequest = ({ request, headers }) => {
|
||||
// NOTE:
|
||||
// This is just a safety check.
|
||||
// The interpolateUrlPathParams method validates the url, but it does not throw
|
||||
if (!URL.canParse(request.url)) {
|
||||
throw new Error('invalid request url');
|
||||
}
|
||||
|
||||
return {
|
||||
method: request.method,
|
||||
url: encodeURI(request.url),
|
||||
url: request.url,
|
||||
httpVersion: 'HTTP/1.1',
|
||||
cookies: [],
|
||||
headers: createHeaders(request, headers),
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"cheerio": "^1.0.0",
|
||||
"crypto-js": "^4.2.0",
|
||||
"json-query": "^2.2.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "3.3.8",
|
||||
|
||||
@@ -33,6 +33,7 @@ const NodeVault = require('node-vault');
|
||||
const xml2js = require('xml2js');
|
||||
const cheerio = require('cheerio');
|
||||
const tv4 = require('tv4');
|
||||
const jsonwebtoken = require('jsonwebtoken');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
|
||||
class ScriptRuntime {
|
||||
@@ -185,6 +186,7 @@ class ScriptRuntime {
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
xml2js: xml2js,
|
||||
jsonwebtoken,
|
||||
cheerio,
|
||||
tv4,
|
||||
...whitelistedModules,
|
||||
@@ -354,6 +356,7 @@ class ScriptRuntime {
|
||||
'node-fetch': fetch,
|
||||
'crypto-js': CryptoJS,
|
||||
'xml2js': xml2js,
|
||||
jsonwebtoken,
|
||||
cheerio,
|
||||
tv4,
|
||||
...whitelistedModules,
|
||||
|
||||
@@ -35,6 +35,7 @@ const NodeVault = require('node-vault');
|
||||
const xml2js = require('xml2js');
|
||||
const cheerio = require('cheerio');
|
||||
const tv4 = require('tv4');
|
||||
const jsonwebtoken = require('jsonwebtoken');
|
||||
const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
|
||||
|
||||
class TestRuntime {
|
||||
@@ -103,7 +104,8 @@ class TestRuntime {
|
||||
res,
|
||||
expect: chai.expect,
|
||||
assert: chai.assert,
|
||||
__brunoTestResults: __brunoTestResults
|
||||
__brunoTestResults: __brunoTestResults,
|
||||
jwt: jsonwebtoken
|
||||
};
|
||||
|
||||
if (onConsoleLog && typeof onConsoleLog === 'function') {
|
||||
@@ -176,6 +178,7 @@ class TestRuntime {
|
||||
'xml2js': xml2js,
|
||||
cheerio,
|
||||
tv4,
|
||||
'jsonwebtoken': jsonwebtoken,
|
||||
...whitelistedModules,
|
||||
fs: allowScriptFilesystemAccess ? fs : undefined,
|
||||
'node-vault': NodeVault
|
||||
|
||||
@@ -2,12 +2,14 @@ const addAxiosShimToContext = require('./axios');
|
||||
const addNanoidShimToContext = require('./nanoid');
|
||||
const addPathShimToContext = require('./path');
|
||||
const addUuidShimToContext = require('./uuid');
|
||||
const addJwtShimToContext = require('./jwt');
|
||||
|
||||
const addLibraryShimsToContext = async (vm) => {
|
||||
await addNanoidShimToContext(vm);
|
||||
await addAxiosShimToContext(vm);
|
||||
await addUuidShimToContext(vm);
|
||||
await addPathShimToContext(vm);
|
||||
await addJwtShimToContext(vm);
|
||||
};
|
||||
|
||||
module.exports = addLibraryShimsToContext;
|
||||
|
||||
181
packages/bruno-js/src/sandbox/quickjs/shims/lib/jwt.js
Normal file
181
packages/bruno-js/src/sandbox/quickjs/shims/lib/jwt.js
Normal file
@@ -0,0 +1,181 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { marshallToVm, invokeFunction } = require('../../utils');
|
||||
|
||||
const addJwtShimToContext = async (vm) => {
|
||||
// --- sign ---
|
||||
const _jwtSign = vm.newFunction('sign', function (payload, secret, options, callback) {
|
||||
const nativePayload = vm.dump(payload);
|
||||
const nativeSecret = vm.dump(secret);
|
||||
|
||||
let nativeOptions;
|
||||
let callbackHandle = callback;
|
||||
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
||||
if (optionsType === 'function') {
|
||||
callbackHandle = options;
|
||||
nativeOptions = undefined;
|
||||
} else if (optionsType === 'object' && options !== null) {
|
||||
nativeOptions = vm.dump(options);
|
||||
}
|
||||
|
||||
// If a callback is provided
|
||||
if (callbackHandle && vm.typeof(callbackHandle) === 'function') {
|
||||
let tokenResult;
|
||||
let hostError;
|
||||
try {
|
||||
tokenResult = nativeOptions
|
||||
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
|
||||
: jwt.sign(nativePayload, nativeSecret);
|
||||
} catch (err) {
|
||||
hostError = err;
|
||||
}
|
||||
|
||||
try {
|
||||
if (hostError) {
|
||||
const errVm = vm.newError(hostError.message || String(hostError));
|
||||
invokeFunction(vm, callbackHandle, [errVm, vm.undefined])
|
||||
.catch((e) => {
|
||||
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
|
||||
})
|
||||
.finally(() => {
|
||||
errVm.dispose();
|
||||
callbackHandle.dispose();
|
||||
});
|
||||
} else {
|
||||
const tokenVm = marshallToVm(String(tokenResult), vm);
|
||||
invokeFunction(vm, callbackHandle, [vm.null, tokenVm])
|
||||
.catch((e) => {
|
||||
console.warn('[JWT SHIM][sign.cb] callback invocation error:', e);
|
||||
})
|
||||
.finally(() => {
|
||||
tokenVm.dispose();
|
||||
callbackHandle.dispose();
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[JWT SHIM][sign.cb] unexpected error:', e);
|
||||
callbackHandle.dispose();
|
||||
}
|
||||
|
||||
return vm.undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = nativeOptions
|
||||
? jwt.sign(nativePayload, nativeSecret, nativeOptions)
|
||||
: jwt.sign(nativePayload, nativeSecret);
|
||||
return marshallToVm(token, vm);
|
||||
} catch (err) {
|
||||
throw vm.newError(err.message || String(err));
|
||||
}
|
||||
});
|
||||
|
||||
vm.setProp(vm.global, '__bruno__jwt__sign', _jwtSign);
|
||||
_jwtSign.dispose();
|
||||
|
||||
// --- verify ---
|
||||
const _jwtVerify = vm.newFunction('verify', function (token, secret, options, callback) {
|
||||
const nativeToken = vm.dump(token);
|
||||
const nativeSecret = vm.dump(secret);
|
||||
|
||||
let nativeOptions;
|
||||
let actualCallback = callback;
|
||||
|
||||
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
||||
if (optionsType === 'function') {
|
||||
actualCallback = options;
|
||||
nativeOptions = undefined;
|
||||
} else if (optionsType === 'object' && options !== null) {
|
||||
nativeOptions = vm.dump(options);
|
||||
}
|
||||
|
||||
if (actualCallback && vm.typeof(actualCallback) === 'function') {
|
||||
let decodedResult;
|
||||
let hostError;
|
||||
try {
|
||||
decodedResult = nativeOptions
|
||||
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
|
||||
: jwt.verify(nativeToken, nativeSecret);
|
||||
} catch (err) {
|
||||
hostError = err;
|
||||
}
|
||||
|
||||
try {
|
||||
if (hostError) {
|
||||
const vmErr = vm.newError(hostError.message || String(hostError));
|
||||
invokeFunction(vm, actualCallback, [vmErr, vm.undefined])
|
||||
.catch((e) => {
|
||||
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
|
||||
})
|
||||
.finally(() => {
|
||||
vmErr.dispose();
|
||||
actualCallback.dispose();
|
||||
});
|
||||
} else {
|
||||
const vmNull = vm.null;
|
||||
const vmDecoded = marshallToVm(decodedResult, vm);
|
||||
invokeFunction(vm, actualCallback, [vmNull, vmDecoded])
|
||||
.catch((e) => {
|
||||
console.warn('[JWT SHIM][verify.cb] callback invocation error:', e);
|
||||
})
|
||||
.finally(() => {
|
||||
vmDecoded.dispose();
|
||||
actualCallback.dispose();
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[JWT SHIM][verify.cb] unexpected error:', e);
|
||||
actualCallback.dispose();
|
||||
}
|
||||
|
||||
return vm.undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = nativeOptions
|
||||
? jwt.verify(nativeToken, nativeSecret, nativeOptions)
|
||||
: jwt.verify(nativeToken, nativeSecret);
|
||||
return marshallToVm(decoded, vm);
|
||||
} catch (err) {
|
||||
throw vm.newError(err.message || String(err));
|
||||
}
|
||||
});
|
||||
|
||||
vm.setProp(vm.global, '__bruno__jwt__verify', _jwtVerify);
|
||||
_jwtVerify.dispose();
|
||||
|
||||
// --- decode ---
|
||||
const _jwtDecode = vm.newFunction('decode', function (token, options) {
|
||||
const nativeToken = vm.dump(token);
|
||||
|
||||
let nativeOptions;
|
||||
const optionsType = options === undefined ? 'undefined' : vm.typeof(options);
|
||||
if (optionsType === 'object' && options !== null) {
|
||||
nativeOptions = vm.dump(options);
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = nativeOptions
|
||||
? jwt.decode(nativeToken, nativeOptions)
|
||||
: jwt.decode(nativeToken);
|
||||
return marshallToVm(decoded, vm);
|
||||
} catch (err) {
|
||||
throw vm.newError(err.message || String(err));
|
||||
}
|
||||
});
|
||||
|
||||
vm.setProp(vm.global, '__bruno__jwt__decode', _jwtDecode);
|
||||
_jwtDecode.dispose();
|
||||
|
||||
vm.evalCode(`
|
||||
globalThis.jwt = {};
|
||||
globalThis.jwt.sign = globalThis.__bruno__jwt__sign;
|
||||
globalThis.jwt.verify = globalThis.__bruno__jwt__verify;
|
||||
globalThis.jwt.decode = globalThis.__bruno__jwt__decode;
|
||||
globalThis.requireObject = {
|
||||
...globalThis.requireObject,
|
||||
'jsonwebtoken': globalThis.jwt,
|
||||
};
|
||||
`);
|
||||
};
|
||||
|
||||
module.exports = addJwtShimToContext;
|
||||
@@ -30,6 +30,54 @@ const marshallToVm = (value, vm) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invokes a QuickJS function handle.
|
||||
* - Returns a Promise
|
||||
*
|
||||
* @param {Object} vm - QuickJS VM instance
|
||||
* @param {QuickJSHandle} quickFn - A QuickJS function handle
|
||||
* @param {Array} args - Arguments to pass to the function
|
||||
* @returns {Promise<any>} - The result as a Promise
|
||||
*/
|
||||
async function invokeFunction(vm, quickFn, args = []) {
|
||||
if (vm.typeof(quickFn) !== 'function') {
|
||||
throw new TypeError('Target is not a QuickJS function');
|
||||
}
|
||||
|
||||
const result = vm.callFunction(quickFn, vm.global, ...args);
|
||||
|
||||
if (result.error) {
|
||||
const error = vm.dump(result.error);
|
||||
result.error.dispose();
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Check if the result is a QuickJS Promise handle (async functions)
|
||||
if (vm.typeof(result.value) === 'object' && result.value.constructor && vm.typeof(result.value.constructor) === 'function') {
|
||||
try {
|
||||
const promiseHandle = vm.unwrapResult(result);
|
||||
const resolvedResult = await vm.resolvePromise(promiseHandle);
|
||||
promiseHandle.dispose();
|
||||
const resolvedHandle = vm.unwrapResult(resolvedResult);
|
||||
const value = vm.dump(resolvedHandle);
|
||||
resolvedHandle.dispose();
|
||||
return Promise.resolve(value);
|
||||
} catch (promiseError) {
|
||||
// If it's not a valid Promise, throw an error
|
||||
result.value.dispose();
|
||||
throw new Error(`Invalid Promise handle: ${promiseError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const value = vm.dump(result.value);
|
||||
result.value.dispose();
|
||||
|
||||
return (value && typeof value.then === 'function')
|
||||
? value
|
||||
: Promise.resolve(value);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
marshallToVm
|
||||
marshallToVm,
|
||||
invokeFunction
|
||||
};
|
||||
|
||||
@@ -22,4 +22,6 @@ test('Should verify all support links with correct URL in preference > Support t
|
||||
|
||||
const locator_documentation = page.getByRole('link', { name: 'Documentation', exact: true });
|
||||
expect(await locator_documentation.getAttribute('href')).toEqual('https://docs.usebruno.com');
|
||||
});
|
||||
|
||||
await page.locator('[data-test-id="modal-close-button"]').click();
|
||||
});
|
||||
|
||||
100
tests/request/encoding/curl-encoding.spec.ts
Normal file
100
tests/request/encoding/curl-encoding.spec.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { test, expect } from '../../../playwright';
|
||||
import { closeAllCollections } from '../../utils/page';
|
||||
|
||||
test.describe('Code Generation URL Encoding', () => {
|
||||
test.afterEach(async ({ pageWithUserData: page }) => {
|
||||
try {
|
||||
const modalCloseButton = page.locator('[data-test-id="modal-close-button"]');
|
||||
if (await modalCloseButton.isVisible()) {
|
||||
await modalCloseButton.click();
|
||||
await modalCloseButton.waitFor({ state: 'hidden' });
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('Should generate code with proper URL encoding for unencoded input', async ({
|
||||
pageWithUserData: page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
await page.locator('.dropdown-icon').click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
|
||||
await page.getByLabel('Name').fill('unencoded-test-collection');
|
||||
await page.getByLabel('Location').fill(await createTmpDir('unencoded-test-collection'));
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
|
||||
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'unencoded-test-collection' })).toBeVisible();
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'unencoded-test-collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await page.locator('#create-new-tab').getByRole('img').click();
|
||||
await page.getByPlaceholder('Request Name').fill('unencoded-request');
|
||||
await page.locator('#new-request-url .CodeMirror').click();
|
||||
await page.locator('textarea').fill('http://base.source?name=John Doe');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'unencoded-request' })).toBeVisible();
|
||||
|
||||
await page.locator('.collection-item-name').filter({ hasText: 'unencoded-request' }).click();
|
||||
|
||||
await page.locator('#send-request .infotip').first().click();
|
||||
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
await expect(page.getByRole('dialog').locator('.bruno-modal-header-title')).toContainText('Generate Code');
|
||||
|
||||
const codeEditor = page.locator('.editor-content .CodeMirror').first();
|
||||
await expect(codeEditor).toBeVisible();
|
||||
|
||||
const generatedCode = await codeEditor.textContent();
|
||||
|
||||
expect(generatedCode).toContain('http://base.source/?name=John%20Doe');
|
||||
|
||||
await page.locator('[data-test-id="modal-close-button"]').click();
|
||||
|
||||
await page.locator('[data-test-id="modal-close-button"]').waitFor({ state: 'hidden' });
|
||||
});
|
||||
|
||||
test('Should generate code with proper URL encoding for encoded input', async ({
|
||||
pageWithUserData: page,
|
||||
createTmpDir
|
||||
}) => {
|
||||
await page.locator('.dropdown-icon').click();
|
||||
await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
|
||||
await page.getByLabel('Name').fill('encoded-test-collection');
|
||||
await page.getByLabel('Location').fill(await createTmpDir('encoded-test-collection'));
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
|
||||
await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'encoded-test-collection' })).toBeVisible();
|
||||
await page.locator('#sidebar-collection-name').filter({ hasText: 'encoded-test-collection' }).click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
await page.locator('#create-new-tab').getByRole('img').click();
|
||||
await page.getByPlaceholder('Request Name').fill('encoded-request');
|
||||
await page.locator('#new-request-url .CodeMirror').click();
|
||||
await page.locator('textarea').fill('http://base.source?name=John%20Doe');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page.locator('.collection-item-name').filter({ hasText: 'encoded-request' })).toBeVisible();
|
||||
|
||||
await page.locator('.collection-item-name').filter({ hasText: 'encoded-request' }).click();
|
||||
|
||||
await page.locator('#send-request .infotip').first().click();
|
||||
|
||||
await expect(page.getByRole('dialog')).toBeVisible();
|
||||
await expect(page.getByRole('dialog').locator('.bruno-modal-header-title')).toContainText('Generate Code');
|
||||
|
||||
const codeEditor = page.locator('.editor-content .CodeMirror').first();
|
||||
await expect(codeEditor).toBeVisible();
|
||||
|
||||
const generatedCode = await codeEditor.textContent();
|
||||
|
||||
expect(generatedCode).toContain('http://base.source/?name=John%20Doe');
|
||||
|
||||
await page.locator('[data-test-id="modal-close-button"]').click();
|
||||
|
||||
await page.locator('[data-test-id="modal-close-button"]').waitFor({ state: 'hidden' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "jsonwebtoken",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
meta {
|
||||
name: decode
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const testPayload = {
|
||||
userId: 456,
|
||||
username: 'decodeuser',
|
||||
role: 'user',
|
||||
iat: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
const secret = bru.getEnvVar('secret') || 'test-secret-key';
|
||||
const testToken = jwt.sign(testPayload, secret, { algorithm: 'HS256', expiresIn: '1h' });
|
||||
|
||||
try {
|
||||
console.log('Testing JWT decoding...');
|
||||
console.log('Test token:', testToken);
|
||||
|
||||
const decoded = jwt.decode(testToken);
|
||||
|
||||
bru.setEnvVar('decoded_payload', JSON.stringify(decoded));
|
||||
|
||||
} catch (error) {
|
||||
console.error('JWT decoding failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("Decoded payload should exist", function() {
|
||||
const decodedPayload = bru.getEnvVar('decoded_payload');
|
||||
expect(decodedPayload).to.exist;
|
||||
});
|
||||
|
||||
test("Decoded payload should contain correct user data", function() {
|
||||
const decodedPayload = JSON.parse(bru.getEnvVar('decoded_payload'));
|
||||
|
||||
expect(decodedPayload.userId).to.equal(456);
|
||||
expect(decodedPayload.username).to.equal('decodeuser');
|
||||
expect(decodedPayload.role).to.equal('user');
|
||||
});
|
||||
|
||||
test("Decoded payload should have timestamp fields", function() {
|
||||
const decodedPayload = JSON.parse(bru.getEnvVar('decoded_payload'));
|
||||
|
||||
expect(decodedPayload.iat).to.exist;
|
||||
expect(decodedPayload.exp).to.exist;
|
||||
expect(typeof decodedPayload.iat).to.equal('number');
|
||||
expect(typeof decodedPayload.exp).to.equal('number');
|
||||
});
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: decode
|
||||
seq: 3
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
vars {
|
||||
host: http://httpfaker.org
|
||||
secret: my-secret-key
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: sign
|
||||
seq: 1
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
meta {
|
||||
name: sign with callback err
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
tests {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const HS_SECRET = 'supersecret';
|
||||
|
||||
/**
|
||||
* Helper that calls jwt.sign **with a callback** and resolves/rejects
|
||||
* based on the callback's (err, token) — so tests can `await` it.
|
||||
*/
|
||||
function signViaCallback(payload, secret, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.sign(payload, secret, options, (err, token) => {
|
||||
if (err) return reject(err);
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
ERROR TESTS — jwt.sign should call callback with `err`
|
||||
============================================================ */
|
||||
|
||||
test('ERROR (callback) — missing secret for HS256', async function () {
|
||||
try {
|
||||
await signViaCallback({ sub: 'no_secret' }, undefined, { algorithm: 'HS256' });
|
||||
throw new Error('Expected jwt.sign to error via callback');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/secret|private key must have a value/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('ERROR (callback) — invalid expiresIn format', async function () {
|
||||
try {
|
||||
await signViaCallback({ sub: 'bad_exp' }, HS_SECRET, { expiresIn: 'not-a-time' });
|
||||
throw new Error('Expected jwt.sign to error via callback');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/expiresIn/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('ERROR (callback) — unsupported/invalid algorithm', async function () {
|
||||
try {
|
||||
await signViaCallback({ sub: 'bad_alg' }, HS_SECRET, { algorithm: 'FOO256' });
|
||||
throw new Error('Expected jwt.sign to error via callback');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/algorithm/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('CONTROL (callback) — succeeds when options are valid', async function () {
|
||||
const token = await jwt.sign({ sub: 'ok' }, HS_SECRET, { algorithm: 'HS256', expiresIn: '10m' });
|
||||
expect(token).to.be.a('string');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
meta {
|
||||
name: sign with callback token
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
tests {
|
||||
const jwt = require('jsonwebtoken');
|
||||
const HS_SECRET = 'supersecret';
|
||||
|
||||
const payload = { sub: 'user123' };
|
||||
|
||||
function once(fn) {
|
||||
let called = false;
|
||||
return (...args) => {
|
||||
if (!called) {
|
||||
called = true;
|
||||
fn(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function signAsync(payload, secret, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.sign(payload, secret, options, (err, token) => {
|
||||
if (err) reject(err);
|
||||
else resolve(token);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 1. Named Normal Callback
|
||||
// ------------------------------------------------------------
|
||||
test('sign — named normal callback', function () {
|
||||
function signCallback(err, token) {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
// Verify token to ensure correctness
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log('Named callback signed token:', token);
|
||||
}
|
||||
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256', expiresIn: '15m' }, signCallback);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 2. Anonymous Callback
|
||||
// ------------------------------------------------------------
|
||||
test('sign — anonymous callback', function () {
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, function (err, token) {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log('Anonymous callback signed token:', token);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 3. Arrow Function Callback
|
||||
// ------------------------------------------------------------
|
||||
test('sign — arrow function callback', function () {
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, (err, token) => {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log('Arrow callback signed token:', token);
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 4. Bound Method Callback
|
||||
// ------------------------------------------------------------
|
||||
test('sign — bound method callback', function () {
|
||||
const signer = {
|
||||
prefix: '[SIGN]',
|
||||
done(err, token) {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log(this.prefix, 'Bound callback signed token:', token);
|
||||
},
|
||||
};
|
||||
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, signer.done.bind(signer));
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 5. Higher-Order Callback
|
||||
// ------------------------------------------------------------
|
||||
function makeSignCallback(label) {
|
||||
return (err, token) => {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log(label, 'Higher-order callback signed token:', token);
|
||||
};
|
||||
}
|
||||
|
||||
test('sign — higher-order callback', function () {
|
||||
const cb = makeSignCallback('[CUSTOM LABEL]');
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, cb);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 6. Once-Wrapped Callback
|
||||
// ------------------------------------------------------------
|
||||
test('sign — once-wrapped callback', function () {
|
||||
const cb = once((err, token) => {
|
||||
expect(err).to.be.null;
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log('Once callback executed and signed token:', token);
|
||||
});
|
||||
|
||||
jwt.sign(payload, HS_SECRET, { algorithm: 'HS256' }, cb);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// 7. Promise / Async-Await
|
||||
// ------------------------------------------------------------
|
||||
test('sign — promise wrapper with async/await', async function () {
|
||||
const token = await signAsync(payload, HS_SECRET, { algorithm: 'HS256', expiresIn: '15m' });
|
||||
expect(token).to.be.a('string');
|
||||
|
||||
const decoded = jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
|
||||
console.log('Promise/async signed token:', token);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
meta {
|
||||
name: sign
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const payload = {
|
||||
userId: 123,
|
||||
username: 'testuser',
|
||||
role: 'admin',
|
||||
iat: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
const secret = bru.getEnvVar('secret');
|
||||
|
||||
const options = {
|
||||
algorithm: 'HS256',
|
||||
expiresIn: '1h'
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('Testing JWT encoding...');
|
||||
const token = jwt.sign(payload, secret, options);
|
||||
|
||||
console.log('JWT Token encoded successfully:', token);
|
||||
|
||||
bru.setEnvVar('jwt_token', token);
|
||||
|
||||
bru.setEnvVar('original_payload', JSON.stringify(payload));
|
||||
|
||||
console.log('JWT encoding test passed!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('JWT encoding failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
tests {
|
||||
const atob = require('atob')
|
||||
|
||||
test("JWT token should be generated", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
expect(jwtToken).to.exist;
|
||||
});
|
||||
|
||||
test("JWT token should be a string", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
expect(typeof jwtToken).to.equal('string');
|
||||
});
|
||||
|
||||
test("JWT token should have 3 parts (header.payload.signature)", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
const parts = jwtToken.split('.');
|
||||
expect(parts.length).to.equal(3);
|
||||
});
|
||||
|
||||
test("JWT token should be valid base64", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
const parts = jwtToken.split('.');
|
||||
|
||||
// Test that each part is valid base64
|
||||
parts.forEach((part, index) => {
|
||||
try {
|
||||
atob(part);
|
||||
} catch (e) {
|
||||
throw new Error(`JWT part ${index + 1} is not valid base64`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("JWT token should contain expected payload data", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
const originalPayload = JSON.parse(bru.getEnvVar('original_payload'));
|
||||
|
||||
// Decode the payload part (second part of JWT)
|
||||
const parts = jwtToken.split('.');
|
||||
const payloadPart = parts[1];
|
||||
const decodedPayload = JSON.parse(atob(payloadPart));
|
||||
console.log(decodedPayload)
|
||||
|
||||
expect(decodedPayload.userId).to.equal(originalPayload.userId);
|
||||
expect(decodedPayload.username).to.equal(originalPayload.username);
|
||||
expect(decodedPayload.role).to.equal(originalPayload.role);
|
||||
});
|
||||
|
||||
test("JWT token should have proper header", function() {
|
||||
const jwtToken = bru.getEnvVar('jwt_token');
|
||||
const parts = jwtToken.split('.');
|
||||
const headerPart = parts[0];
|
||||
const header = JSON.parse(atob(headerPart));
|
||||
|
||||
expect(header.alg).to.equal('HS256');
|
||||
expect(header.typ).to.equal('JWT');
|
||||
});
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: verify
|
||||
seq: 2
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
meta {
|
||||
name: verify with callback err
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
tests {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const HS_SECRET = 'supersecret';
|
||||
|
||||
function verifyViaCallback(token, secret, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, secret, options, (err, decoded) => {
|
||||
if (err) return reject(err);
|
||||
resolve(decoded);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createValidToken(payload = { sub: 'user123' }, secret = HS_SECRET) {
|
||||
return jwt.sign(payload, secret, { algorithm: 'HS256', expiresIn: '1h' });
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
ERROR TESTS — jwt.verify should call callback with `err`
|
||||
============================================================ */
|
||||
|
||||
test('ERROR (callback) — malformed token', async function () {
|
||||
const malformedToken = 'abc.def'; // not a valid JWT
|
||||
try {
|
||||
await verifyViaCallback(malformedToken, HS_SECRET, { algorithms: ['HS256'] });
|
||||
throw new Error('Expected jwt.verify to error via callback');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/jwt malformed|invalid token/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('ERROR (callback) — invalid signature (wrong secret)', async function () {
|
||||
const token = createValidToken(); // signed with HS_SECRET
|
||||
try {
|
||||
await verifyViaCallback(token, 'wrong_secret', { algorithms: ['HS256'] });
|
||||
throw new Error('Expected jwt.verify to error via callback');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/invalid signature/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('ERROR (callback) — invalid algorithm', async function () {
|
||||
const token = createValidToken();
|
||||
try {
|
||||
// Pass unsupported algorithm intentionally
|
||||
await verifyViaCallback(token, HS_SECRET, { algorithms: ['RS256'] });
|
||||
throw new Error('Expected jwt.verify to error due to invalid algorithm');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/invalid algorithm/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('ERROR (callback) — missing secret', async function () {
|
||||
const token = createValidToken();
|
||||
try {
|
||||
await verifyViaCallback(token, undefined, { algorithms: ['HS256'] });
|
||||
throw new Error('Expected jwt.verify to error due to missing secret');
|
||||
} catch (err) {
|
||||
expect(err).to.be.instanceOf(Error);
|
||||
expect(String(err.message)).to.match(/secret|key must be provided/i);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
meta {
|
||||
name: verify with callback token
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
tests {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const HS_SECRET = 'supersecret';
|
||||
|
||||
const token = jwt.sign({ sub: 'user123' }, HS_SECRET, {
|
||||
algorithm: 'HS256',
|
||||
expiresIn: '15m',
|
||||
});
|
||||
|
||||
function once(fn) {
|
||||
let called = false;
|
||||
return (...args) => {
|
||||
if (!called) {
|
||||
called = true;
|
||||
fn(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function verifyAsync(token, secret, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, secret, options, (err, decoded) => {
|
||||
if (err) reject(err);
|
||||
else resolve(decoded);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test('verify — named normal callback', function () {
|
||||
function verifyCallback(err, decoded) {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log('Named callback verified user:', decoded.sub);
|
||||
}
|
||||
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, verifyCallback);
|
||||
});
|
||||
|
||||
test('verify — anonymous callback', function () {
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, function (err, decoded) {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log('Anonymous callback verified user:', decoded.sub);
|
||||
});
|
||||
});
|
||||
|
||||
test('verify — arrow function callback', function () {
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log('Arrow callback verified user:', decoded.sub);
|
||||
});
|
||||
});
|
||||
|
||||
test('verify — bound method callback', function () {
|
||||
const handler = {
|
||||
prefix: '[VERIFY]',
|
||||
done(err, decoded) {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log(this.prefix, 'Bound callback verified user:', decoded.sub);
|
||||
},
|
||||
};
|
||||
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, handler.done.bind(handler));
|
||||
});
|
||||
|
||||
function makeVerifyCallback(label) {
|
||||
return (err, decoded) => {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log(label, 'Higher-order callback verified user:', decoded.sub);
|
||||
};
|
||||
}
|
||||
|
||||
test('verify — higher-order callback', function () {
|
||||
const cb = makeVerifyCallback('[CUSTOM LABEL]');
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, cb);
|
||||
});
|
||||
|
||||
test('verify — once-wrapped callback', function () {
|
||||
const cb = once((err, decoded) => {
|
||||
expect(err).to.be.null;
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log('Once callback executed and verified user:', decoded.sub);
|
||||
});
|
||||
|
||||
jwt.verify(token, HS_SECRET, { algorithms: ['HS256'] }, cb);
|
||||
});
|
||||
|
||||
test('verify — promise wrapper with async/await', async function () {
|
||||
const decoded = await verifyAsync(token, HS_SECRET, { algorithms: ['HS256'] });
|
||||
expect(decoded.sub).to.equal('user123');
|
||||
console.log('Promise/async verified user:', decoded.sub);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
meta {
|
||||
name: verify
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{host}}/api/echo
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
script:pre-request {
|
||||
const jwt = require('jsonwebtoken');
|
||||
|
||||
const validPayload = {
|
||||
userId: 789,
|
||||
username: 'verifyuser',
|
||||
role: 'admin',
|
||||
iat: Math.floor(Date.now() / 1000)
|
||||
};
|
||||
|
||||
const secret = bru.getEnvVar('secret') || 'test-secret-key';
|
||||
const wrongSecret = 'wrong-secret-key';
|
||||
|
||||
const validToken = jwt.sign(validPayload, secret, { algorithm: 'HS256', expiresIn: '1h' });
|
||||
const invalidToken = jwt.sign(validPayload, wrongSecret, { algorithm: 'HS256', expiresIn: '1h' });
|
||||
|
||||
|
||||
bru.setEnvVar('valid_token', validToken);
|
||||
bru.setEnvVar('invalid_token', invalidToken);
|
||||
|
||||
try {
|
||||
console.log('Testing JWT verification...');
|
||||
console.log('Valid token:', validToken);
|
||||
|
||||
const verified = jwt.verify(validToken, secret);
|
||||
|
||||
const verifiedWithOptions = jwt.verify(validToken, secret, {
|
||||
algorithms: ['HS256'],
|
||||
ignoreExpiration: false
|
||||
});
|
||||
if (!verifiedWithOptions) {
|
||||
throw new Error('Verification with options should work');
|
||||
}
|
||||
|
||||
console.log('JWT verification test passed!');
|
||||
|
||||
bru.setEnvVar('verified_payload', JSON.stringify(verified));
|
||||
|
||||
} catch (error) {
|
||||
console.error('JWT verification failed:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
tests {
|
||||
test("Verified payload should exist", function() {
|
||||
const verifiedPayload = bru.getEnvVar('verified_payload');
|
||||
expect(verifiedPayload).to.exist;
|
||||
});
|
||||
|
||||
test("Verified payload should be valid JSON", function() {
|
||||
const verifiedPayload = bru.getEnvVar('verified_payload');
|
||||
const parsed = JSON.parse(verifiedPayload);
|
||||
expect(typeof parsed).to.equal('object');
|
||||
});
|
||||
|
||||
test("Verified payload should contain correct user data", function() {
|
||||
const verifiedPayload = JSON.parse(bru.getEnvVar('verified_payload'));
|
||||
|
||||
expect(verifiedPayload.userId).to.equal(789);
|
||||
expect(verifiedPayload.username).to.equal('verifyuser');
|
||||
expect(verifiedPayload.role).to.equal('admin');
|
||||
});
|
||||
|
||||
test("Verified payload should have timestamp fields", function() {
|
||||
const verifiedPayload = JSON.parse(bru.getEnvVar('verified_payload'));
|
||||
|
||||
expect(verifiedPayload.iat).to.exist;
|
||||
expect(verifiedPayload.exp).to.exist;
|
||||
expect(typeof verifiedPayload.iat).to.equal('number');
|
||||
expect(typeof verifiedPayload.exp).to.equal('number');
|
||||
});
|
||||
|
||||
test("Invalid token with wrong secret should throw error", function() {
|
||||
const jwt = require('jsonwebtoken');
|
||||
const invalidToken = bru.getEnvVar('invalid_token');
|
||||
const secret = bru.getEnvVar('secret') || 'test-secret-key';
|
||||
|
||||
try {
|
||||
jwt.verify(invalidToken, secret);
|
||||
expect.fail('Expected JWT verification to throw an error for invalid token');
|
||||
} catch (error) {
|
||||
expect(error).to.exist;
|
||||
expect(error.message).to.equal('invalid signature');
|
||||
console.log('Invalid token correctly threw error:', error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"maximized": true,
|
||||
"lastOpenedCollections": ["{{projectRoot}}/tests/scripting/inbuilt-libraries/jsonwebtoken/fixtures/collection"],
|
||||
"preferences": {
|
||||
"request": {
|
||||
"sslVerification": true,
|
||||
"customCaCertificate": {
|
||||
"enabled": false,
|
||||
"filePath": ""
|
||||
},
|
||||
"keepDefaultCaCertificates": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"collections": [
|
||||
{
|
||||
"pathname": "{{projectRoot}}/tests/scripting/inbuilt-libraries/jsonwebtoken/fixtures/collection",
|
||||
"selectedEnvironment": "Prod"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { test, expect } from '../../../../playwright';
|
||||
|
||||
test.describe.serial('jwt collection success', () => {
|
||||
test('developer mode', async ({ pageWithUserData: page }) => {
|
||||
// init dev mode
|
||||
await page.getByTitle('jsonwebtoken').click();
|
||||
await page.getByLabel('Developer Mode(use only if').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
// Run the collection
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
// Parse and validate test results
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(7);
|
||||
await expect(parseInt(passed)).toBe(7);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
|
||||
test('safe mode', async ({ pageWithUserData: page }) => {
|
||||
// init safe mode
|
||||
await page.getByTitle('jsonwebtoken').click();
|
||||
await page.getByText('Developer Mode').click();
|
||||
await page.getByLabel('Safe Mode').check();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
test.setTimeout(2 * 60 * 1000);
|
||||
|
||||
// Run the collection
|
||||
await page.locator('.collection-actions').hover();
|
||||
await page.locator('.collection-actions .icon').click();
|
||||
await page.getByText('Run', { exact: true }).click();
|
||||
await page.getByRole('button', { name: 'Run Collection' }).click();
|
||||
await page.getByRole('button', { name: 'Run Again' }).waitFor({ timeout: 2 * 60 * 1000 });
|
||||
|
||||
// Parse and validate test results
|
||||
const result = await page.getByText('Total Requests: ').innerText();
|
||||
const matches = result.match(/Total Requests: (\d+), Passed: (\d+), Failed: (\d+), Skipped: (\d+)/);
|
||||
if (!matches) {
|
||||
throw new Error('Could not parse test results');
|
||||
}
|
||||
const [totalRequests, passed, failed, skipped] = matches.slice(1);
|
||||
|
||||
await expect(parseInt(totalRequests)).toBe(7);
|
||||
await expect(parseInt(passed)).toBe(7);
|
||||
await expect(parseInt(failed)).toBe(0);
|
||||
await expect(parseInt(skipped)).toBe(0);
|
||||
await expect(parseInt(passed)).toBe(parseInt(totalRequests) - parseInt(skipped) - parseInt(failed));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user