feat: enhance request and response translation capabilities (#6981)

- Added translations for req.method, req.headers, and req.body to their Postman equivalents.
- Implemented handling for req.setUrl, req.setMethod, req.setBody, and req.setHeaders, converting them to appropriate Postman assignments and method calls.
- Introduced support for translating res.responseTime and res.headers properties.
- Updated tests to cover new translations and ensure accuracy in request and response handling.
This commit is contained in:
sanish chirayath
2026-01-30 15:50:27 +05:30
committed by GitHub
parent b268aa9f98
commit 8fface4bbe
3 changed files with 359 additions and 33 deletions

View File

@@ -56,6 +56,9 @@ const simpleTranslations = {
// Note: req.getUrl(), req.getMethod(), req.getHeaders(), req.getBody(), req.getName() are handled
// in complexTransformations because they're function -> property conversions
'req.url': 'pm.request.url',
'req.method': 'pm.request.method',
'req.headers': 'pm.request.headers',
'req.body': 'pm.request.body',
'req.getHeader': 'pm.request.headers.get',
'req.setHeader': 'pm.request.headers.set',
@@ -66,6 +69,8 @@ const simpleTranslations = {
'res.statusText': 'pm.response.status',
'res.body': 'pm.response.body',
'res.url': 'pm.response.url',
'res.responseTime': 'pm.response.responseTime',
'res.headers': 'pm.response.headers',
'res.getBody': 'pm.response.json',
'res.getHeader': 'pm.response.headers.get',
'res.getSize': 'pm.response.size',
@@ -77,6 +82,38 @@ const simpleTranslations = {
'expect.fail': 'pm.expect.fail'
};
// =============================================================================
// UNSUPPORTED BRUNO APIs (No Postman Equivalent)
// =============================================================================
/**
* UNSUPPORTED BRUNO APIs (No Postman Equivalent)
*
* These Bruno APIs have no direct Postman equivalent and will be left unchanged
* in the translated code. Users should be aware that these calls will not work
* in Postman:
*
* Request APIs:
* - req.getTags() - Postman doesn't have tags
* - req.setMaxRedirects() - Postman doesn't expose redirect settings
* - req.getTimeout() / req.setTimeout() - Postman doesn't expose timeout settings
* - req.getExecutionMode() / req.getExecutionPlatform() - Bruno-specific
* - req.onFail() - Postman doesn't support error handlers
*
* Response APIs:
* - res.setBody() - Postman response is read-only
*
* Bru APIs:
* - bru.runRequest() - Postman doesn't support nested request execution
* - bru.sleep() - Postman doesn't have sleep (use setTimeout workaround)
* - bru.getProcessEnv() - Postman doesn't expose process env vars
* - bru.getOauth2CredentialVar() - Bruno-specific
* - bru.getCollectionName() - pm.info doesn't expose collection name
* - bru.disableParsingResponseJson() - Bruno-specific
* - bru.cwd() - Bruno-specific
* - bru.getAssertionResults() / bru.getTestResults() - Bruno-specific
*/
// =============================================================================
// COMPLEX TRANSFORMATIONS
// =============================================================================
@@ -160,6 +197,11 @@ const complexTransformations = [
pattern: 'req.getName',
transform: () => buildMemberExpressionFromString('pm.info.requestName')
},
// req.getAuthMode() -> pm.request.auth.type
{
pattern: 'req.getAuthMode',
transform: () => buildMemberExpressionFromString('pm.request.auth.type')
},
// Response helpers: function -> property conversions
// res.getStatus() -> pm.response.code
@@ -186,6 +228,133 @@ const complexTransformations = [
{
pattern: 'res.getUrl',
transform: () => buildMemberExpressionFromString('pm.response.url')
},
// Request modifiers: function calls -> assignments
// req.setUrl(url) -> pm.request.url = url
{
pattern: 'req.setUrl',
transform: (path) => {
const callExpr = path.value;
const args = callExpr.arguments;
if (!args || args.length === 0) {
// No arguments, return the property access
return buildMemberExpressionFromString('pm.request.url');
}
// Transform req.setUrl(url) to pm.request.url = url
return j.assignmentExpression(
'=',
buildMemberExpressionFromString('pm.request.url'),
args[0]
);
}
},
// req.setMethod(method) -> pm.request.method = method
{
pattern: 'req.setMethod',
transform: (path) => {
const callExpr = path.value;
const args = callExpr.arguments;
if (!args || args.length === 0) {
// No arguments, return the property access
return buildMemberExpressionFromString('pm.request.method');
}
// Transform req.setMethod(method) to pm.request.method = method
return j.assignmentExpression(
'=',
buildMemberExpressionFromString('pm.request.method'),
args[0]
);
}
},
// req.setBody(data) -> pm.request.body.update({mode: "raw", raw: JSON.stringify(data)})
{
pattern: 'req.setBody',
transform: (path) => {
const callExpr = path.value;
const args = callExpr.arguments;
if (!args || args.length === 0) {
// No arguments, return the property access
return buildMemberExpressionFromString('pm.request.body');
}
// Transform req.setBody(data) to pm.request.body.update({mode: "raw", raw: JSON.stringify(data)})
const bodyArg = args[0];
const updateCall = j.callExpression(
j.memberExpression(
buildMemberExpressionFromString('pm.request.body'),
j.identifier('update')
),
[
j.objectExpression([
j.property('init', j.identifier('mode'), j.literal('raw')),
j.property('init', j.identifier('raw'), j.callExpression(
j.identifier('JSON.stringify'),
[bodyArg]
))
])
]
);
return updateCall;
}
},
// req.setHeaders(headers) -> loop calling pm.request.headers.upsert() for each header
{
pattern: 'req.setHeaders',
transform: (path) => {
const callExpr = path.value;
const args = callExpr.arguments;
if (!args || args.length === 0) {
// No arguments, return the property access
return buildMemberExpressionFromString('pm.request.headers');
}
const headersArg = args[0];
// Transform req.setHeaders(obj) to a for...in loop that calls upsert for each property
// Generate: for (const key in headersObj) { pm.request.headers.upsert({key: key, value: headersObj[key]}); }
const headersVar = j.identifier('_headers');
const keyVar = j.identifier('key');
// Create: for (const key in _headers) { pm.request.headers.upsert({key: key, value: _headers[key]}); }
const forLoop = j.forInStatement(
j.variableDeclaration('const', [j.variableDeclarator(keyVar)]),
headersVar,
j.blockStatement([
j.expressionStatement(
j.callExpression(
j.memberExpression(
buildMemberExpressionFromString('pm.request.headers'),
j.identifier('upsert')
),
[
j.objectExpression([
j.property('init', j.identifier('key'), keyVar),
j.property('init', j.identifier('value'), j.memberExpression(headersVar, keyVar, true))
])
]
)
)
])
);
// We need to replace the call expression with a block that includes the variable declaration and loop
// But the current architecture only replaces the call expression itself
// So we'll create an IIFE (Immediately Invoked Function Expression) that contains both
const iife = j.callExpression(
j.functionExpression(
null,
[],
j.blockStatement([
j.variableDeclaration('const', [
j.variableDeclarator(headersVar, headersArg)
]),
forLoop
])
),
[]
);
return iife;
}
}
];

View File

@@ -13,6 +13,24 @@ describe('Bruno to Postman Request Translation', () => {
expect(translatedCode).toBe('const url = pm.request.url;');
});
it('should translate req.method to pm.request.method (property to property)', () => {
const code = 'const method = req.method;';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const method = pm.request.method;');
});
it('should translate req.headers to pm.request.headers (property to property)', () => {
const code = 'const headers = req.headers;';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const headers = pm.request.headers;');
});
it('should translate req.body to pm.request.body (property to property)', () => {
const code = 'const body = req.body;';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const body = pm.request.body;');
});
it('should translate req.getMethod() to pm.request.method (function to property)', () => {
const code = 'const method = req.getMethod();';
const translatedCode = translateBruToPostman(code);
@@ -37,6 +55,18 @@ describe('Bruno to Postman Request Translation', () => {
expect(translatedCode).toBe('const name = pm.info.requestName;');
});
it('should translate req.getAuthMode() to pm.request.auth.type (function to property)', () => {
const code = 'const authMode = req.getAuthMode();';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const authMode = pm.request.auth.type;');
});
it('should handle req.getAuthMode() in conditionals', () => {
const code = 'if (req.getAuthMode() === "oauth2") { console.log("OAuth2 auth"); }';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('if (pm.request.auth.type === "oauth2") { console.log("OAuth2 auth"); }');
});
it('should translate req.getHeader() to pm.request.headers.get()', () => {
const code = 'const contentType = req.getHeader("Content-Type");';
const translatedCode = translateBruToPostman(code);
@@ -61,12 +91,17 @@ const name = req.getName();
console.log(\`Request: \${method} \${url} - \${name}\`);
`;
const translatedCode = translateBruToPostman(code);
const expected = `
// All request properties
const url = pm.request.url;
const method = pm.request.method;
const headers = pm.request.headers;
const body = pm.request.body;
const name = pm.info.requestName;
expect(translatedCode).toContain('const url = pm.request.url;');
expect(translatedCode).toContain('const method = pm.request.method;');
expect(translatedCode).toContain('const headers = pm.request.headers;');
expect(translatedCode).toContain('const body = pm.request.body;');
expect(translatedCode).toContain('const name = pm.info.requestName;');
console.log(\`Request: \${method} \${url} - \${name}\`);
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle request properties in conditionals', () => {
@@ -77,9 +112,13 @@ if (req.getMethod() === 'POST' || req.getMethod() === 'PUT') {
}
`;
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toContain('if (pm.request.method === \'POST\' || pm.request.method === \'PUT\') {');
expect(translatedCode).toContain('const body = pm.request.body;');
const expected = `
if (pm.request.method === 'POST' || pm.request.method === 'PUT') {
const body = pm.request.body;
console.log("Request body:", body);
}
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle request logging', () => {
@@ -89,9 +128,54 @@ console.log("Method:", req.getMethod());
console.log("Headers:", JSON.stringify(req.getHeaders()));
`;
const translatedCode = translateBruToPostman(code);
const expected = `
console.log("Making request to:", pm.request.url);
console.log("Method:", pm.request.method);
console.log("Headers:", JSON.stringify(pm.request.headers));
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
expect(translatedCode).toContain('console.log("Making request to:", pm.request.url);');
expect(translatedCode).toContain('console.log("Method:", pm.request.method);');
expect(translatedCode).toContain('console.log("Headers:", JSON.stringify(pm.request.headers));');
it('should translate req.setUrl() to pm.request.url assignment', () => {
const code = 'req.setUrl("https://api.example.com/users");';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('pm.request.url = "https://api.example.com/users";');
});
it('should translate req.setMethod() to pm.request.method assignment', () => {
const code = 'req.setMethod("POST");';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('pm.request.method = "POST";');
});
it('should translate req.setBody() to pm.request.body.update()', () => {
const code = 'req.setBody({name: "John", age: 30});';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('pm.request.body.update({\n mode: "raw",\n raw: JSON.stringify({name: "John", age: 30})\n});');
});
it('should translate req.setHeaders() to pm.request.headers.upsert() calls', () => {
const code = 'req.setHeaders({"Content-Type": "application/json", "Authorization": "Bearer token"});';
const translatedCode = translateBruToPostman(code);
// Should generate an IIFE with a for...in loop that calls upsert for each header
expect(translatedCode).toBe('(function() {\n const _headers = {"Content-Type": "application/json", "Authorization": "Bearer token"};\n\n for (const key in _headers) {\n pm.request.headers.upsert({\n key: key,\n value: _headers[key]\n });\n }\n})();');
});
it('should handle req.setUrl() with variable', () => {
const code = 'const newUrl = "https://api.example.com"; req.setUrl(newUrl);';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const newUrl = "https://api.example.com"; pm.request.url = newUrl;');
});
it('should handle req.setMethod() with variable', () => {
const code = 'const method = "PUT"; req.setMethod(method);';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const method = "PUT"; pm.request.method = method;');
});
it('should handle req.setBody() with variable', () => {
const code = 'const body = {id: 1}; req.setBody(body);';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const body = {id: 1}; pm.request.body.update({\n mode: "raw",\n raw: JSON.stringify(body)\n});');
});
});

View File

@@ -120,10 +120,12 @@ const [first, second] = items;
bru.setEnvVar("userId", id);
`;
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toContain('const { id, name, items } = pm.response.json();');
expect(translatedCode).toContain('const [first, second] = items;');
expect(translatedCode).toContain('pm.environment.set("userId", id);');
const expected = `
const { id, name, items } = pm.response.json();
const [first, second] = items;
pm.environment.set("userId", id);
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle response JSON with optional chaining', () => {
@@ -132,9 +134,11 @@ const userId = res.getBody()?.user?.id ?? "anonymous";
const items = res.getBody()?.data?.items || [];
`;
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toContain('const userId = pm.response.json()?.user?.id ?? "anonymous";');
expect(translatedCode).toContain('const items = pm.response.json()?.data?.items || [];');
const expected = `
const userId = pm.response.json()?.user?.id ?? "anonymous";
const items = pm.response.json()?.data?.items || [];
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle response in complex conditionals', () => {
@@ -156,13 +160,24 @@ if (res.getStatus() >= 200 && res.getStatus() < 300) {
}
`;
const translatedCode = translateBruToPostman(code);
const expected = `
if (pm.response.code >= 200 && pm.response.code < 300) {
if (pm.response.headers.get('Content-Type').includes('application/json')) {
const data = pm.response.json();
expect(translatedCode).toContain('if (pm.response.code >= 200 && pm.response.code < 300) {');
expect(translatedCode).toContain('if (pm.response.headers.get(\'Content-Type\').includes(\'application/json\')) {');
expect(translatedCode).toContain('const data = pm.response.json();');
expect(translatedCode).toContain('pm.environment.set("authToken", data.token);');
expect(translatedCode).toContain('} else if (pm.response.code === 404) {');
expect(translatedCode).toContain('console.error("Request failed with status:", pm.response.code);');
if (data.success === true && data.token) {
pm.environment.set("authToken", data.token);
} else if (data.error) {
console.error("API error:", data.error);
}
}
} else if (pm.response.code === 404) {
console.log("Resource not found");
} else {
console.error("Request failed with status:", pm.response.code);
}
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle all response property methods together', () => {
@@ -174,11 +189,14 @@ const statusText = res.statusText;
const responseTime = res.getResponseTime();
`;
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toContain('const statusCode = pm.response.code;');
expect(translatedCode).toContain('const responseBody = pm.response.json();');
expect(translatedCode).toContain('const statusText = pm.response.status;');
expect(translatedCode).toContain('const responseTime = pm.response.responseTime;');
const expected = `
// All response property methods
const statusCode = pm.response.code;
const responseBody = pm.response.json();
const statusText = pm.response.status;
const responseTime = pm.response.responseTime;
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle response processing in arrow functions', () => {
@@ -192,10 +210,65 @@ const itemIds = processItems();
bru.setEnvVar("itemIds", JSON.stringify(itemIds));
`;
const translatedCode = translateBruToPostman(code);
const expected = `
const processItems = () => {
const items = pm.response.json().items;
return items.map(item => item.id);
};
expect(translatedCode).toContain('const items = pm.response.json().items;');
expect(translatedCode).toContain('return items.map(item => item.id);');
expect(translatedCode).toContain('const itemIds = processItems();');
expect(translatedCode).toContain('pm.environment.set("itemIds", JSON.stringify(itemIds));');
const itemIds = processItems();
pm.environment.set("itemIds", JSON.stringify(itemIds));
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should translate res.responseTime property to pm.response.responseTime', () => {
const code = 'const time = res.responseTime;';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const time = pm.response.responseTime;');
});
it('should translate res.headers property to pm.response.headers', () => {
const code = 'const headers = res.headers;';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const headers = pm.response.headers;');
});
it('should handle res.responseTime in conditionals', () => {
const code = 'if (res.responseTime > 1000) { console.log("Slow response"); }';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('if (pm.response.responseTime > 1000) { console.log("Slow response"); }');
});
it('should handle res.headers property access', () => {
const code = 'const contentType = res.headers["Content-Type"];';
const translatedCode = translateBruToPostman(code);
expect(translatedCode).toBe('const contentType = pm.response.headers["Content-Type"];');
});
it('should handle both res.responseTime property and res.getResponseTime() method', () => {
const code = `
const time1 = res.responseTime;
const time2 = res.getResponseTime();
`;
const translatedCode = translateBruToPostman(code);
const expected = `
const time1 = pm.response.responseTime;
const time2 = pm.response.responseTime;
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
it('should handle both res.headers property and res.getHeaders() method', () => {
const code = `
const headers1 = res.headers;
const headers2 = res.getHeaders();
`;
const translatedCode = translateBruToPostman(code);
const expected = `
const headers1 = pm.response.headers;
const headers2 = pm.response.headers;
`;
expect(translatedCode.trim()).toBe(expected.trim());
});
});