diff --git a/packages/bruno-converters/src/postman/postman-translations.js b/packages/bruno-converters/src/postman/postman-translations.js index 5ca8a9c7b..f315daa89 100644 --- a/packages/bruno-converters/src/postman/postman-translations.js +++ b/packages/bruno-converters/src/postman/postman-translations.js @@ -68,6 +68,10 @@ const replacements = { 'pm\\.execution\\.skipRequest': 'bru.runner.skipRequest', 'pm\\.execution\\.setNextRequest\\(null\\)': 'bru.runner.stopExecution()', 'pm\\.execution\\.setNextRequest\\(\'null\'\\)': 'bru.runner.stopExecution()', + // Direct cookie access translations (pm.cookies.has/get/toObject) + 'pm\\.cookies\\.has\\(([^)]+)\\)': 'await bru.cookies.jar().hasCookie(req.getUrl(), $1)', + 'pm\\.cookies\\.get\\(([^)]+)\\)': '(await bru.cookies.jar().getCookie(req.getUrl(), $1))?.value', + 'pm\\.cookies\\.toObject\\(\\)': '(await bru.cookies.jar().getCookies(req.getUrl())).reduce((obj, c) => ({...obj, [c.key]: c.value}), {})', // Cookie jar translations 'pm\\.cookies\\.jar\\(\\)': 'bru.cookies.jar()', 'pm\\.cookies\\.jar\\(\\)\\.get\\(': 'bru.cookies.jar().getCookie(', diff --git a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js index 7a21d07e1..fac8ac03b 100644 --- a/packages/bruno-converters/src/utils/postman-to-bruno-translator.js +++ b/packages/bruno-converters/src/utils/postman-to-bruno-translator.js @@ -240,6 +240,94 @@ const complexTransformations = [ args ); } + }, + + // pm.cookies.has(name) → await bru.cookies.jar().hasCookie(req.getUrl(), name) + { + pattern: 'pm.cookies.has', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + + const hasCookieCall = j.callExpression( + j.identifier('bru.cookies.jar().hasCookie'), + [j.identifier('req.getUrl()'), ...args] + ); + + return j.awaitExpression(hasCookieCall); + } + }, + + // pm.cookies.get(name) → (await bru.cookies.jar().getCookie(req.getUrl(), name))?.value + { + pattern: 'pm.cookies.get', + transform: (path, j) => { + const callExpr = path.parent.value; + const args = callExpr.arguments; + + const getCookieCall = j.callExpression( + j.identifier('bru.cookies.jar().getCookie'), + [j.identifier('req.getUrl()'), ...args] + ); + + const awaitExpr = j.awaitExpression(getCookieCall); + const parenAwait = j.parenthesizedExpression + ? j.parenthesizedExpression(awaitExpr) + : awaitExpr; + + return j.optionalMemberExpression( + parenAwait, + j.identifier('value'), + false, + true + ); + } + }, + + // pm.cookies.toObject() → (await bru.cookies.jar().getCookies(req.getUrl())).reduce((obj, c) => ({...obj, [c.key]: c.value}), {}) + { + pattern: 'pm.cookies.toObject', + transform: (path, j) => { + const getCookiesCall = j.callExpression( + j.identifier('bru.cookies.jar().getCookies'), + [j.identifier('req.getUrl()')] + ); + + const awaitExpr = j.awaitExpression(getCookiesCall); + + // Build the reduce callback: (obj, c) => ({...obj, [c.key]: c.value}) + const objParam = j.identifier('obj'); + const cParam = j.identifier('c'); + + const spreadElement = j.spreadElement(objParam); + const computedProp = j.property( + 'init', + j.memberExpression(cParam, j.identifier('key')), + j.memberExpression(cParam, j.identifier('value')) + ); + computedProp.computed = true; + + const objectExpr = j.objectExpression([spreadElement, computedProp]); + + const arrowBody = j.parenthesizedExpression + ? j.parenthesizedExpression(objectExpr) + : objectExpr; + + const reduceFn = j.arrowFunctionExpression( + [objParam, cParam], + arrowBody + ); + reduceFn.expression = true; + + // Build: (await ...).reduce(fn, {}) + return j.callExpression( + j.memberExpression( + awaitExpr, + j.identifier('reduce') + ), + [reduceFn, j.objectExpression([])] + ); + } } ]; @@ -249,7 +337,7 @@ complexTransformations.forEach((transform) => { complexTransformationsMap[transform.pattern] = transform; }); -const varInitsToReplace = new Set(['pm', 'postman', 'pm.request', 'pm.response', 'pm.test', 'pm.expect', 'pm.environment', 'pm.variables', 'pm.collectionVariables', 'pm.execution', 'pm.globals']); +const varInitsToReplace = new Set(['pm', 'postman', 'pm.request', 'pm.response', 'pm.test', 'pm.expect', 'pm.environment', 'pm.variables', 'pm.collectionVariables', 'pm.execution', 'pm.globals', 'pm.cookies']); /** * Process all transformations (both simple and complex) in the AST in a single pass diff --git a/packages/bruno-converters/tests/postman/postman-translations/postman-cookie-conversions.spec.js b/packages/bruno-converters/tests/postman/postman-translations/postman-cookie-conversions.spec.js index 7ee9ee997..d17680567 100644 --- a/packages/bruno-converters/tests/postman/postman-translations/postman-cookie-conversions.spec.js +++ b/packages/bruno-converters/tests/postman/postman-translations/postman-cookie-conversions.spec.js @@ -316,4 +316,57 @@ describe('postmanTranslations - cookie API conversions', () => { expect(postmanTranslation(inputScript)).toBe(expectedOutput); }); + + // Tests for pm.cookies direct access methods (has, get, toObject) + + test('should convert pm.cookies.has(name) to await hasCookie', () => { + const inputScript = `pm.cookies.has('token')`; + const expectedOutput = `await bru.cookies.jar().hasCookie(req.getUrl(), 'token')`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should convert pm.cookies.get(name) to await getCookie?.value', () => { + const inputScript = `pm.cookies.get('token')`; + const expectedOutput = `(await bru.cookies.jar().getCookie(req.getUrl(), 'token'))?.value`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should convert pm.cookies.toObject() to getCookies reduce', () => { + const inputScript = `pm.cookies.toObject()`; + const expectedOutput = `(await bru.cookies.jar().getCookies(req.getUrl())).reduce((obj, c) => ({ + ...obj, + [c.key]: c.value +}), {})`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should convert pm.cookies.has inside an if conditional', () => { + const inputScript = `if (pm.cookies.has('auth')) { console.log('found'); }`; + const expectedOutput = `if (await bru.cookies.jar().hasCookie(req.getUrl(), 'auth')) { console.log('found'); }`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should convert pm.cookies.get with a variable argument', () => { + const inputScript = `const val = pm.cookies.get(cookieName)`; + const expectedOutput = `const val = (await bru.cookies.jar().getCookie(req.getUrl(), cookieName))?.value`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should handle mixed pm.cookies.get and pm.cookies.jar().set without conflict', () => { + const inputScript = `const v = pm.cookies.get('token'); pm.cookies.jar().set('https://example.com', 'a', 'b');`; + const expectedOutput = `const v = (await bru.cookies.jar().getCookie(req.getUrl(), 'token'))?.value; bru.cookies.jar().setCookie('https://example.com', 'a', 'b');`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should handle combined has + get in same script', () => { + const inputScript = `if (pm.cookies.has('auth')) { const token = pm.cookies.get('auth'); }`; + const expectedOutput = `if (await bru.cookies.jar().hasCookie(req.getUrl(), 'auth')) { const token = (await bru.cookies.jar().getCookie(req.getUrl(), 'auth'))?.value; }`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); + + test('should handle aliased access: const cookies = pm.cookies', () => { + const inputScript = `const cookies = pm.cookies; cookies.get('token');`; + const expectedOutput = `(await bru.cookies.jar().getCookie(req.getUrl(), 'token'))?.value;`; + expect(postmanTranslation(inputScript)).toBe(expectedOutput); + }); }); diff --git a/packages/bruno-js/src/bru.js b/packages/bruno-js/src/bru.js index 4b0edd795..ec2816376 100644 --- a/packages/bruno-js/src/bru.js +++ b/packages/bruno-js/src/bru.js @@ -81,6 +81,11 @@ class Bru { deleteCookie: (url, cookieName, callback) => { const interpolatedUrl = this.interpolate(url); return cookieJar.deleteCookie(interpolatedUrl, cookieName, callback); + }, + + hasCookie: (url, cookieName, callback) => { + const interpolatedUrl = this.interpolate(url); + return cookieJar.hasCookie(interpolatedUrl, cookieName, callback); } }; } diff --git a/packages/bruno-requests/src/cookies/index.spec.ts b/packages/bruno-requests/src/cookies/index.spec.ts index 1edb76efd..ee59cb98a 100644 --- a/packages/bruno-requests/src/cookies/index.spec.ts +++ b/packages/bruno-requests/src/cookies/index.spec.ts @@ -142,6 +142,19 @@ describe('Bruno Cookie Jar Wrapper - API Examples', () => { }); }); + describe('hasCookie', () => { + test('hasCookie returns true for existing cookie', async () => { + await jar.setCookie(testUrl, 'authToken', 'jwt123'); + const exists = await jar.hasCookie(testUrl, 'authToken'); + expect(exists).toBe(true); + }); + + test('hasCookie returns false for non-existent cookie', async () => { + const exists = await jar.hasCookie(testUrl, 'nonexistent'); + expect(exists).toBe(false); + }); + }); + describe('Error Handling', () => { test('setCookie handles missing URL', async () => { await expect(jar.setCookie('', 'name', 'value')).rejects.toThrow('URL is required'); diff --git a/packages/bruno-requests/src/cookies/index.ts b/packages/bruno-requests/src/cookies/index.ts index 629727953..0f83b49b9 100644 --- a/packages/bruno-requests/src/cookies/index.ts +++ b/packages/bruno-requests/src/cookies/index.ts @@ -214,6 +214,35 @@ const cookieJarWrapper = () => { }); }, + // Check whether a cookie with the given name exists for the URL. + hasCookie: function ( + url: string, + cookieName: string, + callback?: (err: Error | null | undefined, exists?: boolean) => void + ) { + if (!url || !cookieName) { + const error = new Error('URL and cookie name are required'); + if (callback) return callback(error); + return Promise.reject(error); + } + + if (callback) { + return cookieJar.getCookies(url, (err: Error | null, cookies?: Cookie[]) => { + if (err) return callback(err); + const cookieList = cookies || []; + callback(null, cookieList.some((c) => c.key === cookieName)); + }); + } + + return new Promise((resolve, reject) => { + cookieJar.getCookies(url, (err: Error | null, cookies?: Cookie[]) => { + if (err) return reject(err); + const cookieList = cookies || []; + resolve(cookieList.some((c) => c.key === cookieName)); + }); + }); + }, + // Get all cookies that would be sent to the given URL. getCookies: function (url: string, callback?: (err: Error | null | undefined, cookies?: Cookie[]) => void) { if (!url) { diff --git a/packages/bruno-tests/collection/scripting/api/bru/cookies/hasCookie.bru b/packages/bruno-tests/collection/scripting/api/bru/cookies/hasCookie.bru new file mode 100644 index 000000000..a2b17fc13 --- /dev/null +++ b/packages/bruno-tests/collection/scripting/api/bru/cookies/hasCookie.bru @@ -0,0 +1,44 @@ +meta { + name: hasCookie + type: http + seq: 10 +} + +get { + url: {{host}}/ping + body: none + auth: inherit +} + +script:pre-request { + const jar = bru.cookies.jar() + + jar.setCookie("https://testbench-sanity.usebruno.com", "existing_cookie", "some_value") +} + +tests { + const jar = bru.cookies.jar() + + test("should return true for a cookie that exists", async function() { + const exists = await jar.hasCookie('https://testbench-sanity.usebruno.com', 'existing_cookie'); + expect(exists).to.be.true; + }); + + test("should return false for a cookie that does not exist", async function() { + const exists = await jar.hasCookie('https://testbench-sanity.usebruno.com', 'nonexistent_cookie'); + expect(exists).to.be.false; + }); + + jar.hasCookie("https://testbench-sanity.usebruno.com", "existing_cookie", function(error, exists) { + test("should work with callback pattern", function() { + expect(error).to.be.null; + expect(exists).to.be.true; + }); + }); + + jar.clear() +} + +settings { + encodeUrl: true +}