diff --git a/src/models/operation-trait.ts b/src/models/operation-trait.ts index baeae0f32..e17bc79a2 100644 --- a/src/models/operation-trait.ts +++ b/src/models/operation-trait.ts @@ -2,6 +2,7 @@ import type { BaseModel } from "./base"; import type { BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface, ExternalDocumentationMixinInterface, TagsMixinInterface } from './mixins'; import type { OperationAction } from "./operation"; import type { SecuritySchemeInterface } from "./security-scheme"; +import { SecurityRequirements } from "./v2/security-requirements"; export interface OperationTraitInterface extends BaseModel, BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface, ExternalDocumentationMixinInterface, TagsMixinInterface { id(): string; @@ -10,5 +11,5 @@ export interface OperationTraitInterface extends BaseModel, BindingsMixinInterfa operationId(): string | undefined; hasSummary(): boolean; summary(): string | undefined; - security(): Array>; + security(): SecurityRequirements[]; } diff --git a/src/models/security-requirement.ts b/src/models/security-requirement.ts new file mode 100644 index 000000000..777e6c04f --- /dev/null +++ b/src/models/security-requirement.ts @@ -0,0 +1,7 @@ +import type { BaseModel } from "./base"; +import type { SecuritySchemeInterface } from "./security-scheme"; + +export interface SecurityRequirementInterface extends BaseModel { + scheme(): SecuritySchemeInterface + scopes(): string[]; +} diff --git a/src/models/security-requirements.ts b/src/models/security-requirements.ts new file mode 100644 index 000000000..3efc07283 --- /dev/null +++ b/src/models/security-requirements.ts @@ -0,0 +1,4 @@ +import type { Collection} from './collection'; +import type { SecurityRequirementInterface } from './security-requirement'; + +export interface SecurityRequirementsInterface extends Collection {} \ No newline at end of file diff --git a/src/models/server.ts b/src/models/server.ts index e8e302636..ccec3391a 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -4,7 +4,7 @@ import type { MessagesInterface } from './messages' import type { BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface } from './mixins'; import type { OperationsInterface } from './operations' import type { ServerVariablesInterface } from "./server-variables"; -import type { SecuritySchemeInterface } from "./security-scheme"; +import type { SecurityRequirementsInterface } from "./security-requirements"; export interface ServerInterface extends BaseModel, DescriptionMixinInterface, BindingsMixinInterface, ExtensionsMixinInterface { id(): string @@ -16,5 +16,5 @@ export interface ServerInterface extends BaseModel, DescriptionMixinInterface, B operations(): OperationsInterface; messages(): MessagesInterface; variables(): ServerVariablesInterface; - security(): Array>; + security(): SecurityRequirementsInterface[]; } diff --git a/src/models/v2/operation-trait.ts b/src/models/v2/operation-trait.ts index 6a24b2e5d..c46a38a2c 100644 --- a/src/models/v2/operation-trait.ts +++ b/src/models/v2/operation-trait.ts @@ -12,6 +12,8 @@ import type { SecuritySchemeInterface } from "../security-scheme"; import type { TagsInterface } from "../tags"; import type { v2 } from "../../spec-types"; +import { SecurityRequirements } from "./security-requirements"; +import { SecurityRequirement } from "./security-requirement"; export class OperationTrait extends BaseModel implements OperationTraitInterface { id(): string { @@ -54,17 +56,17 @@ export class OperationTrait> { - const securitySchemes = this._meta.asyncapi?.parsed?.components?.securitySchemes || {}; - return (this._json.security || []).map((requirement: any) => { - const requirements: Record = {}; + security(): SecurityRequirements[] { + const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; + return (this._json.security || []).map((requirement, index) => { + const requirements: SecurityRequirement[] = []; Object.entries(requirement).forEach(([security, scopes]) => { - requirements[security] = { - schema: this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }), - scopes: scopes as Array, - } + const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); + requirements.push( + this.createModel(SecurityRequirement, scopes, { id: security, scheme: scheme, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + ); }); - return requirements; + return new SecurityRequirements(requirements); }) } diff --git a/src/models/v2/security-requirement.ts b/src/models/v2/security-requirement.ts new file mode 100644 index 000000000..4a47a97d8 --- /dev/null +++ b/src/models/v2/security-requirement.ts @@ -0,0 +1,15 @@ +import { BaseModel } from '../base'; +import type { SecuritySchemeInterface } from '../security-scheme'; +import type { v2 } from "../../spec-types"; +import { SecurityRequirementInterface } from 'models/security-requirement'; +import { SecurityScheme } from './security-scheme'; + +export class SecurityRequirement extends BaseModel<{}, { id: string, scheme: SecuritySchemeInterface }> implements SecurityRequirementInterface { + scheme(): SecuritySchemeInterface { + return this.meta().scheme; + } + + scopes() : string[] { + return this._json as string[]; + } +} \ No newline at end of file diff --git a/src/models/v2/security-requirements.ts b/src/models/v2/security-requirements.ts new file mode 100644 index 000000000..ee1ce0869 --- /dev/null +++ b/src/models/v2/security-requirements.ts @@ -0,0 +1,14 @@ +import { Collection } from '../collection'; + +import type { SecurityRequirementsInterface } from '../security-requirements'; +import type { SecurityRequirementInterface } from '../security-requirement'; + +export class SecurityRequirements extends Collection implements SecurityRequirementsInterface { + override get(id: string): SecurityRequirementInterface | undefined { + return this.collections.find(securityRequirement => securityRequirement.meta().id === id); + } + + override has(id: string): boolean { + return this.collections.some(securityRequirement => securityRequirement.meta().id === id); + } +} diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index 821f7c831..075fb89c0 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -23,6 +23,8 @@ import type { ExtensionsInterface } from '../extensions'; import type { BindingsInterface } from '../bindings'; import type { v2 } from "../../spec-types"; +import { SecurityRequirements } from './security-requirements'; +import { SecurityRequirement } from './security-requirement'; export class Server extends BaseModel implements ServerInterface { id(): string { @@ -89,17 +91,17 @@ export class Server extends BaseModel implement ); } - security(): Array> { - const securitySchemes = this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}; - return (this._json.security || []).map((requirement: any) => { - const requirements: Record = {}; + security(): SecurityRequirements[] { + const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record; + return (this._json.security || []).map((requirement, index) => { + const requirements: SecurityRequirement[] = []; Object.entries(requirement).forEach(([security, scopes]) => { - requirements[security] = { - schema: this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }), - scopes: scopes as Array, - } + const scheme = this.createModel(SecurityScheme, securitySchemes[security], { id: security, pointer: `/components/securitySchemes/${security}` }); + requirements.push( + this.createModel(SecurityRequirement, scopes, { id: security, scheme: scheme, pointer: `${this.meta().pointer}/security/${index}/${security}` }) + ); }); - return requirements; + return new SecurityRequirements(requirements); }) } diff --git a/test/models/v2/operation-trait.spec.ts b/test/models/v2/operation-trait.spec.ts index 9abfb7a9c..13785b7a2 100644 --- a/test/models/v2/operation-trait.spec.ts +++ b/test/models/v2/operation-trait.spec.ts @@ -1,4 +1,6 @@ import { OperationTrait } from '../../../src/models/v2/operation-trait'; +import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; +import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; import { SecurityScheme } from '../../../src/models/v2/security-scheme'; import { assertBindings, assertDescription, assertExtensions, assertExternalDocumentation, assertTags } from './utils'; @@ -86,11 +88,16 @@ describe('OperationTrait model', function() { it('should return collection of security requirements', function() { const doc = { security: [ { requirement: [] } ] }; const d = new OperationTrait(doc); - expect(Array.isArray(d.security())).toEqual(true); - expect(d.security()).toHaveLength(1); - expect(typeof d.security()[0]).toEqual('object'); - expect(d.security()[0]['requirement'].schema).toBeInstanceOf(SecurityScheme); - expect(d.security()[0]['requirement'].scopes).toEqual([]); + + const security = d.security(); + expect(Array.isArray(security)).toEqual(true); + expect(security).toHaveLength(1); + expect(security[0]).toBeInstanceOf(SecurityRequirements); + + const requirement = security[0].get('requirement') as SecurityRequirement; + expect(requirement).toBeInstanceOf(SecurityRequirement); + expect(requirement.scheme()).toBeInstanceOf(SecurityScheme); + expect(requirement.scopes()).toEqual([]); }); it('should return collection of security requirements when value is undefined', function() { diff --git a/test/models/v2/security-requirement.spec.ts b/test/models/v2/security-requirement.spec.ts new file mode 100644 index 000000000..f153ecc82 --- /dev/null +++ b/test/models/v2/security-requirement.spec.ts @@ -0,0 +1,26 @@ +import { ModelMetadata } from '../../../src/models/base'; +import { SecuritySchemeInterface } from '../../../src/models/security-scheme'; +import { SecurityRequirement } from '../../../src/models/v2/security-requirement' +import { SecurityScheme } from '../../../src/models/v2/security-scheme'; + +describe('SecurityRequirement model', function() { + describe('.scheme()', function() { + it('should return scheme', function() { + const doc = {}; + const expectedScheme = new SecurityScheme({type: "oauth2"}, {id: "test"} as any); + const d = new SecurityRequirement(doc, ({ id: "test", scheme: expectedScheme } as any)); // TODO Pointer + + expect(d.scheme()).toBeInstanceOf(SecurityScheme); + expect(d.scheme()).toEqual(expectedScheme); + }); + + }) + describe('.scopes()', function() { + it('should return scopes', function() { + const doc = ["scope_one"]; + const d = new SecurityRequirement(doc); // TODO Pointer + + expect(d.scopes()).toEqual(doc); + }); + }); +}); diff --git a/test/models/v2/security-requirements.spec.ts b/test/models/v2/security-requirements.spec.ts new file mode 100644 index 000000000..258b4bb4f --- /dev/null +++ b/test/models/v2/security-requirements.spec.ts @@ -0,0 +1,48 @@ +import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; +import { Operation } from '../../../src/models/v2/operation'; +import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; + +const operation = { + operationId: 'test', +}; +const operationItem = new Operation(operation, { asyncapi: {} as any, pointer: '', id: 'test', action: 'publish' }); + +const requirementItem = new SecurityRequirement({}, {id: "test"} as any); + +describe('SecurityRequirements model', function () { + describe('.isEmpty()', function () { + it('should return true if collection is empty', function () { + const requirements = new SecurityRequirements([]); + expect(requirements.isEmpty()).toEqual(true); + }); + + it('should return false if collection is not empty', function () { + const requirements = new SecurityRequirements([requirementItem]); + expect(requirements.isEmpty()).toEqual(false); + }); + }); + + describe('.get(id)', function () { + it('should return a specific SecurityRequirement if it is present', function () { + const requirements = new SecurityRequirements([requirementItem]); + expect(requirements.get('test')).toBeTruthy(); + }); + + it('should return undefined if specific SecurityRequirement is missing', function () { + const requirements = new SecurityRequirements([]); + expect(requirements.get('test')).toBeUndefined(); + }); + }); + + describe('.has(id)', function () { + it('should return true if the said id is available', function () { + const requirements = new SecurityRequirements([requirementItem]); + expect(requirements.has('test')).toEqual(true); + }) + + it('should return false if the SecurityRequirement id is missing', function () { + const requirements = new SecurityRequirements([requirementItem]); + expect(requirements.has('anotherId')).toEqual(false); + }) + }) +}) \ No newline at end of file diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index 514166022..e169bbd7b 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -11,6 +11,8 @@ import { SecurityScheme } from '../../../src/models/v2/security-scheme'; import { serializeInput, assertBindings, assertDescription, assertExtensions } from './utils'; import type { v2 } from '../../../src/spec-types'; +import { SecurityRequirements } from '../../../src/models/v2/security-requirements'; +import { SecurityRequirement } from '../../../src/models/v2/security-requirement'; const doc = { 'development': { @@ -179,17 +181,24 @@ describe('Server Model', function () { }) describe('.security()', function() { - it('should return collection of security requirements', function() { + it('should return SecurityRequirements', function() { const doc = serializeInput({ security: [ { requirement: [] } ] }); - const d = new Server(doc); - expect(Array.isArray(d.security())).toEqual(true); - expect(d.security()).toHaveLength(1); - expect(typeof d.security()[0]).toEqual('object'); - expect(d.security()[0]['requirement'].schema).toBeInstanceOf(SecurityScheme); - expect(d.security()[0]['requirement'].scopes).toEqual([]); + const d = new Server(doc, {pointer: "/servers/test"} as any); + + const security = d.security(); + expect(Array.isArray(security)).toEqual(true); + expect(security).toHaveLength(1); + expect(security[0]).toBeInstanceOf(SecurityRequirements); + + const requirement = security[0].get('requirement') as SecurityRequirement; + expect(requirement).toBeInstanceOf(SecurityRequirement); + expect(requirement.scheme()).toBeInstanceOf(SecurityScheme); + expect(requirement.scopes()).toEqual([]); + expect(requirement.meta().id).toEqual("requirement"); + expect(requirement.meta().pointer).toEqual("/servers/test/security/0/requirement"); }); - it('should return collection of security requirements when value is undefined', function() { + it('should return SecurityRequirements when value is undefined', function() { const doc = serializeInput({}); const d = new Server(doc); expect(Array.isArray(d.security())).toEqual(true);