diff --git a/packages/bruno-converters/src/openapi/openapi-to-bruno.js b/packages/bruno-converters/src/openapi/openapi-to-bruno.js index 68f05759d..adca4e795 100644 --- a/packages/bruno-converters/src/openapi/openapi-to-bruno.js +++ b/packages/bruno-converters/src/openapi/openapi-to-bruno.js @@ -128,15 +128,15 @@ const buildEmptyJsonBody = (bodySchema, visited = new Map()) => { let _jsonBody = {}; each(bodySchema.properties || {}, (prop, name) => { - if (prop.type === 'object') { + if (prop.type === 'object' || prop.properties) { _jsonBody[name] = buildEmptyJsonBody(prop, visited); } else if (prop.type === 'array') { - if (prop.items && prop.items.type === 'object') { + if (prop.items && (prop.items.type === 'object' || prop.items.properties)) { _jsonBody[name] = [buildEmptyJsonBody(prop.items, visited)]; } else { _jsonBody[name] = []; } - } else if (prop.type === 'integer') { + } else if (prop.type === 'integer' || prop.type === 'number') { _jsonBody[name] = 0; } else if (prop.type === 'boolean') { _jsonBody[name] = false; @@ -489,7 +489,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => { if (CONTENT_TYPE_PATTERNS.JSON.test(normalizedMimeType)) { brunoRequestItem.request.body.mode = 'json'; - if (bodySchema && bodySchema.type === 'object') { + if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) { let _jsonBody = buildEmptyJsonBody(bodySchema); brunoRequestItem.request.body.json = JSON.stringify(_jsonBody, null, 2); } @@ -498,7 +498,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => { } } else if (normalizedMimeType === 'application/x-www-form-urlencoded') { brunoRequestItem.request.body.mode = 'formUrlEncoded'; - if (bodySchema && bodySchema.type === 'object') { + if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) { each(bodySchema.properties || {}, (prop, name) => { brunoRequestItem.request.body.formUrlEncoded.push({ uid: uuid(), @@ -511,7 +511,7 @@ const transformOpenapiRequestItem = (request, usedNames = new Set()) => { } } else if (normalizedMimeType === 'multipart/form-data') { brunoRequestItem.request.body.mode = 'multipartForm'; - if (bodySchema && bodySchema.type === 'object') { + if (bodySchema && (bodySchema.type === 'object' || bodySchema.properties)) { each(bodySchema.properties || {}, (prop, name) => { brunoRequestItem.request.body.multipartForm.push({ uid: uuid(), @@ -927,7 +927,7 @@ const getSecurity = (apiSpec) => { let defaultSchemes = apiSpec.security || []; let securitySchemes = get(apiSpec, 'components.securitySchemes', {}); - if (Object.keys(securitySchemes) === 0) { + if (Object.keys(securitySchemes).length === 0) { return { supported: [] }; diff --git a/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js b/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js new file mode 100644 index 000000000..670b51756 --- /dev/null +++ b/packages/bruno-converters/tests/openapi/openapi-to-bruno/openapi-body.spec.js @@ -0,0 +1,234 @@ +import { describe, it, expect } from '@jest/globals'; +import openApiToBruno from '../../../src/openapi/openapi-to-bruno'; + +describe('openapi requestBody with $ref', () => { + it('should import body fields when requestBody uses $ref to components/requestBodies with inline schema (no explicit type: object)', () => { + const openApiSpec = ` +openapi: "3.0.0" +info: + version: "1.0.0" + title: "RequestBody Ref Inline Schema Test" +servers: + - url: "https://api.example.com" +paths: + /salesInvoices: + post: + summary: "Creates a salesInvoice" + operationId: "postSalesInvoice" + requestBody: + $ref: '#/components/requestBodies/salesInvoice' + responses: + '201': + description: "A new salesInvoice has been successfully created" +components: + requestBodies: + salesInvoice: + required: true + content: + application/json: + schema: + properties: + id: + type: string + format: uuid + number: + type: string + maxLength: 20 + externalDocumentNumber: + type: string + maxLength: 35 + invoiceDate: + type: string + format: date-time + dueDate: + type: string + format: date-time + fees: + type: array + items: + $ref: '#/components/requestBodies/fees' + fees: + properties: + id: + type: string + format: uuid + name: + type: string + maxLength: 50 + amount: + type: number + required: + - id + - amount +`; + + const result = openApiToBruno(openApiSpec); + + // Should have one request item + expect(result.items.length).toBe(1); + const request = result.items[0]; + + // Body mode should be json + expect(request.request.body.mode).toBe('json'); + + // Body should contain the properties from the schema + expect(request.request.body.json).not.toBeNull(); + + const bodyJson = JSON.parse(request.request.body.json); + expect(bodyJson).toHaveProperty('id'); + expect(bodyJson).toHaveProperty('number'); + expect(bodyJson).toHaveProperty('externalDocumentNumber'); + expect(bodyJson).toHaveProperty('invoiceDate'); + expect(bodyJson).toHaveProperty('dueDate'); + expect(bodyJson).toHaveProperty('fees'); + expect(bodyJson['fees'][0]).toHaveProperty('id'); + }); + + it('should import formUrlEncoded body when requestBody uses $ref with inline schema', () => { + const openApiSpec = ` +openapi: "3.0.0" +info: + version: "1.0.0" + title: "Form URL Encoded Ref Test" +servers: + - url: "https://api.example.com" +paths: + /login: + post: + summary: "Login" + operationId: "login" + requestBody: + $ref: '#/components/requestBodies/loginForm' + responses: + '200': + description: "Login successful" +components: + requestBodies: + loginForm: + required: true + content: + application/x-www-form-urlencoded: + schema: + properties: + username: + type: string + password: + type: string +`; + + const result = openApiToBruno(openApiSpec); + + expect(result.items.length).toBe(1); + const request = result.items[0]; + + expect(request.request.body.mode).toBe('formUrlEncoded'); + expect(request.request.body.formUrlEncoded.length).toBe(2); + + const fieldNames = request.request.body.formUrlEncoded.map((f) => f.name); + expect(fieldNames).toContain('username'); + expect(fieldNames).toContain('password'); + }); + + it('should import multipartForm body when requestBody uses $ref with inline schema', () => { + const openApiSpec = ` +openapi: "3.0.0" +info: + version: "1.0.0" + title: "Multipart Form Ref Test" +servers: + - url: "https://api.example.com" +paths: + /upload: + post: + summary: "Upload file" + operationId: "uploadFile" + requestBody: + $ref: '#/components/requestBodies/fileUpload' + responses: + '200': + description: "Upload successful" +components: + requestBodies: + fileUpload: + required: true + content: + multipart/form-data: + schema: + properties: + file: + type: string + format: binary + description: + type: string +`; + + const result = openApiToBruno(openApiSpec); + + expect(result.items.length).toBe(1); + const request = result.items[0]; + + expect(request.request.body.mode).toBe('multipartForm'); + expect(request.request.body.multipartForm.length).toBe(2); + + const fieldNames = request.request.body.multipartForm.map((f) => f.name); + expect(fieldNames).toContain('file'); + expect(fieldNames).toContain('description'); + }); + + it('should handle number and integer types with correct default values', () => { + const openApiSpec = ` +openapi: "3.0.0" +info: + version: "1.0.0" + title: "Number Type Test" +servers: + - url: "https://api.example.com" +paths: + /orders: + post: + summary: "Create order" + operationId: "createOrder" + requestBody: + content: + application/json: + schema: + properties: + quantity: + type: integer + price: + type: number + discount: + type: number + name: + type: string + active: + type: boolean + responses: + '201': + description: "Order created" +`; + + const result = openApiToBruno(openApiSpec); + + expect(result.items.length).toBe(1); + const request = result.items[0]; + + expect(request.request.body.mode).toBe('json'); + expect(request.request.body.json).not.toBeNull(); + + const bodyJson = JSON.parse(request.request.body.json); + + // integer type should be 0 + expect(bodyJson.quantity).toBe(0); + + // number type should be 0 (not empty string) + expect(bodyJson.price).toBe(0); + expect(bodyJson.discount).toBe(0); + + // string type should be empty string + expect(bodyJson.name).toBe(''); + + // boolean type should be false + expect(bodyJson.active).toBe(false); + }); +});