mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-15 03:41:28 +00:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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});');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user