diff --git a/packages/bruno-app/src/utils/exporters/openapi-spec.js b/packages/bruno-app/src/utils/exporters/openapi-spec.js index 4e9802056..d9ed25e0e 100644 --- a/packages/bruno-app/src/utils/exporters/openapi-spec.js +++ b/packages/bruno-app/src/utils/exporters/openapi-spec.js @@ -390,10 +390,11 @@ export const exportApiSpec = ({ variables, items, name, environments }) => { [componentId]: [] }; break; - case 'oauth2': + case 'oauth2': { if (!auth?.oauth2?.grantType) break; componentId = getItemComponentId(); const { authorizationUrl, accessTokenUrl, callbackUrl, scope } = auth?.oauth2; + const scopes = scope ? { [scope]: '' } : {}; switch (auth?.oauth2?.grantType) { case 'authorization_code': components.securitySchemes[componentId] = { @@ -402,13 +403,7 @@ export const exportApiSpec = ({ variables, items, name, environments }) => { authorizationCode: { authorizationUrl, tokenUrl: accessTokenUrl, - ...(scope.length > 0 - ? { - scopes: { - [scope]: '' - } - } - : {}) + scopes } } }; @@ -422,13 +417,7 @@ export const exportApiSpec = ({ variables, items, name, environments }) => { flows: { password: { tokenUrl: accessTokenUrl, - ...(scope.length > 0 - ? { - scopes: { - [scope]: '' - } - } - : {}) + scopes } } }; @@ -442,13 +431,7 @@ export const exportApiSpec = ({ variables, items, name, environments }) => { flows: { password: { tokenUrl: accessTokenUrl, - ...(scope.length > 0 - ? { - scopes: { - [scope]: '' - } - } - : {}) + scopes } } }; @@ -458,6 +441,7 @@ export const exportApiSpec = ({ variables, items, name, environments }) => { break; } break; + } case 'awsv4': componentId = getItemComponentId(); components.securitySchemes[componentId] = { diff --git a/packages/bruno-app/src/utils/exporters/openapi-spec.spec.js b/packages/bruno-app/src/utils/exporters/openapi-spec.spec.js index 8ac0f58ac..87f668e20 100644 --- a/packages/bruno-app/src/utils/exporters/openapi-spec.spec.js +++ b/packages/bruno-app/src/utils/exporters/openapi-spec.spec.js @@ -816,3 +816,68 @@ describe('exportApiSpec - multi-environment servers', () => { expect(content).toContain('description: Staging'); }); }); + +describe('exportApiSpec - OAuth2 scope handling (BRU-3297)', () => { + const makeOauth2Item = (grantType, scope) => ({ + name: 'Req', + type: 'http-request', + request: { + url: 'https://api.example.com/users', + method: 'GET', + params: [], + headers: [], + body: {}, + auth: { + mode: 'oauth2', + oauth2: { + grantType, + authorizationUrl: 'https://auth.example.com/authorize', + accessTokenUrl: 'https://auth.example.com/token', + callbackUrl: 'https://app.example.com/callback', + scope + } + } + } + }); + + // No-throw checks for all 3 grant types affected by BRU-3297. + describe.each([ + 'authorization_code', + 'password', + 'client_credentials' + ])('grant type %s', (grantType) => { + it(`should not throw when scope is null`, () => { + const items = [makeOauth2Item(grantType, null)]; + expect(() => exportApiSpec({ variables: {}, items, name: 'Test' })).not.toThrow(); + }); + + it(`should not throw when scope is undefined`, () => { + const items = [makeOauth2Item(grantType, undefined)]; + expect(() => exportApiSpec({ variables: {}, items, name: 'Test' })).not.toThrow(); + }); + }); + + describe.each([ + ['authorization_code', 'authorizationCode'], + ['password', 'password'] + ])('grant type %s emits valid scopes object', (grantType, flowKey) => { + it(`should emit empty scopes object when scope is null (OpenAPI 3.0 requires scopes key)`, () => { + const items = [makeOauth2Item(grantType, null)]; + const { content } = exportApiSpec({ variables: {}, items, name: 'Test' }); + expect(content).toContain(`${flowKey}:`); + expect(content).toMatch(new RegExp(`${flowKey}:[\\s\\S]*?scopes:\\s*{}`)); + }); + + it(`should emit empty scopes object when scope is empty string`, () => { + const items = [makeOauth2Item(grantType, '')]; + const { content } = exportApiSpec({ variables: {}, items, name: 'Test' }); + expect(content).toMatch(new RegExp(`${flowKey}:[\\s\\S]*?scopes:\\s*{}`)); + }); + + it(`should emit scope entry when scope is a non-empty string`, () => { + const items = [makeOauth2Item(grantType, 'openid')]; + const { content } = exportApiSpec({ variables: {}, items, name: 'Test' }); + expect(content).toContain('openid:'); + }); + }); +});