diff --git a/packages/api-elements/lib/elements/AuthScheme.js b/packages/api-elements/lib/elements/AuthScheme.js index 856f0f337..043f412a8 100644 --- a/packages/api-elements/lib/elements/AuthScheme.js +++ b/packages/api-elements/lib/elements/AuthScheme.js @@ -18,7 +18,7 @@ class AuthScheme extends ArrayElement { /** * @name transitions * @type ArraySlice - * @memberof HttpMessagePayload.prototype + * @memberof AuthScheme.prototype */ get transitions() { return this.children.filter(item => item.element === 'transition'); @@ -27,11 +27,25 @@ class AuthScheme extends ArrayElement { /** * @name members * @type ArraySlice - * @memberof HttpMessagePayload.prototype + * @memberof AuthScheme.prototype */ get members() { return this.children.filter(item => item.element === 'member'); } + + /** + * @name grantTypeValue + * @memberof AuthScheme.prototype + */ + get grantTypeValue() { + const grantType = this.members.find(item => item.key.toValue() === 'grantType'); + + if (grantType && grantType.value) { + return grantType.value.toValue(); + } + + return undefined; + } } module.exports = AuthScheme; diff --git a/packages/api-elements/test/api-description-test.js b/packages/api-elements/test/api-description-test.js index 22bcb18e7..12eb55c46 100644 --- a/packages/api-elements/test/api-description-test.js +++ b/packages/api-elements/test/api-description-test.js @@ -1003,6 +1003,19 @@ describe('API description namespace', () => { }, }, }, + { + element: 'member', + content: { + key: { + element: 'string', + content: 'grantType', + }, + value: { + element: 'string', + content: 'token', + }, + }, + }, { element: 'transition', attributes: { @@ -1039,7 +1052,7 @@ describe('API description namespace', () => { it('should contain members', () => { const { members } = authScheme; - expect(members).to.have.length(1); + expect(members).to.have.length(2); members.forEach((item) => { expect(item).to.be.an.instanceof(MemberElement); }); @@ -1052,6 +1065,56 @@ describe('API description namespace', () => { expect(item).to.be.an.instanceof(Transition); }); }); + + it('should retrieve grant type', () => { + expect(authScheme.grantTypeValue).to.equal('token'); + }); + + it('without grant type should return undefined', () => { + const refracted = { + element: 'Token Auth Scheme', + meta: { + id: { + element: 'string', + content: 'Custom Token Auth', + }, + }, + content: [], + }; + + const element = namespace.fromRefract(refracted); + const authScheme = new AuthScheme(element.content, element.meta, element.attributes); + + expect(authScheme.grantTypeValue).to.be.undefined; + }); + + it('without grant type value should return undefined', () => { + const refracted = { + element: 'Token Auth Scheme', + meta: { + id: { + element: 'string', + content: 'Custom Token Auth', + }, + }, + content: [ + { + element: 'member', + content: { + key: { + element: 'string', + content: 'grantType', + }, + }, + }, + ], + }; + + const element = namespace.fromRefract(refracted); + const authScheme = new AuthScheme(element.content, element.meta, element.attributes); + + expect(authScheme.grantTypeValue).to.be.undefined; + }); }); context('HTTP transaction element', () => { diff --git a/packages/fury-adapter-oas3-parser/STATUS.md b/packages/fury-adapter-oas3-parser/STATUS.md index 82eb2ce24..983c119d4 100644 --- a/packages/fury-adapter-oas3-parser/STATUS.md +++ b/packages/fury-adapter-oas3-parser/STATUS.md @@ -72,7 +72,7 @@ Key: | responses | [~](#responses-object) | | callbacks | [✕](https://github.com/apiaryio/api-elements.js/issues/74) | | deprecated | ✕ | -| security | [✕](https://github.com/apiaryio/api-elements.js/issues/77) | +| security | [✕](https://github.com/apiaryio/api-elements.js/issues/329) | | servers | [✕](https://github.com/apiaryio/api-elements.js/issues/76) | ## Parameter Object diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseComponentsObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseComponentsObject.js index 22e5b88e5..6854f110c 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseComponentsObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseComponentsObject.js @@ -156,9 +156,20 @@ function parseComponentsObject(context, element) { object.forEach((value, key) => { if (value) { - // eslint-disable-next-line no-param-reassign - value.meta.id = key.clone(); - array.push(value); + if (value instanceof namespace.elements.AuthScheme) { + // eslint-disable-next-line no-param-reassign + value.id = key.clone(); + array.push(value); + + return; + } + + // append oauth2 flow names + value.forEach((flow) => { + // eslint-disable-next-line no-param-reassign + flow.id = `${key.toValue()} ${flow.grantTypeValue}`; + array.push(flow); + }); } }); diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowObject.js index 98f6ff0ce..1e2bc114e 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowObject.js @@ -40,11 +40,25 @@ function parseOauthFlowObject(context, object) { return scope; })); + const parseUrl = pipeParseResult(namespace, + parseString(context, name, false), + (url) => { + const transition = new namespace.elements.Transition(); + + transition.href = url.value.clone(); + transition.relation = url.key.clone(); + + // remove 'Url' from key + transition.relation.content = transition.relation.toValue().slice(0, -3); + + return transition; + }); + const parseMember = R.cond([ [hasKey('scopes'), R.compose(parseScopes, getValue)], - [hasKey('refreshUrl'), parseString(context, name, false)], - [hasKey('authorizationUrl'), parseString(context, name, false)], - [hasKey('tokenUrl'), parseString(context, name, false)], + [hasKey('refreshUrl'), parseUrl], + [hasKey('authorizationUrl'), parseUrl], + [hasKey('tokenUrl'), parseUrl], // FIXME Support exposing extensions into parse result [isExtension, () => new namespace.elements.ParseResult()], diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowsObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowsObject.js index 36813b731..e00a966ee 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowsObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOauthFlowsObject.js @@ -72,6 +72,10 @@ function parseOauthFlowsObject(context, object) { authScheme.push(new namespace.elements.Member('grantType', grantTypes[member.key.toValue()])); authScheme.push(member.value.getMember('scopes')); + R.filter(R.is(namespace.elements.Transition), member.value).forEach((item) => { + authScheme.push(item); + }); + return authScheme; })); diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOpenAPIObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOpenAPIObject.js index 671f8170b..d906bb1bb 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOpenAPIObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOpenAPIObject.js @@ -125,13 +125,23 @@ function parseOASObject(context, object) { parseObject(context, name, parseMember, requiredKeys, ['components']), (object) => { const api = object.get('info'); + const components = object.get('components'); + + if (components) { + const schemes = R.or(components.get('securitySchemes'), new namespace.elements.Array()); + + if (!schemes.isEmpty) { + api.push(new namespace.elements.Category( + schemes.content, { classes: ['authSchemes'] } + )); + } + } const resources = object.get('paths'); if (resources) { api.content = api.content.concat(resources.content); } - const components = object.get('components'); if (components) { const schemas = R.or(components.get('schemas'), new namespace.elements.Array()) .content diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOperationObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOperationObject.js index 0dca766c3..99ed152d7 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOperationObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseOperationObject.js @@ -1,8 +1,9 @@ const R = require('ramda'); const { - isMember, isExtension, hasKey, getValue, + isArray, isMember, isExtension, hasKey, getValue, } = require('../../predicates'); const { + createWarning, createUnsupportedMemberWarning, createInvalidMemberWarning, createIdentifierNotUniqueWarning, @@ -13,6 +14,7 @@ const parseObject = require('../parseObject'); const parseString = require('../parseString'); const parseResponsesObject = require('./parseResponsesObject'); const parseParameterObjects = require('./parseParameterObjects'); +const parseSecurityRequirementObject = require('./parseSecurityRequirementObject'); const parseRequestBodyObject = require('./parseRequestBodyObject'); const parseReference = require('../parseReference'); @@ -21,7 +23,7 @@ const parseRequestBodyObjectOrRef = parseReference('requestBodies', parseRequest const name = 'Operation Object'; const requiredKeys = ['responses']; const unsupportedKeys = [ - 'tags', 'externalDocs', 'callbacks', 'deprecated', 'security', + 'tags', 'externalDocs', 'callbacks', 'deprecated', ]; const isUnsupportedKey = R.anyPass(R.map(hasKey, unsupportedKeys)); @@ -102,6 +104,24 @@ function parseOperationObject(context, path, member) { ), ])); + const parseSecurity = pipeParseResult(namespace, + R.unless(isArray, createWarning(namespace, `'${name}' 'security' is not an array`)), + R.compose(R.chain(parseSecurityRequirementObject(context)), R.constructN(1, namespace.elements.Array)), + requirements => requirements.map((requirement) => { + if (requirement.length === 1) { + return requirement.get(0); + } + + const link = new namespace.elements.Link(); + link.relation = 'profile'; + link.href = 'https://github.com/refractproject/rfcs/issues/39'; + + const allOf = new namespace.elements.Extension(requirement.content); + allOf.meta.set('links', new namespace.elements.Array([link])); + + return allOf; + })); + const parseMember = R.cond([ [hasKey('summary'), parseString(context, name, false)], [hasKey('description'), parseCopy(context, name, false)], @@ -109,6 +129,7 @@ function parseOperationObject(context, path, member) { [hasKey('responses'), R.compose(parseResponsesObject(context), getValue)], [hasKey('requestBody'), R.compose(parseRequestBodyObjectOrRef(context), getValue)], [hasKey('parameters'), R.compose(parseParameterObjects(context, name), getValue)], + [hasKey('security'), R.compose(parseSecurity, getValue)], [isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)], @@ -161,6 +182,13 @@ function parseOperationObject(context, path, member) { } } + const security = operation.get('security'); + if (security) { + transactions.forEach((transaction) => { + transaction.attributes.set('authSchemes', security.clone()); + }); + } + return transition; }); diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecurityRequirementObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecurityRequirementObject.js index df6a2ab60..1f705b7bc 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecurityRequirementObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecurityRequirementObject.js @@ -40,6 +40,7 @@ function parseSecurityRequirementObject(context, object) { const parseSecurityRequirement = pipeParseResult(namespace, parseObject(context, name, parseMember), (securityRequirement) => { + // TODO: expand oauth requirements into multiples depending on flows const arr = new namespace.elements.Array([]); securityRequirement.forEach((value, key) => { @@ -47,9 +48,9 @@ function parseSecurityRequirementObject(context, object) { const scopes = value.map(scope => scope.toValue()); if (scopes.length) { - e = new namespace.elements.Object({ scopes }); + e = new namespace.elements.AuthScheme({ scopes }); } else { - e = new namespace.elements.Object({}); + e = new namespace.elements.AuthScheme({}); } e.element = key.toValue(); diff --git a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecuritySchemeObject.js b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecuritySchemeObject.js index 9186516e9..7c8756534 100644 --- a/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecuritySchemeObject.js +++ b/packages/fury-adapter-oas3-parser/lib/parser/oas/parseSecuritySchemeObject.js @@ -10,21 +10,22 @@ const { const pipeParseResult = require('../../pipeParseResult'); const parseObject = require('../parseObject'); const parseString = require('../parseString'); +const parseOauthFlowsObject = require('./parseOauthFlowsObject'); const name = 'Security Scheme Object'; const requiredKeys = ['type']; const unsupportedKeys = ['bearerFormat', 'openIdConnectUrl']; const isUnsupportedKey = R.anyPass(R.map(hasKey, unsupportedKeys)); -const passThrough = R.anyPass(R.map(hasKey, ['name', 'in', 'scheme', 'flows'])); +const outerPassThrough = R.anyPass(R.map(hasKey, ['name', 'in', 'scheme', 'flows'])); +const innerPassThrough = R.anyPass(R.map(hasKey, ['type', 'description'])); const isApiKeyScheme = securityScheme => securityScheme.getValue('type') === 'apiKey'; const isHttpScheme = securityScheme => securityScheme.getValue('type') === 'http'; -// const isOauth2Scheme = securityScheme => securityScheme.getValue('type') === 'oauth2'; +const isOauth2Scheme = securityScheme => securityScheme.getValue('type') === 'oauth2'; const isValidTypeValue = R.anyPass(R.map(hasValue, ['apiKey', 'http', 'oauth2', 'openIdConnect'])); const isSupportedType = R.anyPass(R.map(hasValue, ['apiKey', 'http', 'oauth2'])); const isValidInValue = R.anyPass(R.map(hasValue, ['query', 'header', 'cookie'])); -const isSupportedIn = R.anyPass(R.map(hasValue, ['query', 'header'])); function validateApiKeyScheme(context, securityScheme) { const { namespace } = context; @@ -35,35 +36,56 @@ function validateApiKeyScheme(context, securityScheme) { ); const validateIn = R.unless(isValidInValue, createInvalidInWarning); - const createUnsupportedInWarning = member => createWarning(namespace, - `'${name}' 'in' '${member.value.toValue()}' is unsupported`, member.value); - const ensureSupportedIn = R.unless(isSupportedIn, createUnsupportedInWarning); - const parseIn = pipeParseResult(namespace, parseString(context, name, false), - validateIn, - ensureSupportedIn); + validateIn); const parseMember = R.cond([ [hasKey('name'), parseString(context, name, false)], [hasKey('in'), parseIn], - [R.T, e => e], + [innerPassThrough, e => e], + [isUnsupportedKey, e => e], + [isExtension, e => e], + + [R.T, createInvalidMemberWarning(namespace, `${name}' 'apiKey`)], ]); return parseObject(context, name, parseMember, ['name', 'in'], [], true)(securityScheme); } function validateHttpScheme(context, securityScheme) { + const { namespace } = context; + const parseMember = R.cond([ [hasKey('scheme'), parseString(context, name, false)], - [R.T, e => e], + [innerPassThrough, e => e], + [isUnsupportedKey, e => e], + [isExtension, e => e], + + [R.T, createInvalidMemberWarning(namespace, `${name}' 'http`)], ]); return parseObject(context, name, parseMember, ['scheme'], [], true)(securityScheme); } +function validateOauth2Scheme(context, securityScheme) { + const { namespace } = context; + + const parseMember = R.cond([ + [hasKey('flows'), R.compose(parseOauthFlowsObject(context), getValue)], + + [innerPassThrough, e => e], + [isUnsupportedKey, e => e], + [isExtension, e => e], + + [R.T, createInvalidMemberWarning(namespace, `${name}' 'oauth2`)], + ]); + + return parseObject(context, name, parseMember, ['flows'], [], true)(securityScheme); +} + /** * Parse Security Scheme Object * @@ -95,7 +117,7 @@ function parseSecuritySchemeObject(context, object) { const parseMember = R.cond([ [hasKey('type'), parseType], [hasKey('description'), parseString(context, name, false)], - [passThrough, e => e], + [outerPassThrough, e => e], [isUnsupportedKey, createUnsupportedMemberWarning(namespace, name)], @@ -110,20 +132,35 @@ function parseSecuritySchemeObject(context, object) { parseObject(context, name, parseMember, requiredKeys, [], true), R.when(isApiKeyScheme, R.curry(validateApiKeyScheme)(context)), R.when(isHttpScheme, R.curry(validateHttpScheme)(context)), - // R.when(isOauth2Scheme, parseSecuritySchemeFlowsObject), + R.when(isOauth2Scheme, R.curry(validateOauth2Scheme)(context)), (securityScheme) => { const authScheme = new namespace.elements.AuthScheme(); const type = securityScheme.getValue('type'); const scheme = securityScheme.getValue('scheme'); + const description = securityScheme.get('description'); + + if (type === 'oauth2') { + const flows = securityScheme.get('flows'); + + if (description) { + flows.forEach((flow) => { + // eslint-disable-next-line no-param-reassign + flow.description = description.clone(); + }); + } + + return flows; + } if (type === 'apiKey' || (type === 'http' && scheme === 'bearer')) { authScheme.element = 'Token Authentication Scheme'; } else if (type === 'http' && scheme === 'basic') { authScheme.element = 'Basic Authentication Scheme'; + } else { + throw new Error(`Invalid security Scheme '${type}' '${scheme}'`); } - const description = securityScheme.get('description'); if (description) { authScheme.description = description; } @@ -136,6 +173,8 @@ function parseSecuritySchemeObject(context, object) { key = 'httpHeaderName'; } else if (inValue === 'query') { key = 'queryParameterName'; + } else if (inValue === 'cookie') { + key = 'cookieName'; } authScheme.push(new namespace.elements.Member(key, securityScheme.get('name'))); diff --git a/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.json b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.json new file mode 100644 index 000000000..0ca6d511e --- /dev/null +++ b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.json @@ -0,0 +1,141 @@ +{ + "element": "parseResult", + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "api" + } + ] + }, + "title": { + "element": "string", + "content": "Auth Scheme" + } + }, + "attributes": { + "version": { + "element": "string", + "content": "1.0.0" + } + }, + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "authSchemes" + } + ] + } + }, + "content": [ + { + "element": "Basic Authentication Scheme", + "meta": { + "id": { + "element": "string", + "content": "basic" + } + } + } + ] + }, + { + "element": "resource", + "attributes": { + "href": { + "element": "string", + "content": "/user" + } + }, + "content": [ + { + "element": "transition", + "meta": { + "title": { + "element": "string", + "content": "View the current User" + } + }, + "content": [ + { + "element": "httpTransaction", + "attributes": { + "authSchemes": { + "element": "array", + "content": [ + { + "element": "basic" + } + ] + } + }, + "content": [ + { + "element": "httpRequest", + "attributes": { + "method": { + "element": "string", + "content": "GET" + } + } + }, + { + "element": "httpResponse", + "attributes": { + "headers": { + "element": "httpHeaders", + "content": [ + { + "element": "member", + "content": { + "key": { + "element": "string", + "content": "Content-Type" + }, + "value": { + "element": "string", + "content": "application/json" + } + } + } + ] + }, + "statusCode": { + "element": "string", + "content": "200" + } + }, + "content": [ + { + "element": "dataStructure", + "content": { + "element": "string" + } + }, + { + "element": "copy", + "content": "A user" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.sourcemap.json b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.sourcemap.json new file mode 100644 index 000000000..4ebf595a5 --- /dev/null +++ b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.sourcemap.json @@ -0,0 +1,316 @@ +{ + "element": "parseResult", + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "api" + } + ] + }, + "title": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 49 + }, + { + "element": "number", + "content": 11 + } + ] + } + ] + } + ] + } + }, + "content": "Auth Scheme" + } + }, + "attributes": { + "version": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 34 + }, + { + "element": "number", + "content": 5 + } + ] + } + ] + } + ] + } + }, + "content": "1.0.0" + } + }, + "content": [ + { + "element": "category", + "meta": { + "classes": { + "element": "array", + "content": [ + { + "element": "string", + "content": "authSchemes" + } + ] + } + }, + "content": [ + { + "element": "Basic Authentication Scheme", + "meta": { + "id": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 356 + }, + { + "element": "number", + "content": 5 + } + ] + } + ] + } + ] + } + }, + "content": "basic" + } + } + } + ] + }, + { + "element": "resource", + "attributes": { + "href": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 70 + }, + { + "element": "number", + "content": 5 + } + ] + } + ] + } + ] + } + }, + "content": "/user" + } + }, + "content": [ + { + "element": "transition", + "meta": { + "title": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 101 + }, + { + "element": "number", + "content": 21 + } + ] + } + ] + } + ] + } + }, + "content": "View the current User" + } + }, + "content": [ + { + "element": "httpTransaction", + "attributes": { + "authSchemes": { + "element": "array", + "content": [ + { + "element": "basic" + } + ] + } + }, + "content": [ + { + "element": "httpRequest", + "attributes": { + "method": { + "element": "string", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 81 + }, + { + "element": "number", + "content": 3 + } + ] + } + ] + } + ] + } + }, + "content": "GET" + } + } + }, + { + "element": "httpResponse", + "attributes": { + "headers": { + "element": "httpHeaders", + "content": [ + { + "element": "member", + "content": { + "key": { + "element": "string", + "content": "Content-Type" + }, + "value": { + "element": "string", + "content": "application/json" + } + } + } + ] + }, + "statusCode": { + "element": "string", + "content": "200" + } + }, + "content": [ + { + "element": "dataStructure", + "content": { + "element": "string" + } + }, + { + "element": "copy", + "attributes": { + "sourceMap": { + "element": "array", + "content": [ + { + "element": "sourceMap", + "content": [ + { + "element": "array", + "content": [ + { + "element": "number", + "content": 214 + }, + { + "element": "number", + "content": 6 + } + ] + } + ] + } + ] + } + }, + "content": "A user" + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.yaml b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.yaml new file mode 100644 index 000000000..0e37e9884 --- /dev/null +++ b/packages/fury-adapter-oas3-parser/test/integration/fixtures/auth-scheme.yaml @@ -0,0 +1,22 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Auth Scheme +paths: + /user: + get: + summary: View the current User + security: + - basic: [] + responses: + '200': + description: A user + content: + application/json: + schema: + type: string +components: + securitySchemes: + basic: + type: http + scheme: basic diff --git a/packages/fury-adapter-oas3-parser/test/integration/parse-test.js b/packages/fury-adapter-oas3-parser/test/integration/parse-test.js index 1f3254068..1e906b6ef 100644 --- a/packages/fury-adapter-oas3-parser/test/integration/parse-test.js +++ b/packages/fury-adapter-oas3-parser/test/integration/parse-test.js @@ -11,4 +11,14 @@ describe('#parse', () => { const file = path.join(__dirname, 'fixtures', 'petstore'); return testParseFixture(file, true); }); + + it('can parse auth schemes', () => { + const file = path.join(__dirname, 'fixtures', 'auth-scheme'); + return testParseFixture(file); + }); + + it('can parse auth schemes generating source maps', () => { + const file = path.join(__dirname, 'fixtures', 'auth-scheme'); + return testParseFixture(file, true); + }); }); diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseComponentsObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseComponentsObject-test.js index 11f132c7e..58f8d6a48 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseComponentsObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseComponentsObject-test.js @@ -340,7 +340,40 @@ describe('Components Object', () => { const securitySchemes = parsedComponents.get('securitySchemes'); expect(securitySchemes).to.be.instanceof(namespace.elements.Array); expect(securitySchemes.get(0)).to.be.instanceof(namespace.elements.AuthScheme); - expect(securitySchemes.get(0).meta.id.toValue()).to.equal('token'); + expect(securitySchemes.get(0).id.toValue()).to.equal('token'); + }); + + it('parses oauth2 securityScheme with multiple flows', () => { + const components = new namespace.elements.Object({ + securitySchemes: { + oauth: { + type: 'oauth2', + flows: { + password: { + tokenUrl: '/token', + scopes: {}, + }, + implicit: { + authorizationUrl: '/authorization', + scopes: {}, + }, + }, + }, + }, + }); + + const parseResult = parse(context, components); + expect(parseResult.length).to.equal(1); + + const parsedComponents = parseResult.get(0); + expect(parsedComponents).to.be.instanceof(namespace.elements.Object); + + const securitySchemes = parsedComponents.get('securitySchemes'); + expect(securitySchemes).to.be.instanceof(namespace.elements.Array); + expect(securitySchemes.get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(securitySchemes.get(0).id.toValue()).to.equal('oauth resource owner password credentials'); + expect(securitySchemes.get(1)).to.be.instanceof(namespace.elements.AuthScheme); + expect(securitySchemes.get(1).id.toValue()).to.equal('oauth implicit'); }); it('handles invalid security scheme', () => { diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowObject-test.js index 04740002c..2c6a91a40 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowObject-test.js @@ -99,6 +99,24 @@ describe('Oauth Flow Object', () => { expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array); expect(parseResult.get(0).get('scopes').length).to.equal(0); }); + + it('parses it correctly', () => { + const oauthFlow = new namespace.elements.Object({ + scopes: {}, + refreshUrl: '/refresh', + }); + + const parseResult = parse(context, oauthFlow); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object); + + const refreshUrl = parseResult.get(0).get('refreshUrl'); + + expect(refreshUrl).to.be.instanceof(namespace.elements.Transition); + expect(refreshUrl.relation.toValue()).to.equal('refresh'); + expect(refreshUrl.href.toValue()).to.equal('/refresh'); + }); }); describe('#tokenUrl', () => { @@ -117,6 +135,24 @@ describe('Oauth Flow Object', () => { expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array); expect(parseResult.get(0).get('scopes').length).to.equal(0); }); + + it('parses it correctly', () => { + const oauthFlow = new namespace.elements.Object({ + scopes: {}, + tokenUrl: '/token', + }); + + const parseResult = parse(context, oauthFlow); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object); + + const tokenUrl = parseResult.get(0).get('tokenUrl'); + + expect(tokenUrl).to.be.instanceof(namespace.elements.Transition); + expect(tokenUrl.relation.toValue()).to.equal('token'); + expect(tokenUrl.href.toValue()).to.equal('/token'); + }); }); describe('#authorizationUrl', () => { @@ -135,6 +171,24 @@ describe('Oauth Flow Object', () => { expect(parseResult.get(0).get('scopes')).to.be.instanceof(namespace.elements.Array); expect(parseResult.get(0).get('scopes').length).to.equal(0); }); + + it('parses it correctly', () => { + const oauthFlow = new namespace.elements.Object({ + scopes: {}, + authorizationUrl: '/authorization', + }); + + const parseResult = parse(context, oauthFlow); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Object); + + const authorizationUrl = parseResult.get(0).get('authorizationUrl'); + + expect(authorizationUrl).to.be.instanceof(namespace.elements.Transition); + expect(authorizationUrl.relation.toValue()).to.equal('authorization'); + expect(authorizationUrl.href.toValue()).to.equal('/authorization'); + }); }); it('provides warning for invalid keys', () => { diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowsObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowsObject-test.js index e0966753e..cc9540eeb 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowsObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOauthFlowsObject-test.js @@ -36,17 +36,35 @@ describe('Oauth Flows Object', () => { expect(parseResult).to.contain.warning("'Oauth Flows Object' 'implicit' is missing required property 'authorizationUrl'"); }); - it.skip('parses correctly', () => { + it('parses correctly', () => { const oauthFlows = new namespace.elements.Object({ implicit: { - authorizationUrl: '/authorize', + authorizationUrl: '/authorization', scopes: {}, }, }); const parseResult = parse(context, oauthFlows); - console.log(JSON.stringify(parseResult, null, 2)); + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(1); + + const authScheme = parseResult.get(0).get(0); + + expect(authScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(authScheme.length).to.equal(3); + + expect(authScheme.get(0)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(0).key.toValue()).to.equal('grantType'); + expect(authScheme.get(0).value.toValue()).to.equal('implicit'); + + expect(authScheme.get(1)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(1).key.toValue()).to.equal('scopes'); + expect(authScheme.get(1).value).to.be.instanceof(namespace.elements.Array); + + expect(authScheme.get(2)).to.be.instanceof(namespace.elements.Transition); + expect(authScheme.get(2).href.toValue()).to.equal('/authorization'); }); }); @@ -65,6 +83,37 @@ describe('Oauth Flows Object', () => { expect(parseResult.get(0).length).to.equal(0); expect(parseResult).to.contain.warning("'Oauth Flows Object' 'password' is missing required property 'tokenUrl'"); }); + + it('parses correctly', () => { + const oauthFlows = new namespace.elements.Object({ + password: { + tokenUrl: '/token', + scopes: {}, + }, + }); + + const parseResult = parse(context, oauthFlows); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(1); + + const authScheme = parseResult.get(0).get(0); + + expect(authScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(authScheme.length).to.equal(3); + + expect(authScheme.get(0)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(0).key.toValue()).to.equal('grantType'); + expect(authScheme.get(0).value.toValue()).to.equal('resource owner password credentials'); + + expect(authScheme.get(1)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(1).key.toValue()).to.equal('scopes'); + expect(authScheme.get(1).value).to.be.instanceof(namespace.elements.Array); + + expect(authScheme.get(2)).to.be.instanceof(namespace.elements.Transition); + expect(authScheme.get(2).href.toValue()).to.equal('/token'); + }); }); describe('#clientCredentials', () => { @@ -82,6 +131,37 @@ describe('Oauth Flows Object', () => { expect(parseResult.get(0).length).to.equal(0); expect(parseResult).to.contain.warning("'Oauth Flows Object' 'clientCredentials' is missing required property 'tokenUrl'"); }); + + it('parses correctly', () => { + const oauthFlows = new namespace.elements.Object({ + clientCredentials: { + tokenUrl: '/token', + scopes: {}, + }, + }); + + const parseResult = parse(context, oauthFlows); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(1); + + const authScheme = parseResult.get(0).get(0); + + expect(authScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(authScheme.length).to.equal(3); + + expect(authScheme.get(0)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(0).key.toValue()).to.equal('grantType'); + expect(authScheme.get(0).value.toValue()).to.equal('client credentials'); + + expect(authScheme.get(1)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(1).key.toValue()).to.equal('scopes'); + expect(authScheme.get(1).value).to.be.instanceof(namespace.elements.Array); + + expect(authScheme.get(2)).to.be.instanceof(namespace.elements.Transition); + expect(authScheme.get(2).href.toValue()).to.equal('/token'); + }); }); describe('#authorizationCode', () => { @@ -103,7 +183,7 @@ describe('Oauth Flows Object', () => { it('provides warning when no tokenUrl', () => { const oauthFlows = new namespace.elements.Object({ authorizationCode: { - authorizationUrl: '/authorize', + authorizationUrl: '/authorization', scopes: {}, }, }); @@ -115,9 +195,71 @@ describe('Oauth Flows Object', () => { expect(parseResult.get(0).length).to.equal(0); expect(parseResult).to.contain.warning("'Oauth Flows Object' 'authorizationCode' is missing required property 'tokenUrl'"); }); + + it('parses correctly', () => { + const oauthFlows = new namespace.elements.Object({ + authorizationCode: { + authorizationUrl: '/authorization', + tokenUrl: '/token', + scopes: {}, + }, + }); + + const parseResult = parse(context, oauthFlows); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(1); + + const authScheme = parseResult.get(0).get(0); + + expect(authScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(authScheme.length).to.equal(4); + + expect(authScheme.get(0)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(0).key.toValue()).to.equal('grantType'); + expect(authScheme.get(0).value.toValue()).to.equal('authorization code'); + + expect(authScheme.get(1)).to.be.instanceof(namespace.elements.Member); + expect(authScheme.get(1).key.toValue()).to.equal('scopes'); + expect(authScheme.get(1).value).to.be.instanceof(namespace.elements.Array); + + expect(authScheme.get(2)).to.be.instanceof(namespace.elements.Transition); + expect(authScheme.get(2).href.toValue()).to.equal('/authorization'); + + expect(authScheme.get(3)).to.be.instanceof(namespace.elements.Transition); + expect(authScheme.get(3).href.toValue()).to.equal('/token'); + }); }); - // TODO: Multiple flows + it('parses multiple flows', () => { + const oauthFlows = new namespace.elements.Object({ + implicit: { + authorizationUrl: '/authorization', + scopes: {}, + }, + password: { + tokenUrl: '/token', + scopes: {}, + }, + }); + + const parseResult = parse(context, oauthFlows); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(2); + + const implicitScheme = parseResult.get(0).get(0); + + expect(implicitScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(implicitScheme.length).to.equal(3); + + const passwordScheme = parseResult.get(0).get(1); + + expect(passwordScheme).to.be.instanceof(namespace.elements.AuthScheme); + expect(passwordScheme.length).to.equal(3); + }); it('provides warning for invalid keys', () => { const oauthFlows = new namespace.elements.Object({ diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOperationObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOperationObject-test.js index bf06b783c..10053ed13 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOperationObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseOperationObject-test.js @@ -105,17 +105,6 @@ describe('Operation Object', () => { expect(parseResult).to.contain.warning("'Operation Object' contains unsupported key 'deprecated'"); }); - it('provides warning for unsupported security key', () => { - const operation = new namespace.elements.Member('get', { - security: '', - responses: {}, - }); - - const parseResult = parse(context, path, operation); - - expect(parseResult).to.contain.warning("'Operation Object' contains unsupported key 'security'"); - }); - it('does not provide warning/errors for extensions', () => { const operation = new namespace.elements.Member('get', { responses: {}, @@ -504,6 +493,97 @@ describe('Operation Object', () => { }); }); + describe('#security', () => { + it('warns when security is not an array', () => { + const operation = new namespace.elements.Member('get', { + security: {}, + responses: {}, + }); + + const parseResult = parse(context, path, operation); + + expect(parseResult.length).to.equal(2); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Transition); + + expect(parseResult).to.contain.warning("'Operation Object' 'security' is not an array"); + }); + + it('parses correctly when there is a security requirement', () => { + const operation = new namespace.elements.Member('get', { + security: [ + { + apiKey: [], + }, + ], + responses: { + 200: { + description: 'dummy', + }, + }, + }); + + const parseResult = parse(context, path, operation); + + expect(parseResult.length).to.equal(1); + + const transition = parseResult.get(0); + + expect(transition).to.be.instanceof(namespace.elements.Transition); + expect(transition.transactions.length).to.equal(1); + + const transaction = transition.transactions.get(0); + const { authSchemes } = transaction; + + expect(authSchemes).to.be.instanceof(namespace.elements.Array); + expect(authSchemes.length).to.equal(1); + expect(authSchemes.get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(authSchemes.get(0).element).to.equal('apiKey'); + }); + + it('parses correctly when there are multiple security requirement', () => { + const operation = new namespace.elements.Member('get', { + security: [ + { + apiKey: [], + }, + { + custom1: [], + custom2: [], + }, + ], + responses: { + 200: { + description: 'dummy', + }, + }, + }); + + const parseResult = parse(context, path, operation); + + expect(parseResult.length).to.equal(1); + + const transition = parseResult.get(0); + + expect(transition).to.be.instanceof(namespace.elements.Transition); + expect(transition.transactions.length).to.equal(1); + + const transaction = transition.transactions.get(0); + const { authSchemes } = transaction; + + expect(authSchemes).to.be.instanceof(namespace.elements.Array); + expect(authSchemes.length).to.equal(2); + expect(authSchemes.get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(authSchemes.get(0).element).to.equal('apiKey'); + expect(authSchemes.get(1)).to.be.instanceof(namespace.elements.Extension); + expect(authSchemes.get(1).content.length).to.equal(2); + // TODO: Remove content[n] and use get(n) when moved to allOf element + expect(authSchemes.get(1).content[0]).to.be.instanceof(namespace.elements.AuthScheme); + expect(authSchemes.get(1).content[0].element).to.equal('custom1'); + expect(authSchemes.get(1).content[1]).to.be.instanceof(namespace.elements.AuthScheme); + expect(authSchemes.get(1).content[1].element).to.equal('custom2'); + }); + }); + describe('#responses', () => { it('returns a transition including a transaction', () => { const operation = new namespace.elements.Member('get', { diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecurityRequirementObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecurityRequirementObject-test.js index 859440a71..ed740409d 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecurityRequirementObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecurityRequirementObject-test.js @@ -70,7 +70,7 @@ describe('Security Requirement Object', () => { expect(arr.get(0).element).to.equal('customOauth2'); expect(arr.get(0).length).to.equal(1); - const scopes = arr.get(0).get('scopes'); + const scopes = arr.get(0).get(0).value; expect(scopes).to.be.instanceof(namespace.elements.Array); expect(scopes.length).to.equal(2); @@ -97,7 +97,7 @@ describe('Security Requirement Object', () => { expect(arr.get(0).element).to.equal('customOauth2'); expect(arr.get(0).length).to.equal(1); - const scopes = arr.get(0).get('scopes'); + const scopes = arr.get(0).get(0).value; expect(scopes).to.be.instanceof(namespace.elements.Array); expect(scopes.length).to.equal(1); diff --git a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecuritySchemeObject-test.js b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecuritySchemeObject-test.js index 0167ca429..b1fc5c984 100644 --- a/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecuritySchemeObject-test.js +++ b/packages/fury-adapter-oas3-parser/test/unit/parser/oas/parseSecuritySchemeObject-test.js @@ -221,7 +221,7 @@ describe('Security Scheme Object', () => { expect(members.get(0).value.toValue()).to.equal('example'); }); - it('provides warning about unsupported cookie', () => { + it('parses correctly when in a cookie', () => { const securityScheme = new namespace.elements.Object({ type: 'apiKey', name: 'example', @@ -231,10 +231,17 @@ describe('Security Scheme Object', () => { const parseResult = parse(context, securityScheme); expect(parseResult.length).to.equal(1); - expect(parseResult).to.contain.warning("'Security Scheme Object' 'in' 'cookie' is unsupported"); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(parseResult.get(0).element).to.equal('Token Authentication Scheme'); + + const { members } = parseResult.get(0); + + expect(members.length).to.equal(1); + expect(members.get(0).key.toValue()).to.equal('cookieName'); + expect(members.get(0).value.toValue()).to.equal('example'); }); - it('does not complain about scheme', () => { + it('provides warning for invalid scheme', () => { const securityScheme = new namespace.elements.Object({ type: 'apiKey', name: 'example', @@ -244,8 +251,112 @@ describe('Security Scheme Object', () => { const parseResult = parse(context, securityScheme); + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'apiKey' contains invalid key 'scheme'"); + }); + + it('provides warning for invalid flows', () => { + const securityScheme = new namespace.elements.Object({ + type: 'apiKey', + name: 'example', + in: 'query', + flows: 1, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'apiKey' contains invalid key 'flows'"); + }); + }); + + describe('when type is oauth2', () => { + it('parses correctly when single flow', () => { + const securityScheme = new namespace.elements.Object({ + type: 'oauth2', + flows: { + password: { + tokenUrl: '/token', + scopes: {}, + }, + }, + }); + + const parseResult = parse(context, securityScheme); + expect(parseResult.length).to.equal(1); - expect(parseResult).to.not.contain.annotations; + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(1); + expect(parseResult.get(0).get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(parseResult.get(0).get(0).element).to.equal('Oauth2 Scheme'); + }); + + it('parses correctly when multiple flows', () => { + const securityScheme = new namespace.elements.Object({ + type: 'oauth2', + description: 'oauth2 implementation', + flows: { + password: { + tokenUrl: '/token', + scopes: {}, + }, + implicit: { + authorizationUrl: '/authorization', + scopes: {}, + }, + }, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(1); + expect(parseResult.get(0)).to.be.instanceof(namespace.elements.Array); + expect(parseResult.get(0).length).to.equal(2); + expect(parseResult.get(0).get(0)).to.be.instanceof(namespace.elements.AuthScheme); + expect(parseResult.get(0).get(0).element).to.equal('Oauth2 Scheme'); + expect(parseResult.get(0).get(0).description.toValue()).to.equal('oauth2 implementation'); + expect(parseResult.get(0).get(1)).to.be.instanceof(namespace.elements.AuthScheme); + expect(parseResult.get(0).get(1).element).to.equal('Oauth2 Scheme'); + expect(parseResult.get(0).get(1).description.toValue()).to.equal('oauth2 implementation'); + }); + + it('provides warning for invalid name', () => { + const securityScheme = new namespace.elements.Object({ + type: 'oauth2', + flows: {}, + name: 1, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'oauth2' contains invalid key 'name'"); + }); + + it('provides warning for invalid in', () => { + const securityScheme = new namespace.elements.Object({ + type: 'oauth2', + flows: {}, + in: 1, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'oauth2' contains invalid key 'in'"); + }); + + it('provides warning for invalid scheme', () => { + const securityScheme = new namespace.elements.Object({ + type: 'oauth2', + flows: {}, + scheme: 1, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'oauth2' contains invalid key 'scheme'"); }); }); @@ -264,7 +375,7 @@ describe('Security Scheme Object', () => { expect(parseResult.get(0).members.length).to.equal(0); }); - it('does not complain about name', () => { + it('provides warning for invalid name', () => { const securityScheme = new namespace.elements.Object({ type: 'http', scheme: 'basic', @@ -273,11 +384,11 @@ describe('Security Scheme Object', () => { const parseResult = parse(context, securityScheme); - expect(parseResult.length).to.equal(1); - expect(parseResult).to.not.contain.annotations; + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'http' contains invalid key 'name'"); }); - it('does not complain about in', () => { + it('provides warning for invalid in', () => { const securityScheme = new namespace.elements.Object({ type: 'http', scheme: 'basic', @@ -286,8 +397,21 @@ describe('Security Scheme Object', () => { const parseResult = parse(context, securityScheme); - expect(parseResult.length).to.equal(1); - expect(parseResult).to.not.contain.annotations; + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'http' contains invalid key 'in'"); + }); + + it('provides warning for invalid flows', () => { + const securityScheme = new namespace.elements.Object({ + type: 'http', + scheme: 'basic', + flows: 1, + }); + + const parseResult = parse(context, securityScheme); + + expect(parseResult.length).to.equal(2); + expect(parseResult).to.contain.warning("'Security Scheme Object' 'http' contains invalid key 'flows'"); }); });