Skip to content

Commit

Permalink
refactor: add SecurityRequirement(s) models (#588)
Browse files Browse the repository at this point in the history
  • Loading branch information
smoya authored Sep 1, 2022
1 parent b5ce5d9 commit cc46e8f
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 34 deletions.
3 changes: 2 additions & 1 deletion src/models/operation-trait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -10,5 +11,5 @@ export interface OperationTraitInterface extends BaseModel, BindingsMixinInterfa
operationId(): string | undefined;
hasSummary(): boolean;
summary(): string | undefined;
security(): Array<Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }>>;
security(): SecurityRequirements[];
}
7 changes: 7 additions & 0 deletions src/models/security-requirement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { BaseModel } from "./base";
import type { SecuritySchemeInterface } from "./security-scheme";

export interface SecurityRequirementInterface extends BaseModel {
scheme(): SecuritySchemeInterface
scopes(): string[];
}
4 changes: 4 additions & 0 deletions src/models/security-requirements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Collection} from './collection';
import type { SecurityRequirementInterface } from './security-requirement';

export interface SecurityRequirementsInterface extends Collection<SecurityRequirementInterface> {}
4 changes: 2 additions & 2 deletions src/models/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,5 +16,5 @@ export interface ServerInterface extends BaseModel, DescriptionMixinInterface, B
operations(): OperationsInterface;
messages(): MessagesInterface;
variables(): ServerVariablesInterface;
security(): Array<Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }>>;
security(): SecurityRequirementsInterface[];
}
20 changes: 11 additions & 9 deletions src/models/v2/operation-trait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<J extends v2.OperationTraitObject = v2.OperationTraitObject> extends BaseModel<J, { id: string, action: OperationAction }> implements OperationTraitInterface {
id(): string {
Expand Down Expand Up @@ -54,17 +56,17 @@ export class OperationTrait<J extends v2.OperationTraitObject = v2.OperationTrai
return externalDocs(this);
}

security(): Array<Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }>> {
const securitySchemes = this._meta.asyncapi?.parsed?.components?.securitySchemes || {};
return (this._json.security || []).map((requirement: any) => {
const requirements: Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }> = {};
security(): SecurityRequirements[] {
const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record<string, v2.SecuritySchemeObject>;
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<string>,
}
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);
})
}

Expand Down
15 changes: 15 additions & 0 deletions src/models/v2/security-requirement.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
}
14 changes: 14 additions & 0 deletions src/models/v2/security-requirements.ts
Original file line number Diff line number Diff line change
@@ -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<SecurityRequirementInterface> 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);
}
}
20 changes: 11 additions & 9 deletions src/models/v2/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<v2.ServerObject, { id: string }> implements ServerInterface {
id(): string {
Expand Down Expand Up @@ -89,17 +91,17 @@ export class Server extends BaseModel<v2.ServerObject, { id: string }> implement
);
}

security(): Array<Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }>> {
const securitySchemes = this._meta?.asyncapi?.parsed?.components?.securitySchemes || {};
return (this._json.security || []).map((requirement: any) => {
const requirements: Record<string, { schema: SecuritySchemeInterface; scopes: string[]; }> = {};
security(): SecurityRequirements[] {
const securitySchemes = (this._meta?.asyncapi?.parsed?.components?.securitySchemes || {}) as Record<string, v2.SecuritySchemeObject>;
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<string>,
}
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);
})
}

Expand Down
17 changes: 12 additions & 5 deletions test/models/v2/operation-trait.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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() {
Expand Down
26 changes: 26 additions & 0 deletions test/models/v2/security-requirement.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
48 changes: 48 additions & 0 deletions test/models/v2/security-requirements.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
})
})
})
25 changes: 17 additions & 8 deletions test/models/v2/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down Expand Up @@ -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<v2.ServerObject>({ 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<v2.ServerObject>({});
const d = new Server(doc);
expect(Array.isArray(d.security())).toEqual(true);
Expand Down

0 comments on commit cc46e8f

Please sign in to comment.