From a86f050bc293a149ff8fb2371f8b32c90065dc48 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Wed, 16 Mar 2022 12:21:25 +0100 Subject: [PATCH 1/7] refactor: add metadata to models --- src/models/asyncapi.ts | 4 +-- src/models/base.ts | 65 +++++++++++++++++++++++++++++++++++++-- src/models/v2/asyncapi.ts | 4 +-- src/models/v2/info.ts | 6 ++-- src/models/v2/license.ts | 2 +- src/models/v3/asyncapi.ts | 9 +++--- src/models/v3/info.ts | 6 ++-- src/types.ts | 4 +++ test/models/base.spec.ts | 8 +++-- test/stringify.spec.ts | 6 ++-- test/utils.spec.ts | 11 ++++--- 11 files changed, 99 insertions(+), 26 deletions(-) diff --git a/src/models/asyncapi.ts b/src/models/asyncapi.ts index 0c20dfcb5..0aac2fa86 100644 --- a/src/models/asyncapi.ts +++ b/src/models/asyncapi.ts @@ -21,9 +21,9 @@ export function newAsyncAPIDocument(json: Record): AsyncAPIDocument const major = version.split(".")[0]; switch (major) { case '2': - return new AsyncAPIDocumentV2(json); + return new AsyncAPIDocumentV2(json, { parent: null, asyncapi: json as any, pointer: '/' }); case '3': - return new AsyncAPIDocumentV3(json); + return new AsyncAPIDocumentV3(json, { parent: null, asyncapi: json as any, pointer: '/' }); default: throw new Error(`Unsupported version: ${version}`); } diff --git a/src/models/base.ts b/src/models/base.ts index 7e09c8007..219fd38e3 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -1,7 +1,22 @@ -export class BaseModel { +import { DetailedAsyncAPI, Constructor } from "../types"; + +export interface ModelMetadata

{ + asyncapi: DetailedAsyncAPI; + pointer: string; + parent: P | null; +} + +export abstract class BaseModel { + protected readonly _meta: ModelMetadata; + constructor( protected readonly _json: Record, - ) {} + metadata: ModelMetadata = {} as any, + ) { + this._meta = { + ...metadata, + } + } json>(): T; json(key: string | number): T; @@ -10,4 +25,50 @@ export class BaseModel { if (!this._json) return; return this._json[String(key)]; } + + meta(): ModelMetadata { + return this._meta!; + } + + jsonPath(field?: string): string | undefined { + if (typeof field !== 'string') { + return this._meta?.pointer; + } + return `${this._meta?.pointer}/${field}`; + } + + protected createModel(Model: Constructor, json: any, field: string | number): T { + return new (Model as any)(json, { asyncapi: this._meta?.asyncapi, parent: this, pointer: `${this._meta?.pointer}/${field}` } as ModelMetadata); + } + + protected createMapOfModel(map: Record, Model: Constructor): Record { + const result: Record = {}; + if (!map) return result; + + Object.entries(map).forEach(([key, value]) => { + result[String(key)] = this.createModel(Model, value, key); + }); + return result; + }; + + protected createListOfModel(array: Array, Model: Constructor): Array { + if (!array) return []; + return array.map((item, index) => this.createModel(Model, item, index)); + }; + + protected createModelByKey(collection: Record, key: string, Model: Constructor): T | undefined; + protected createModelByKey(collection: Array, index: number, Model: Constructor): T | undefined; + protected createModelByKey(collection: Record | Array, keyOrIndex: string | number, Model: Constructor): T | undefined { + if (!collection) { + return; + } + if ( + (typeof keyOrIndex === 'string' && !Array.isArray(collection)) || + (typeof keyOrIndex === 'number' && Array.isArray(collection)) + ) { + const value = (collection as Record)[keyOrIndex] as any; + return value && this.createModel(Model, value, keyOrIndex); + } + return; + }; } diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index a220e9fa9..5a4b7905d 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -12,13 +12,13 @@ import { Server } from "./server"; export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implements AsyncAPIDocumentInterface { - + version(): string { return this._json.asyncapi; } info(): InfoInterface { - return new Info(this._json.info); + return this.createModel(Info, this._json.info, 'info'); } servers(): ServersInterface { diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 9c12dd292..9fbd99024 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && new Contact(contact); + return contact && this.createModel(Contact, contact, 'contact'); } hasLicense(): boolean { @@ -55,6 +55,6 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && new License(license); + return license && this.createModel(License, license, 'license'); } -} \ No newline at end of file +} diff --git a/src/models/v2/license.ts b/src/models/v2/license.ts index 834f43b29..69cb09e35 100644 --- a/src/models/v2/license.ts +++ b/src/models/v2/license.ts @@ -17,4 +17,4 @@ export class License extends Mixin(BaseModel, ExtensionsMixin) implements Licens url(): string | undefined { return this._json.url; } -} \ No newline at end of file +} diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 5da958d7c..582a4f6e5 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -1,4 +1,3 @@ -import { AsyncAPIDocumentInterface } from "../../models/asyncapi"; import { BaseModel } from "../base"; import { Info } from "./info"; @@ -8,16 +7,18 @@ import { ServersInterface } from "models/servers"; import { Servers } from "./servers"; import { Server } from "./server"; +import { AsyncAPIDocumentInterface, InfoInterface } from "../../models"; + export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implements AsyncAPIDocumentInterface { version(): string { - return this.json("asyncapi"); + return this._json.asyncapi; } - info(): Info { - return new Info(this.json("info")); + info(): InfoInterface { + return this.createModel(Info, this._json.info, 'info'); } servers(): ServersInterface { diff --git a/src/models/v3/info.ts b/src/models/v3/info.ts index 9c12dd292..9fbd99024 100644 --- a/src/models/v3/info.ts +++ b/src/models/v3/info.ts @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && new Contact(contact); + return contact && this.createModel(Contact, contact, 'contact'); } hasLicense(): boolean { @@ -55,6 +55,6 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && new License(license); + return license && this.createModel(License, license, 'license'); } -} \ No newline at end of file +} diff --git a/src/types.ts b/src/types.ts index 7294aab80..c2c5a33cd 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,3 +26,7 @@ export interface ParserOutput { parsed: AsyncAPIDocumentInterface | undefined; diagnostics: Diagnostic[]; } + +export interface Constructor { + new (...args: any[]): T +} \ No newline at end of file diff --git a/test/models/base.spec.ts b/test/models/base.spec.ts index b26931a29..eb101e38f 100644 --- a/test/models/base.spec.ts +++ b/test/models/base.spec.ts @@ -1,22 +1,24 @@ import { BaseModel } from '../../src/models/base'; describe('Base model', function() { + class Model extends BaseModel {} + describe('.json()', function() { it('should return the whole JSON object', function() { const doc = { test: 'testing' }; - const d = new BaseModel(doc); + const d = new Model(doc); expect(d.json()).toEqual(doc); }); it('should return the value of a given key', function() { const doc = { test: 'testing' }; - const d = new BaseModel(doc); + const d = new Model(doc); expect(d.json('test')).toEqual(doc.test); }); it('should return the value of a given key, even when this is falsy', function() { const doc = { 0: 'testing' }; - const d = new BaseModel(doc); + const d = new Model(doc); expect(d.json(0)).toEqual(doc[0]); }); }); diff --git a/test/stringify.spec.ts b/test/stringify.spec.ts index 6c515f886..6d702920f 100644 --- a/test/stringify.spec.ts +++ b/test/stringify.spec.ts @@ -3,6 +3,8 @@ import { BaseModel, newAsyncAPIDocument } from '../src/models'; import { stringify, unstringify } from '../src/stringify'; describe('stringify & unstringify', function() { + class Model extends BaseModel {} + describe('stringify()', function() { it('should not stringify normal object', function() { expect(stringify({})).toEqual(undefined); @@ -17,7 +19,7 @@ describe('stringify & unstringify', function() { }); it('should not stringify BaseModel instance', function() { - expect(stringify(new BaseModel({}))).toEqual(undefined); + expect(stringify(new Model({}))).toEqual(undefined); }); it('should stringify parsed document', function() { @@ -47,7 +49,7 @@ describe('stringify & unstringify', function() { }); it('should not stringify BaseModel instance', function() { - expect(unstringify(new BaseModel({}))).toEqual(undefined); + expect(unstringify(new Model({}))).toEqual(undefined); }); it('should not unstringify parsed document', function() { diff --git a/test/utils.spec.ts b/test/utils.spec.ts index b8e6afd5d..13d9be02d 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -19,7 +19,10 @@ import { import type { Diagnostic } from '../src/types'; describe('utils', function() { + class Model extends BaseModel {} + describe('toAsyncAPIDocument()', function() { + it('normal object should not return AsyncAPIDocument instance', function() { expect(toAsyncAPIDocument({})).toEqual(undefined); }); @@ -33,7 +36,7 @@ describe('utils', function() { }); it('BaseModel instance should not return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument(new BaseModel({}))).toEqual(undefined); + expect(toAsyncAPIDocument(new Model({}))).toEqual(undefined); }); it('AsyncAPIDocument instance should return AsyncAPIDocument instance', function() { @@ -67,7 +70,7 @@ describe('utils', function() { }); it('BaseModel instance should not be AsyncAPI document', function() { - expect(isAsyncAPIDocument(new BaseModel({}))).toEqual(false); + expect(isAsyncAPIDocument(new Model({}))).toEqual(false); }); it('AsyncAPIDocument instance should be AsyncAPI document', function() { @@ -89,7 +92,7 @@ describe('utils', function() { }); it('BaseModel instance should not be AsyncAPI document', function() { - expect(isParsedDocument(new BaseModel({}))).toEqual(false); + expect(isParsedDocument(new Model({}))).toEqual(false); }); it('AsyncAPIDocument instance should not be parsed document', function() { @@ -119,7 +122,7 @@ describe('utils', function() { }); it('BaseModel instance should not be AsyncAPI document', function() { - expect(isStringifiedDocument(new BaseModel({}))).toEqual(false); + expect(isStringifiedDocument(new Model({}))).toEqual(false); }); it('AsyncAPIDocument instance should not be parsed document', function() { From e4f2b2a1209a20f94bc3da081809937fcca79dcc Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Wed, 16 Mar 2022 12:58:22 +0100 Subject: [PATCH 2/7] refactor: add metadata of models --- src/models/base.ts | 28 +++++++++++----------------- src/models/v2/asyncapi.ts | 4 ++-- src/models/v2/info.ts | 4 ++-- src/models/v3/asyncapi.ts | 2 +- src/models/v3/info.ts | 4 ++-- test/models/v2/contact.spec.ts | 10 ++++++++++ test/models/v2/info.spec.ts | 11 +++++++++++ test/models/v2/license.spec.ts | 10 ++++++++++ 8 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/models/base.ts b/src/models/base.ts index 219fd38e3..baf3f52c6 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -7,16 +7,10 @@ export interface ModelMetadata

{ } export abstract class BaseModel { - protected readonly _meta: ModelMetadata; - constructor( protected readonly _json: Record, - metadata: ModelMetadata = {} as any, - ) { - this._meta = { - ...metadata, - } - } + protected readonly _meta: ModelMetadata = {} as any, + ) {} json>(): T; json(key: string | number): T; @@ -37,8 +31,8 @@ export abstract class BaseModel { return `${this._meta?.pointer}/${field}`; } - protected createModel(Model: Constructor, json: any, field: string | number): T { - return new (Model as any)(json, { asyncapi: this._meta?.asyncapi, parent: this, pointer: `${this._meta?.pointer}/${field}` } as ModelMetadata); + protected createModel(Model: Constructor, { value, pointer }: { value: any, pointer: string | number }): T { + return new Model(value, { asyncapi: this._meta.asyncapi, parent: this, pointer: `${this._meta.pointer}/${pointer}` } as ModelMetadata); } protected createMapOfModel(map: Record, Model: Constructor): Record { @@ -46,28 +40,28 @@ export abstract class BaseModel { if (!map) return result; Object.entries(map).forEach(([key, value]) => { - result[String(key)] = this.createModel(Model, value, key); + result[String(key)] = this.createModel(Model, { value, pointer: key }); }); return result; }; protected createListOfModel(array: Array, Model: Constructor): Array { if (!array) return []; - return array.map((item, index) => this.createModel(Model, item, index)); + return array.map((value, pointer) => this.createModel(Model, { value, pointer })); }; protected createModelByKey(collection: Record, key: string, Model: Constructor): T | undefined; protected createModelByKey(collection: Array, index: number, Model: Constructor): T | undefined; - protected createModelByKey(collection: Record | Array, keyOrIndex: string | number, Model: Constructor): T | undefined { + protected createModelByKey(collection: Record | Array, pointer: string | number, Model: Constructor): T | undefined { if (!collection) { return; } if ( - (typeof keyOrIndex === 'string' && !Array.isArray(collection)) || - (typeof keyOrIndex === 'number' && Array.isArray(collection)) + (typeof pointer === 'string' && !Array.isArray(collection)) || + (typeof pointer === 'number' && Array.isArray(collection)) ) { - const value = (collection as Record)[keyOrIndex] as any; - return value && this.createModel(Model, value, keyOrIndex); + const value = (collection as Record)[pointer] as any; + return value && this.createModel(Model, { value, pointer }); } return; }; diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index 5a4b7905d..a52dd87a9 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -12,13 +12,13 @@ import { Server } from "./server"; export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) implements AsyncAPIDocumentInterface { - + version(): string { return this._json.asyncapi; } info(): InfoInterface { - return this.createModel(Info, this._json.info, 'info'); + return this.createModel(Info, { value: this._json.info, pointer: 'info' }); } servers(): ServersInterface { diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 9fbd99024..42abea49e 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && this.createModel(Contact, contact, 'contact'); + return contact && this.createModel(Contact, { value: contact, pointer: 'contact' }); } hasLicense(): boolean { @@ -55,6 +55,6 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && this.createModel(License, license, 'license'); + return license && this.createModel(License, { value: license, pointer: 'license' }); } } diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index 582a4f6e5..d7b6d4ae8 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -18,7 +18,7 @@ export class AsyncAPIDocument } info(): InfoInterface { - return this.createModel(Info, this._json.info, 'info'); + return this.createModel(Info, { value: this._json.info, pointer: 'info' }); } servers(): ServersInterface { diff --git a/src/models/v3/info.ts b/src/models/v3/info.ts index 9fbd99024..42abea49e 100644 --- a/src/models/v3/info.ts +++ b/src/models/v3/info.ts @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && this.createModel(Contact, contact, 'contact'); + return contact && this.createModel(Contact, { value: contact, pointer: 'contact' }); } hasLicense(): boolean { @@ -55,6 +55,6 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && this.createModel(License, license, 'license'); + return license && this.createModel(License, { value: license, pointer: 'license' }); } } diff --git a/test/models/v2/contact.spec.ts b/test/models/v2/contact.spec.ts index b7b9a8bb6..e43542d0a 100644 --- a/test/models/v2/contact.spec.ts +++ b/test/models/v2/contact.spec.ts @@ -1,8 +1,13 @@ import { Contact } from '../../../src/models/v2/contact'; import { +<<<<<<< next-major assertExtensionsMixinInheritance, } from './mixins/inheritance'; +======= + assertSpecificationExtensionsMixinInheritance, +} from '../mixins/inheritance'; +>>>>>>> refactor: add metadata of models describe('Contact model', function() { describe('.hasName()', function() { @@ -89,7 +94,12 @@ describe('Contact model', function() { }); }); +<<<<<<< next-major describe('mixins inheritance', function() { assertExtensionsMixinInheritance(Contact); +======= + describe('mixins', function() { + assertSpecificationExtensionsMixinInheritance(Contact); +>>>>>>> refactor: add metadata of models }); }); diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index 6d9e97d55..335c79d13 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -4,10 +4,15 @@ import { License } from '../../../src/models/v2/license'; import { assertDescriptionMixinInheritance, +<<<<<<< next-major assertExtensionsMixinInheritance, assertExternalDocumentationMixinInheritance, assertTagsMixinInheritance } from './mixins/inheritance'; +======= + assertSpecificationExtensionsMixinInheritance, +} from '../mixins/inheritance'; +>>>>>>> refactor: add metadata of models describe('Info model', function() { describe('.title()', function() { @@ -110,10 +115,16 @@ describe('Info model', function() { }); }); +<<<<<<< next-major describe('mixins inheritance', function() { assertDescriptionMixinInheritance(Info); assertExtensionsMixinInheritance(Info); assertExternalDocumentationMixinInheritance(Info); assertTagsMixinInheritance(Info); +======= + describe('mixins', function() { + assertDescriptionMixinInheritance(Info); + assertSpecificationExtensionsMixinInheritance(Info); +>>>>>>> refactor: add metadata of models }); }); diff --git a/test/models/v2/license.spec.ts b/test/models/v2/license.spec.ts index 855ba320a..8939c00eb 100644 --- a/test/models/v2/license.spec.ts +++ b/test/models/v2/license.spec.ts @@ -1,8 +1,13 @@ import { License } from '../../../src/models/v2/license'; import { +<<<<<<< next-major assertExtensionsMixinInheritance, } from './mixins/inheritance'; +======= + assertSpecificationExtensionsMixinInheritance, +} from '../mixins/inheritance'; +>>>>>>> refactor: add metadata of models describe('License model', function() { describe('.name()', function() { @@ -41,7 +46,12 @@ describe('License model', function() { }); }); +<<<<<<< next-major describe('mixins inheritance', function() { assertExtensionsMixinInheritance(License); +======= + describe('mixins', function() { + assertSpecificationExtensionsMixinInheritance(License); +>>>>>>> refactor: add metadata of models }); }); From dc561e0d0430b00d986f05298cf250cd22aab2c1 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Wed, 16 Mar 2022 13:14:41 +0100 Subject: [PATCH 3/7] improve newAsyncAPIDocument function --- src/models/asyncapi.ts | 26 +++++++++++--------------- src/parse.ts | 2 +- test/models/v2/info.spec.ts | 17 +---------------- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/models/asyncapi.ts b/src/models/asyncapi.ts index 0aac2fa86..55a8f1ff7 100644 --- a/src/models/asyncapi.ts +++ b/src/models/asyncapi.ts @@ -4,27 +4,23 @@ import { AsyncAPIDocumentV3 } from "./v3"; import type { InfoInterface } from "./info"; import type { BaseModel } from "./base"; import type { ExtensionsMixinInterface } from "./mixins"; -import { ServersInterface } from "./servers"; +import type { ServersInterface } from "./servers"; +import type { DetailedAsyncAPI } from "../types"; export interface AsyncAPIDocumentInterface extends BaseModel, ExtensionsMixinInterface { version(): string; info(): InfoInterface; - servers(): ServersInterface + servers(): ServersInterface; } -export function newAsyncAPIDocument(json: Record): AsyncAPIDocumentInterface { - const version = json['asyncapi']; // Maybe this should be an arg. - if (version == undefined || version == null || version == '') { - throw new Error('Missing AsyncAPI version in document'); - } - - const major = version.split(".")[0]; - switch (major) { - case '2': - return new AsyncAPIDocumentV2(json, { parent: null, asyncapi: json as any, pointer: '/' }); - case '3': - return new AsyncAPIDocumentV3(json, { parent: null, asyncapi: json as any, pointer: '/' }); +export function newAsyncAPIDocument(document: DetailedAsyncAPI): AsyncAPIDocumentInterface { + const majorVersion = document.semver.major; + switch (majorVersion) { + case 2: + return new AsyncAPIDocumentV2(document.parsed, { parent: null, asyncapi: document, pointer: '/' }); + case 3: + return new AsyncAPIDocumentV3(document.parsed, { parent: null, asyncapi: document, pointer: '/' }); default: - throw new Error(`Unsupported version: ${version}`); + throw new Error(`Unsupported AsyncAPI version: ${majorVersion}`); } } diff --git a/src/parse.ts b/src/parse.ts index c5ce643cc..2564f64af 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -47,7 +47,7 @@ export async function parse(asyncapi: ParserInput, options?: ParseOptions): Prom const detailed = createDetailedAsyncAPI(asyncapi as string | Record, parsed); await customOperations(detailed, options); - const parsedDoc = newAsyncAPIDocument(parsed); + const parsedDoc = newAsyncAPIDocument(detailed); return { source: asyncapi, diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index 335c79d13..2e26cf48b 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -4,15 +4,8 @@ import { License } from '../../../src/models/v2/license'; import { assertDescriptionMixinInheritance, -<<<<<<< next-major assertExtensionsMixinInheritance, - assertExternalDocumentationMixinInheritance, - assertTagsMixinInheritance } from './mixins/inheritance'; -======= - assertSpecificationExtensionsMixinInheritance, -} from '../mixins/inheritance'; ->>>>>>> refactor: add metadata of models describe('Info model', function() { describe('.title()', function() { @@ -115,16 +108,8 @@ describe('Info model', function() { }); }); -<<<<<<< next-major - describe('mixins inheritance', function() { - assertDescriptionMixinInheritance(Info); - assertExtensionsMixinInheritance(Info); - assertExternalDocumentationMixinInheritance(Info); - assertTagsMixinInheritance(Info); -======= describe('mixins', function() { assertDescriptionMixinInheritance(Info); - assertSpecificationExtensionsMixinInheritance(Info); ->>>>>>> refactor: add metadata of models + assertExtensionsMixinInheritance(Info); }); }); From 29be3469b0def41e4d862fa90e155508fec3b3b8 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Wed, 16 Mar 2022 13:27:43 +0100 Subject: [PATCH 4/7] fix tests --- src/models/asyncapi.ts | 11 +++++------ src/stringify.ts | 13 +++++++------ src/utils.ts | 2 +- test/models/v2/asyncapi.spec.ts | 28 +++++++++++++++++----------- test/stringify.spec.ts | 5 ++++- test/utils.spec.ts | 24 ++++++++++++++++++------ 6 files changed, 52 insertions(+), 31 deletions(-) diff --git a/src/models/asyncapi.ts b/src/models/asyncapi.ts index 55a8f1ff7..76aca11eb 100644 --- a/src/models/asyncapi.ts +++ b/src/models/asyncapi.ts @@ -13,14 +13,13 @@ export interface AsyncAPIDocumentInterface extends BaseModel, ExtensionsMixinInt servers(): ServersInterface; } -export function newAsyncAPIDocument(document: DetailedAsyncAPI): AsyncAPIDocumentInterface { - const majorVersion = document.semver.major; - switch (majorVersion) { +export function newAsyncAPIDocument(asyncapi: DetailedAsyncAPI): AsyncAPIDocumentInterface { + switch (asyncapi.semver.major) { case 2: - return new AsyncAPIDocumentV2(document.parsed, { parent: null, asyncapi: document, pointer: '/' }); + return new AsyncAPIDocumentV2(asyncapi.parsed, { parent: null, asyncapi, pointer: '/' }); case 3: - return new AsyncAPIDocumentV3(document.parsed, { parent: null, asyncapi: document, pointer: '/' }); + return new AsyncAPIDocumentV3(asyncapi.parsed, { parent: null, asyncapi, pointer: '/' }); default: - throw new Error(`Unsupported AsyncAPI version: ${majorVersion}`); + throw new Error(`Unsupported AsyncAPI version: ${asyncapi.semver.version}`); } } diff --git a/src/stringify.ts b/src/stringify.ts index b49e4ed61..32240adee 100644 --- a/src/stringify.ts +++ b/src/stringify.ts @@ -1,6 +1,6 @@ import { AsyncAPIDocumentInterface, newAsyncAPIDocument } from './models'; -import { isAsyncAPIDocument, isParsedDocument, isStringifiedDocument } from './utils'; +import { createDetailedAsyncAPI, isAsyncAPIDocument, isParsedDocument, isStringifiedDocument } from './utils'; import { xParserSpecStringified } from './constants'; export interface StringifyOptions { @@ -26,25 +26,26 @@ export function stringify(document: unknown, options: StringifyOptions = {}): st } export function unstringify(document: unknown): AsyncAPIDocumentInterface | undefined { + let parsed: unknown = document; if (typeof document === 'string') { try { - document = JSON.parse(document); + parsed = JSON.parse(document); } catch(_) { return; } } - if (!isStringifiedDocument(document)) { + if (!isStringifiedDocument(parsed)) { return; } // shall copy of whole JSON - document = { ...document }; + parsed = { ...parsed }; // remove `x-parser-spec-stringified` extension - delete (>document)[String(xParserSpecStringified)]; + delete (>parsed)[String(xParserSpecStringified)]; traverseStringifiedDoc(document, undefined, document, new Map(), new Map()); - return newAsyncAPIDocument(>document); + return newAsyncAPIDocument(createDetailedAsyncAPI(document as string, parsed as Record)); } function refReplacer() { diff --git a/src/utils.ts b/src/utils.ts index 6c48e1048..da88db8ad 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -17,7 +17,7 @@ export function toAsyncAPIDocument(maybeDoc: unknown): AsyncAPIDocumentInterface if (!isParsedDocument(maybeDoc)) { return; } - return unstringify(maybeDoc) || newAsyncAPIDocument(maybeDoc); + return unstringify(maybeDoc) || newAsyncAPIDocument(createDetailedAsyncAPI(maybeDoc, maybeDoc)); } export function isAsyncAPIDocument(maybeDoc: unknown): maybeDoc is AsyncAPIDocumentInterface { diff --git a/test/models/v2/asyncapi.spec.ts b/test/models/v2/asyncapi.spec.ts index 60ee2499c..fab5cd43f 100644 --- a/test/models/v2/asyncapi.spec.ts +++ b/test/models/v2/asyncapi.spec.ts @@ -1,4 +1,11 @@ -import { newAsyncAPIDocument, AsyncAPIDocumentV2, InfoV2, AsyncAPIDocumentV3, ServersV2 } from '../../../src/models'; +import { + newAsyncAPIDocument, + AsyncAPIDocumentV2, + InfoV2, + ServersV2, + AsyncAPIDocumentV3 +} from '../../../src/models'; +import { createDetailedAsyncAPI } from '../../../src/utils'; import { assertExtensionsMixinInheritance, @@ -23,19 +30,15 @@ describe('AsyncAPIDocument model', function() { it('should return an Info object', function() { const doc = { info: { name: "LeChuck" } }; const d = new AsyncAPIDocumentV2(doc); - expect(d.info() instanceof InfoV2).toBeTruthy(); + expect(d.info()).toBeInstanceOf(InfoV2); }); }); describe('.servers()', function(){ it('should return an servers object', function(){ - const doc = {servers: { - development: { - - } - }}; + const doc = { servers: { development: {} } }; const d = new AsyncAPIDocumentV2(doc); - expect(d.servers() instanceof ServersV2).toBeTruthy(); + expect(d.servers()).toBeInstanceOf(ServersV2); }) }) @@ -47,20 +50,23 @@ describe('AsyncAPIDocument model', function() { describe('AsyncAPIDocument factory', function() { it('should create a valid document from v2.0.0', function() { const doc = { asyncapi: "2.0.0" }; - const d = newAsyncAPIDocument(doc) + const detailed = createDetailedAsyncAPI(doc, doc); + const d = newAsyncAPIDocument(detailed) expect(d.version()).toEqual(doc.asyncapi); expect(d).toBeInstanceOf(AsyncAPIDocumentV2); }); it('should create a valid document from v3.0.0', function() { const doc = { asyncapi: "3.0.0" }; - const d = newAsyncAPIDocument(doc) + const detailed = createDetailedAsyncAPI(doc, doc); + const d = newAsyncAPIDocument(detailed) expect(d.version()).toEqual(doc.asyncapi); expect(d).toBeInstanceOf(AsyncAPIDocumentV3); }); it('should fail trying to create a document from a non supported spec version', function() { const doc = { asyncapi: "99.99.99" }; - expect(() => newAsyncAPIDocument(doc)).toThrow("Unsupported version: 99.99.99"); + const detailed = createDetailedAsyncAPI(doc, doc); + expect(() => newAsyncAPIDocument(detailed)).toThrow("Unsupported AsyncAPI version: 99.99.99"); }); }); diff --git a/test/stringify.spec.ts b/test/stringify.spec.ts index 6d702920f..14c79eee9 100644 --- a/test/stringify.spec.ts +++ b/test/stringify.spec.ts @@ -1,6 +1,7 @@ import { xParserSpecParsed, xParserSpecStringified } from '../src/constants'; import { BaseModel, newAsyncAPIDocument } from '../src/models'; import { stringify, unstringify } from '../src/stringify'; +import { createDetailedAsyncAPI } from '../src/utils'; describe('stringify & unstringify', function() { class Model extends BaseModel {} @@ -31,7 +32,9 @@ describe('stringify & unstringify', function() { }); it('should stringify AsyncAPIDocument instance', function() { - expect(typeof stringify(newAsyncAPIDocument({ asyncapi: '2.0.0' }))).toEqual('string'); + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(typeof stringify(newAsyncAPIDocument(detailed))).toEqual('string'); }); }); diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 13d9be02d..e5ebfdca3 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -40,7 +40,9 @@ describe('utils', function() { }); it('AsyncAPIDocument instance should return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument(newAsyncAPIDocument({ asyncapi: '2.0.0' }))).toBeInstanceOf(AsyncAPIDocumentV2); + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(toAsyncAPIDocument(newAsyncAPIDocument(detailed))).toBeInstanceOf(AsyncAPIDocumentV2); }); it('parsed document should return AsyncAPIDocument instance', function() { @@ -74,7 +76,9 @@ describe('utils', function() { }); it('AsyncAPIDocument instance should be AsyncAPI document', function() { - expect(isAsyncAPIDocument(newAsyncAPIDocument({ asyncapi: '2.0.0' }))).toEqual(true); + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(isAsyncAPIDocument(newAsyncAPIDocument(detailed))).toEqual(true); }); }); @@ -96,11 +100,15 @@ describe('utils', function() { }); it('AsyncAPIDocument instance should not be parsed document', function() { - expect(isParsedDocument(newAsyncAPIDocument({ asyncapi: '2.0.0' }))).toEqual(false); + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); }); it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { - expect(isParsedDocument(newAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true }))).toEqual(false); + const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); }); it('object with proper extension should be parsed document', function() { @@ -126,11 +134,15 @@ describe('utils', function() { }); it('AsyncAPIDocument instance should not be parsed document', function() { - expect(isStringifiedDocument(newAsyncAPIDocument({ asyncapi: '2.0.0' }))).toEqual(false); + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); }); it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { - expect(isStringifiedDocument(newAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true }))).toEqual(false); + const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true }; + const detailed = createDetailedAsyncAPI(doc, doc); + expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); }); it('object with only stringified extension should not be parsed document', function() { From 407e43336fe9143e6335e78497e78cc80c8fea17 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Thu, 24 Mar 2022 12:46:53 +0100 Subject: [PATCH 5/7] improve helper functions --- src/models/base.ts | 42 ++-------- src/models/utils.ts | 2 +- src/models/v2/asyncapi.ts | 2 +- src/models/v2/info.ts | 34 +++++--- src/models/v2/mixins/bindings.ts | 8 +- src/models/v2/mixins/extensions.ts | 8 +- src/models/v2/mixins/tags.ts | 2 +- src/models/v3/asyncapi.ts | 2 +- src/models/v3/info.ts | 34 +++++--- src/models/v3/mixins/bindings.ts | 10 ++- src/models/v3/mixins/extensions.ts | 10 ++- src/models/v3/mixins/tags.ts | 4 +- src/types.ts | 4 +- test/models/v2/contact.spec.ts | 10 --- test/models/v2/info.spec.ts | 87 ++++++++++++++++++++- test/models/v2/license.spec.ts | 10 --- test/models/v2/mixins/external-docs.spec.ts | 1 - 17 files changed, 176 insertions(+), 94 deletions(-) diff --git a/src/models/base.ts b/src/models/base.ts index baf3f52c6..7e7bb8b7a 100644 --- a/src/models/base.ts +++ b/src/models/base.ts @@ -1,4 +1,5 @@ -import { DetailedAsyncAPI, Constructor } from "../types"; +import type { Constructor } from "./utils"; +import type { DetailedAsyncAPI } from "../types"; export interface ModelMetadata

{ asyncapi: DetailedAsyncAPI; @@ -31,38 +32,11 @@ export abstract class BaseModel { return `${this._meta?.pointer}/${field}`; } - protected createModel(Model: Constructor, { value, pointer }: { value: any, pointer: string | number }): T { - return new Model(value, { asyncapi: this._meta.asyncapi, parent: this, pointer: `${this._meta.pointer}/${pointer}` } as ModelMetadata); - } - - protected createMapOfModel(map: Record, Model: Constructor): Record { - const result: Record = {}; - if (!map) return result; - - Object.entries(map).forEach(([key, value]) => { - result[String(key)] = this.createModel(Model, { value, pointer: key }); - }); - return result; - }; - - protected createListOfModel(array: Array, Model: Constructor): Array { - if (!array) return []; - return array.map((value, pointer) => this.createModel(Model, { value, pointer })); - }; - - protected createModelByKey(collection: Record, key: string, Model: Constructor): T | undefined; - protected createModelByKey(collection: Array, index: number, Model: Constructor): T | undefined; - protected createModelByKey(collection: Record | Array, pointer: string | number, Model: Constructor): T | undefined { - if (!collection) { - return; - } - if ( - (typeof pointer === 'string' && !Array.isArray(collection)) || - (typeof pointer === 'number' && Array.isArray(collection)) - ) { - const value = (collection as Record)[pointer] as any; - return value && this.createModel(Model, { value, pointer }); + protected createModel(Model: Constructor, value: any, { id, parent, pointer }: { id?: string, parent?: any, pointer: string | number }): T { + const meta = { asyncapi: this._meta.asyncapi, parent: parent || this, pointer } as ModelMetadata; + if (id) { + return new Model(id, value, meta); } - return; - }; + return new Model(value, meta); + } } diff --git a/src/models/utils.ts b/src/models/utils.ts index e2befd7a8..c97451818 100644 --- a/src/models/utils.ts +++ b/src/models/utils.ts @@ -1,6 +1,6 @@ import type { BaseModel } from './base'; -export interface Constructor extends Function { +export interface Constructor extends Function { new (...any: any[]): T; } diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index a52dd87a9..59af64562 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -18,7 +18,7 @@ export class AsyncAPIDocument } info(): InfoInterface { - return this.createModel(Info, { value: this._json.info, pointer: 'info' }); + return this.createModel(Info, this._json.info, { pointer: '/info' }); } servers(): ServersInterface { diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 42abea49e..6e5cfc62e 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -5,13 +5,15 @@ import { License } from "./license"; import { Mixin } from '../utils'; import { DescriptionMixin } from './mixins/description'; import { ExtensionsMixin } from './mixins/extensions'; -import { ExternalDocumentationMixin } from './mixins/external-docs'; -import { TagsMixin } from './mixins/tags'; +import { ExternalDocumentation } from './mixins/external-docs'; +import { Tags, Tag } from './mixins/tags'; import type { InfoInterface } from "../../models/info"; +import type { ExternalDocumentationInterface } from "../../models/external-docs"; +import type { TagsInterface } from "../../models/tags"; export class Info - extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin, ExternalDocumentationMixin, TagsMixin) + extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin) implements InfoInterface { title(): string { @@ -22,14 +24,12 @@ export class Info return this._json.version; } - // TODO: Implement it id(): string | undefined { - return; + return this._meta.asyncapi.parsed.id as string; } - // TODO: Implement it hasId(): boolean { - return true; + return !!this._meta.asyncapi.parsed.id; } hasTermsOfService(): boolean { @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && this.createModel(Contact, { value: contact, pointer: 'contact' }); + return contact && this.createModel(Contact, contact, { pointer: '/info/contact' }); } hasLicense(): boolean { @@ -55,6 +55,22 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && this.createModel(License, { value: license, pointer: 'license' }); + return license && this.createModel(License, license, { pointer: `/info/license` }); + } + + hasExternalDocs(): boolean { + return Object.keys(this._meta.asyncapi.parsed.externalDocs || {}).length > 0; + }; + + externalDocs(): ExternalDocumentationInterface | undefined { + if (this.hasExternalDocs()) { + return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs, { pointer: `/externalDocs` }); + } + return; + }; + + tags(): TagsInterface { + const tags = this._json.tags || []; + return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `/tags/${idx}` }))); } } diff --git a/src/models/v2/mixins/bindings.ts b/src/models/v2/mixins/bindings.ts index 13dd7a228..ae646e166 100644 --- a/src/models/v2/mixins/bindings.ts +++ b/src/models/v2/mixins/bindings.ts @@ -4,6 +4,7 @@ import { Collection } from '../../collection'; import { Mixin } from '../../utils'; import { ExtensionsMixin } from './extensions'; +import type { ModelMetadata } from "../../base"; import type { BindingsMixinInterface } from "../../mixins"; import type { BindingsInterface } from "../../bindings"; import type { BindingInterface } from "../../binding"; @@ -12,8 +13,9 @@ export class Binding extends Mixin(BaseModel, ExtensionsMixin) implements Bindin constructor( private readonly _protocol: string, _json: Record, + _meta: ModelMetadata, ) { - super(_json); + super(_json, _meta); } protocol(): string { @@ -45,7 +47,9 @@ export abstract class BindingsMixin extends BaseModel implements BindingsMixinIn bindings(): BindingsInterface { const bindings: Record = this._json.bindings || {}; return new Bindings( - Object.entries(bindings).map(([protocol, binding]) => new Binding(protocol, binding)) + Object.entries(bindings).map(([protocol, binding]) => + this.createModel(Binding, binding, { id: protocol, pointer: `${this._meta.pointer}/bindings/${protocol}` }) + ) ); } } diff --git a/src/models/v2/mixins/extensions.ts b/src/models/v2/mixins/extensions.ts index 61ee70409..9666a2b2f 100644 --- a/src/models/v2/mixins/extensions.ts +++ b/src/models/v2/mixins/extensions.ts @@ -1,6 +1,7 @@ import { Collection } from '../../collection'; import { BaseModel } from "../../base"; +import type { ModelMetadata } from "../../base"; import type { ExtensionsMixinInterface } from "../../mixins"; import type { ExtensionsInterface } from "../../extensions"; import type { ExtensionInterface } from "../../extension"; @@ -11,8 +12,9 @@ export class Extension extends BaseModel implements ExtensionInterface { constructor( private readonly _id: string, _json: Record, + _meta: ModelMetadata, ) { - super(_json); + super(_json, _meta); } id(): string { @@ -43,7 +45,9 @@ export abstract class ExtensionsMixin extends BaseModel implements ExtensionsMix const extensions: Extension[] = []; Object.entries(this._json).forEach(([key, value]) => { if (EXTENSION_REGEX.test(key)) { - extensions.push(new Extension(key, value)); + extensions.push( + this.createModel(Extension, value, { id: key, pointer: `${this._meta.pointer}/${key}` }) + ); } }); return new Extensions(extensions); diff --git a/src/models/v2/mixins/tags.ts b/src/models/v2/mixins/tags.ts index 6cdee98b9..220e5edd6 100644 --- a/src/models/v2/mixins/tags.ts +++ b/src/models/v2/mixins/tags.ts @@ -32,6 +32,6 @@ export class Tags extends Collection implements TagsInterface { export abstract class TagsMixin extends BaseModel implements TagsMixinInterface { tags(): TagsInterface { const tags = this._json.tags || []; - return new Tags(tags.map((tag: any) => new Tag(tag))); + return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` }))); } } \ No newline at end of file diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index d7b6d4ae8..e74139292 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -18,7 +18,7 @@ export class AsyncAPIDocument } info(): InfoInterface { - return this.createModel(Info, { value: this._json.info, pointer: 'info' }); + return this.createModel(Info, this._json.info, { pointer: '/info' }); } servers(): ServersInterface { diff --git a/src/models/v3/info.ts b/src/models/v3/info.ts index 42abea49e..6e5cfc62e 100644 --- a/src/models/v3/info.ts +++ b/src/models/v3/info.ts @@ -5,13 +5,15 @@ import { License } from "./license"; import { Mixin } from '../utils'; import { DescriptionMixin } from './mixins/description'; import { ExtensionsMixin } from './mixins/extensions'; -import { ExternalDocumentationMixin } from './mixins/external-docs'; -import { TagsMixin } from './mixins/tags'; +import { ExternalDocumentation } from './mixins/external-docs'; +import { Tags, Tag } from './mixins/tags'; import type { InfoInterface } from "../../models/info"; +import type { ExternalDocumentationInterface } from "../../models/external-docs"; +import type { TagsInterface } from "../../models/tags"; export class Info - extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin, ExternalDocumentationMixin, TagsMixin) + extends Mixin(BaseModel, DescriptionMixin, ExtensionsMixin) implements InfoInterface { title(): string { @@ -22,14 +24,12 @@ export class Info return this._json.version; } - // TODO: Implement it id(): string | undefined { - return; + return this._meta.asyncapi.parsed.id as string; } - // TODO: Implement it hasId(): boolean { - return true; + return !!this._meta.asyncapi.parsed.id; } hasTermsOfService(): boolean { @@ -46,7 +46,7 @@ export class Info contact(): Contact | undefined { const contact = this._json.contact; - return contact && this.createModel(Contact, { value: contact, pointer: 'contact' }); + return contact && this.createModel(Contact, contact, { pointer: '/info/contact' }); } hasLicense(): boolean { @@ -55,6 +55,22 @@ export class Info license(): License | undefined { const license = this._json.license; - return license && this.createModel(License, { value: license, pointer: 'license' }); + return license && this.createModel(License, license, { pointer: `/info/license` }); + } + + hasExternalDocs(): boolean { + return Object.keys(this._meta.asyncapi.parsed.externalDocs || {}).length > 0; + }; + + externalDocs(): ExternalDocumentationInterface | undefined { + if (this.hasExternalDocs()) { + return this.createModel(ExternalDocumentation, this._meta.asyncapi.parsed.externalDocs, { pointer: `/externalDocs` }); + } + return; + }; + + tags(): TagsInterface { + const tags = this._json.tags || []; + return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `/tags/${idx}` }))); } } diff --git a/src/models/v3/mixins/bindings.ts b/src/models/v3/mixins/bindings.ts index dc6770263..ae646e166 100644 --- a/src/models/v3/mixins/bindings.ts +++ b/src/models/v3/mixins/bindings.ts @@ -4,6 +4,7 @@ import { Collection } from '../../collection'; import { Mixin } from '../../utils'; import { ExtensionsMixin } from './extensions'; +import type { ModelMetadata } from "../../base"; import type { BindingsMixinInterface } from "../../mixins"; import type { BindingsInterface } from "../../bindings"; import type { BindingInterface } from "../../binding"; @@ -12,8 +13,9 @@ export class Binding extends Mixin(BaseModel, ExtensionsMixin) implements Bindin constructor( private readonly _protocol: string, _json: Record, + _meta: ModelMetadata, ) { - super(_json); + super(_json, _meta); } protocol(): string { @@ -45,7 +47,9 @@ export abstract class BindingsMixin extends BaseModel implements BindingsMixinIn bindings(): BindingsInterface { const bindings: Record = this._json.bindings || {}; return new Bindings( - Object.entries(bindings).map(([protocol, binding]) => new Binding(protocol, binding)) + Object.entries(bindings).map(([protocol, binding]) => + this.createModel(Binding, binding, { id: protocol, pointer: `${this._meta.pointer}/bindings/${protocol}` }) + ) ); } -} \ No newline at end of file +} diff --git a/src/models/v3/mixins/extensions.ts b/src/models/v3/mixins/extensions.ts index 61ee70409..9e5cb45dc 100644 --- a/src/models/v3/mixins/extensions.ts +++ b/src/models/v3/mixins/extensions.ts @@ -1,6 +1,7 @@ import { Collection } from '../../collection'; import { BaseModel } from "../../base"; +import type { ModelMetadata } from "../../base"; import type { ExtensionsMixinInterface } from "../../mixins"; import type { ExtensionsInterface } from "../../extensions"; import type { ExtensionInterface } from "../../extension"; @@ -11,8 +12,9 @@ export class Extension extends BaseModel implements ExtensionInterface { constructor( private readonly _id: string, _json: Record, + _meta: ModelMetadata, ) { - super(_json); + super(_json, _meta); } id(): string { @@ -43,9 +45,11 @@ export abstract class ExtensionsMixin extends BaseModel implements ExtensionsMix const extensions: Extension[] = []; Object.entries(this._json).forEach(([key, value]) => { if (EXTENSION_REGEX.test(key)) { - extensions.push(new Extension(key, value)); + extensions.push( + this.createModel(Extension, value, { id: key, pointer: `${this._meta.pointer}/${key}` }) + ); } }); return new Extensions(extensions); }; -} \ No newline at end of file +} diff --git a/src/models/v3/mixins/tags.ts b/src/models/v3/mixins/tags.ts index 6cdee98b9..e490069e9 100644 --- a/src/models/v3/mixins/tags.ts +++ b/src/models/v3/mixins/tags.ts @@ -32,6 +32,6 @@ export class Tags extends Collection implements TagsInterface { export abstract class TagsMixin extends BaseModel implements TagsMixinInterface { tags(): TagsInterface { const tags = this._json.tags || []; - return new Tags(tags.map((tag: any) => new Tag(tag))); + return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` }))); } -} \ No newline at end of file +} diff --git a/src/types.ts b/src/types.ts index c2c5a33cd..13e882b8a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,8 +11,8 @@ export interface AsyncAPISemver { } export interface DetailedAsyncAPI { - source: string | Record; - parsed: Record; + source: string | Record; + parsed: Record; semver: AsyncAPISemver; } diff --git a/test/models/v2/contact.spec.ts b/test/models/v2/contact.spec.ts index e43542d0a..b7b9a8bb6 100644 --- a/test/models/v2/contact.spec.ts +++ b/test/models/v2/contact.spec.ts @@ -1,13 +1,8 @@ import { Contact } from '../../../src/models/v2/contact'; import { -<<<<<<< next-major assertExtensionsMixinInheritance, } from './mixins/inheritance'; -======= - assertSpecificationExtensionsMixinInheritance, -} from '../mixins/inheritance'; ->>>>>>> refactor: add metadata of models describe('Contact model', function() { describe('.hasName()', function() { @@ -94,12 +89,7 @@ describe('Contact model', function() { }); }); -<<<<<<< next-major describe('mixins inheritance', function() { assertExtensionsMixinInheritance(Contact); -======= - describe('mixins', function() { - assertSpecificationExtensionsMixinInheritance(Contact); ->>>>>>> refactor: add metadata of models }); }); diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index 2e26cf48b..d8f364a60 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -1,6 +1,8 @@ import { Info } from '../../../src/models/v2/info'; import { Contact } from '../../../src/models/v2/contact'; import { License } from '../../../src/models/v2/license'; +import { ExternalDocumentation } from '../../../src/models/v2/mixins/external-docs'; +import { createDetailedAsyncAPI } from '../../../src/utils'; import { assertDescriptionMixinInheritance, @@ -24,6 +26,38 @@ describe('Info model', function() { }); }); + describe('.hasId()', function() { + it('should return true when there is a value', function() { + const doc = { asyncapi: '2.0.0', id: 'someId' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.hasId()).toEqual(true); + }); + + it('should return false when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.hasId()).toEqual(false); + }); + }); + + describe('.id()', function() { + it('should return the value', function() { + const doc = { asyncapi: '2.0.0', id: 'someId' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.id()).toEqual(doc.id); + }); + + it('should return undefined when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.id()).toEqual(undefined); + }); + }); + describe('.hasTermsOfService()', function() { it('should return true when there is a value', function() { const doc = { termsOfService: "These are the terms of service" }; @@ -59,7 +93,7 @@ describe('Info model', function() { expect(d.hasContact()).toEqual(true); }); - it('should return undefined when there is no value', function() { + it('should return false when there is no value', function() { const doc = {}; const d = new Info(doc); expect(d.hasContact()).toEqual(false); @@ -87,7 +121,7 @@ describe('Info model', function() { expect(d.hasLicense()).toEqual(true); }); - it('should return undefined when there is no value', function() { + it('should return false when there is no value', function() { const doc = {}; const d = new Info(doc); expect(d.hasLicense()).toEqual(false); @@ -108,7 +142,54 @@ describe('Info model', function() { }); }); - describe('mixins', function() { + describe('.hasExternalDocs()', function() { + it('should return true when there is a value', function() { + const doc = { asyncapi: '2.0.0', externalDocs: { url: 'https://example.com' } }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.hasExternalDocs()).toEqual(true); + }); + + it('should return false when there is an empty object', function() { + const doc = { asyncapi: '2.0.0', externalDocs: {} }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.hasExternalDocs()).toEqual(false); + }); + + it('should return false when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.hasExternalDocs()).toEqual(false); + }); + }); + + describe('.externalDocs()', function() { + it('should return the value', function() { + const doc = { asyncapi: '2.0.0', externalDocs: { url: 'https://example.com' } }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.externalDocs()).toBeInstanceOf(ExternalDocumentation); + expect(d.externalDocs()!.json()).toEqual(doc.externalDocs); + }); + + it('should return undefined when there is an empty object', function() { + const doc = { asyncapi: '2.0.0', externalDocs: {} }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.externalDocs()).toEqual(undefined); + }); + + it('should return undefined when there is no value', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.externalDocs()).toEqual(undefined); + }); + }); + + describe('mixins inheritance', function() { assertDescriptionMixinInheritance(Info); assertExtensionsMixinInheritance(Info); }); diff --git a/test/models/v2/license.spec.ts b/test/models/v2/license.spec.ts index 8939c00eb..855ba320a 100644 --- a/test/models/v2/license.spec.ts +++ b/test/models/v2/license.spec.ts @@ -1,13 +1,8 @@ import { License } from '../../../src/models/v2/license'; import { -<<<<<<< next-major assertExtensionsMixinInheritance, } from './mixins/inheritance'; -======= - assertSpecificationExtensionsMixinInheritance, -} from '../mixins/inheritance'; ->>>>>>> refactor: add metadata of models describe('License model', function() { describe('.name()', function() { @@ -46,12 +41,7 @@ describe('License model', function() { }); }); -<<<<<<< next-major describe('mixins inheritance', function() { assertExtensionsMixinInheritance(License); -======= - describe('mixins', function() { - assertSpecificationExtensionsMixinInheritance(License); ->>>>>>> refactor: add metadata of models }); }); diff --git a/test/models/v2/mixins/external-docs.spec.ts b/test/models/v2/mixins/external-docs.spec.ts index 6cf9c995a..f9aac7bc9 100644 --- a/test/models/v2/mixins/external-docs.spec.ts +++ b/test/models/v2/mixins/external-docs.spec.ts @@ -21,7 +21,6 @@ describe('ExternalDocs mixin', function() { }); }); - // TODO: implement it when the ExternalDocs class will be implemented describe('.externalDocs()', function() { it('should return a externalDocs object', function() { expect(d1.externalDocs()).toBeInstanceOf(ExternalDocumentationV2); From ba6f4f261a4b264d7547d1589d1a00876cde6164 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Thu, 24 Mar 2022 13:16:39 +0100 Subject: [PATCH 6/7] add missed test --- src/models/v2/info.ts | 2 +- test/models/v2/info.spec.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/models/v2/info.ts b/src/models/v2/info.ts index 6e5cfc62e..5d53573fc 100644 --- a/src/models/v2/info.ts +++ b/src/models/v2/info.ts @@ -70,7 +70,7 @@ export class Info }; tags(): TagsInterface { - const tags = this._json.tags || []; + const tags = this._meta.asyncapi.parsed.tags || []; return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `/tags/${idx}` }))); } } diff --git a/test/models/v2/info.spec.ts b/test/models/v2/info.spec.ts index d8f364a60..708917cf6 100644 --- a/test/models/v2/info.spec.ts +++ b/test/models/v2/info.spec.ts @@ -2,6 +2,7 @@ import { Info } from '../../../src/models/v2/info'; import { Contact } from '../../../src/models/v2/contact'; import { License } from '../../../src/models/v2/license'; import { ExternalDocumentation } from '../../../src/models/v2/mixins/external-docs'; +import { Tags, Tag } from '../../../src/models/v2/mixins/tags'; import { createDetailedAsyncAPI } from '../../../src/utils'; import { @@ -189,6 +190,27 @@ describe('Info model', function() { }); }); + describe('.tags()', function() { + it('should return the collection of tags', function() { + const tags = [{ name: 'one' }, { name: 'two' }]; + const doc = { asyncapi: '2.0.0', tags }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.tags()).toBeInstanceOf(Tags); + expect(d.tags().length).toEqual(2); + expect(d.tags().all()[0]).toBeInstanceOf(Tag); + expect(d.tags().all()[1]).toBeInstanceOf(Tag); + }); + + it('should return empty array when there is an empty collection', function() { + const doc = { asyncapi: '2.0.0' }; + const asyncapi = createDetailedAsyncAPI(doc, doc); + const d = new Info({}, { asyncapi, parent: null, pointer: '/info' }); + expect(d.tags()).toBeInstanceOf(Tags); + expect(d.tags().all()).toEqual([]); + }); + }); + describe('mixins inheritance', function() { assertDescriptionMixinInheritance(Info); assertExtensionsMixinInheritance(Info); From 1b6a9f55cc21b06a953bd90dd2b3757e9b0d31b6 Mon Sep 17 00:00:00 2001 From: Matatjahu Date: Fri, 25 Mar 2022 13:43:27 +0100 Subject: [PATCH 7/7] fix code after rebase --- src/models/server.ts | 16 ++-- src/models/v2/asyncapi.ts | 12 +-- src/models/v2/mixins/bindings.ts | 2 +- src/models/v2/mixins/extensions.ts | 2 +- src/models/v2/mixins/tags.ts | 6 +- src/models/v2/server.ts | 65 ++++++++------- src/models/v2/servers.ts | 14 ++-- src/models/v3/asyncapi.ts | 18 ++--- src/models/v3/server.ts | 65 ++++++++------- src/models/v3/servers.ts | 14 ++-- test/models/v2/mixins/inheritance.ts | 12 +-- test/models/v2/server.spec.ts | 115 ++++++++++----------------- test/models/v2/servers.spec.ts | 49 ++++++++++++ 13 files changed, 213 insertions(+), 177 deletions(-) create mode 100644 test/models/v2/servers.spec.ts diff --git a/src/models/server.ts b/src/models/server.ts index d9526fd3d..70576066b 100644 --- a/src/models/server.ts +++ b/src/models/server.ts @@ -1,10 +1,10 @@ -import { BaseModel } from "./base"; -import { BindingsMixinInterface, DescriptionMixinInterface } from './mixins'; +import type { BaseModel } from "./base"; +import type { BindingsMixinInterface, DescriptionMixinInterface, ExtensionsMixinInterface } from './mixins'; -export interface ServerInterface extends BaseModel, DescriptionMixinInterface, BindingsMixinInterface { - id(): string - protocol(): string | undefined; - protocolVersion(): string; - hasProtocolVersion(): boolean; - url(): string; +export interface ServerInterface extends BaseModel, DescriptionMixinInterface, BindingsMixinInterface, ExtensionsMixinInterface { + id(): string + url(): string; + protocol(): string | undefined; + protocolVersion(): string; + hasProtocolVersion(): boolean; } \ No newline at end of file diff --git a/src/models/v2/asyncapi.ts b/src/models/v2/asyncapi.ts index 59af64562..65d35296b 100644 --- a/src/models/v2/asyncapi.ts +++ b/src/models/v2/asyncapi.ts @@ -1,13 +1,13 @@ import { BaseModel } from "../base"; import { Info } from "./info"; +import { Servers } from "./servers"; +import { Server } from "./server"; import { Mixin } from '../utils'; import { ExtensionsMixin } from './mixins/extensions'; -import { AsyncAPIDocumentInterface, InfoInterface } from "../../models"; -import { ServersInterface } from "models/servers"; -import { Servers } from "./servers"; -import { Server } from "./server"; +import type { AsyncAPIDocumentInterface, InfoInterface } from "../../models"; +import type { ServersInterface } from "models/servers"; export class AsyncAPIDocument extends Mixin(BaseModel, ExtensionsMixin) @@ -23,7 +23,9 @@ export class AsyncAPIDocument servers(): ServersInterface { return new Servers( - Object.entries(this._json.servers).map(([serverName, server]) => new Server(serverName, server as Record)) + Object.entries(this._json.servers).map(([serverName, server]) => + this.createModel(Server, server, { id: serverName, pointer: `/servers/${serverName}` }) + ) ); } } diff --git a/src/models/v2/mixins/bindings.ts b/src/models/v2/mixins/bindings.ts index ae646e166..b1dfe9b2e 100644 --- a/src/models/v2/mixins/bindings.ts +++ b/src/models/v2/mixins/bindings.ts @@ -13,7 +13,7 @@ export class Binding extends Mixin(BaseModel, ExtensionsMixin) implements Bindin constructor( private readonly _protocol: string, _json: Record, - _meta: ModelMetadata, + _meta: ModelMetadata = {} as any, ) { super(_json, _meta); } diff --git a/src/models/v2/mixins/extensions.ts b/src/models/v2/mixins/extensions.ts index 9666a2b2f..b95175fa5 100644 --- a/src/models/v2/mixins/extensions.ts +++ b/src/models/v2/mixins/extensions.ts @@ -12,7 +12,7 @@ export class Extension extends BaseModel implements ExtensionInterface { constructor( private readonly _id: string, _json: Record, - _meta: ModelMetadata, + _meta: ModelMetadata = {} as any, ) { super(_json, _meta); } diff --git a/src/models/v2/mixins/tags.ts b/src/models/v2/mixins/tags.ts index 220e5edd6..0134183e5 100644 --- a/src/models/v2/mixins/tags.ts +++ b/src/models/v2/mixins/tags.ts @@ -32,6 +32,10 @@ export class Tags extends Collection implements TagsInterface { export abstract class TagsMixin extends BaseModel implements TagsMixinInterface { tags(): TagsInterface { const tags = this._json.tags || []; - return new Tags(tags.map((tag: any, idx: number) => this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` }))); + return new Tags( + tags.map((tag: any, idx: number) => + this.createModel(Tag, tag, { pointer: `${this._meta.pointer}/tags/${idx}` }) + ) + ); } } \ No newline at end of file diff --git a/src/models/v2/server.ts b/src/models/v2/server.ts index 3f52dc724..672af9696 100644 --- a/src/models/v2/server.ts +++ b/src/models/v2/server.ts @@ -1,34 +1,39 @@ -import { Mixin } from '../utils'; import { BaseModel } from '../base'; -import { ServerInterface } from '../server'; -import { DescriptionMixin } from './mixins/description'; + +import { Mixin } from '../utils'; import { BindingsMixin } from './mixins/bindings'; +import { DescriptionMixin } from './mixins/description'; +import { ExtensionsMixin } from './mixins/extensions'; + +import type { ModelMetadata } from "../base"; +import type { ServerInterface } from '../server'; + +export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, ExtensionsMixin) implements ServerInterface { + constructor( + private readonly _id: string, + _json: Record, + _meta: ModelMetadata = {} as any, + ) { + super(_json, _meta); + } + + id(): string { + return this._id; + } + + url(): string { + return this._json.url; + } + + protocol(): string | undefined { + return this._json.protocol; + } + + hasProtocolVersion(): boolean { + return !!this._json.protocolVersion; + } -export class Server extends Mixin(BaseModel, DescriptionMixin, BindingsMixin) implements ServerInterface { - constructor( - private readonly _id: string, - _json: Record - ){ - super(_json); - } - - id(): string { - return this._id; - } - - protocol(): string | undefined { - return this.json('protocol'); - } - - hasProtocolVersion(): boolean { - return !!this.json('protocolVersion'); - } - - protocolVersion(): string { - return this.json('protocolVersion'); - } - - url(): string { - return this.json('url'); - } + protocolVersion(): string { + return this._json.protocolVersion; + } } \ No newline at end of file diff --git a/src/models/v2/servers.ts b/src/models/v2/servers.ts index c8e9fa0dc..68da626c9 100644 --- a/src/models/v2/servers.ts +++ b/src/models/v2/servers.ts @@ -3,11 +3,11 @@ import { ServerInterface } from '../server'; import { ServersInterface } from '../servers'; export class Servers extends Collection implements ServersInterface { - override get(id: string): ServerInterface | undefined { - return this.collections.find(server => server.id() === id); - } + override get(id: string): ServerInterface | undefined { + return this.collections.find(server => server.id() === id); + } - override has(id: string): boolean { - return this.collections.some(server => server.id() === id); - } -} \ No newline at end of file + override has(id: string): boolean { + return this.collections.some(server => server.id() === id); + } +} diff --git a/src/models/v3/asyncapi.ts b/src/models/v3/asyncapi.ts index e74139292..65d35296b 100644 --- a/src/models/v3/asyncapi.ts +++ b/src/models/v3/asyncapi.ts @@ -1,16 +1,16 @@ import { BaseModel } from "../base"; import { Info } from "./info"; +import { Servers } from "./servers"; +import { Server } from "./server"; import { Mixin } from '../utils'; import { ExtensionsMixin } from './mixins/extensions'; -import { ServersInterface } from "models/servers"; -import { Servers } from "./servers"; -import { Server } from "./server"; -import { AsyncAPIDocumentInterface, InfoInterface } from "../../models"; +import type { AsyncAPIDocumentInterface, InfoInterface } from "../../models"; +import type { ServersInterface } from "models/servers"; -export class AsyncAPIDocument - extends Mixin(BaseModel, ExtensionsMixin) +export class AsyncAPIDocument + extends Mixin(BaseModel, ExtensionsMixin) implements AsyncAPIDocumentInterface { version(): string { @@ -23,9 +23,9 @@ export class AsyncAPIDocument servers(): ServersInterface { return new Servers( - Object.entries(this._json.servers).map( - ([serverName, server]) => new Server(serverName, server as Record) + Object.entries(this._json.servers).map(([serverName, server]) => + this.createModel(Server, server, { id: serverName, pointer: `/servers/${serverName}` }) ) - ) + ); } } diff --git a/src/models/v3/server.ts b/src/models/v3/server.ts index 3f52dc724..672af9696 100644 --- a/src/models/v3/server.ts +++ b/src/models/v3/server.ts @@ -1,34 +1,39 @@ -import { Mixin } from '../utils'; import { BaseModel } from '../base'; -import { ServerInterface } from '../server'; -import { DescriptionMixin } from './mixins/description'; + +import { Mixin } from '../utils'; import { BindingsMixin } from './mixins/bindings'; +import { DescriptionMixin } from './mixins/description'; +import { ExtensionsMixin } from './mixins/extensions'; + +import type { ModelMetadata } from "../base"; +import type { ServerInterface } from '../server'; + +export class Server extends Mixin(BaseModel, BindingsMixin, DescriptionMixin, ExtensionsMixin) implements ServerInterface { + constructor( + private readonly _id: string, + _json: Record, + _meta: ModelMetadata = {} as any, + ) { + super(_json, _meta); + } + + id(): string { + return this._id; + } + + url(): string { + return this._json.url; + } + + protocol(): string | undefined { + return this._json.protocol; + } + + hasProtocolVersion(): boolean { + return !!this._json.protocolVersion; + } -export class Server extends Mixin(BaseModel, DescriptionMixin, BindingsMixin) implements ServerInterface { - constructor( - private readonly _id: string, - _json: Record - ){ - super(_json); - } - - id(): string { - return this._id; - } - - protocol(): string | undefined { - return this.json('protocol'); - } - - hasProtocolVersion(): boolean { - return !!this.json('protocolVersion'); - } - - protocolVersion(): string { - return this.json('protocolVersion'); - } - - url(): string { - return this.json('url'); - } + protocolVersion(): string { + return this._json.protocolVersion; + } } \ No newline at end of file diff --git a/src/models/v3/servers.ts b/src/models/v3/servers.ts index c8e9fa0dc..68da626c9 100644 --- a/src/models/v3/servers.ts +++ b/src/models/v3/servers.ts @@ -3,11 +3,11 @@ import { ServerInterface } from '../server'; import { ServersInterface } from '../servers'; export class Servers extends Collection implements ServersInterface { - override get(id: string): ServerInterface | undefined { - return this.collections.find(server => server.id() === id); - } + override get(id: string): ServerInterface | undefined { + return this.collections.find(server => server.id() === id); + } - override has(id: string): boolean { - return this.collections.some(server => server.id() === id); - } -} \ No newline at end of file + override has(id: string): boolean { + return this.collections.some(server => server.id() === id); + } +} diff --git a/test/models/v2/mixins/inheritance.ts b/test/models/v2/mixins/inheritance.ts index d1b473e36..f0fa18333 100644 --- a/test/models/v2/mixins/inheritance.ts +++ b/test/models/v2/mixins/inheritance.ts @@ -4,7 +4,9 @@ import { ExtensionsMixin } from '../../../../src/models/v2/mixins/extensions'; import { ExternalDocumentationMixin } from '../../../../src/models/v2/mixins/external-docs'; import { TagsMixin } from '../../../../src/models/v2/mixins/tags'; -export function assertBindingsMixinInheritance(model: typeof BindingsMixin) { +import type { Constructor } from '../../../../src/models/utils'; + +export function assertBindingsMixinInheritance(model: Constructor) { describe('BindingsMixin inheritance', function() { it(`check if ${model.name} model has inherited methods from BindingsMixin`, function() { expect(model.prototype.bindings).not.toEqual(undefined); @@ -14,7 +16,7 @@ export function assertBindingsMixinInheritance(model: typeof BindingsMixin) { }); } -export function assertDescriptionMixinInheritance(model: typeof DescriptionMixin) { +export function assertDescriptionMixinInheritance(model: Constructor) { describe('DescriptionMixin inheritance', function() { it(`check if ${model.name} model has inherited methods from DescriptionMixin`, function() { expect(model.prototype.hasDescription).not.toEqual(undefined); @@ -28,7 +30,7 @@ export function assertDescriptionMixinInheritance(model: typeof DescriptionMixin }); } -export function assertExtensionsMixinInheritance(model: typeof ExtensionsMixin) { +export function assertExtensionsMixinInheritance(model: Constructor) { describe('SpecificationExtensionsMixin inheritance', function() { it(`check if ${model.name} model has inherited methods from ExtensionsMixin`, function() { expect(model.prototype.extensions).not.toEqual(undefined); @@ -38,7 +40,7 @@ export function assertExtensionsMixinInheritance(model: typeof ExtensionsMixin) }); } -export function assertExternalDocumentationMixinInheritance(model: typeof ExternalDocumentationMixin) { +export function assertExternalDocumentationMixinInheritance(model: Constructor) { describe('ExternalDocsMixin inheritance', function() { it(`check if ${model.name} model has inherited methods from ExternalDocumentationMixin`, function() { expect(model.prototype.hasExternalDocs).not.toEqual(undefined); @@ -52,7 +54,7 @@ export function assertExternalDocumentationMixinInheritance(model: typeof Extern }); } -export function assertTagsMixinInheritance(model: typeof TagsMixin) { +export function assertTagsMixinInheritance(model: Constructor) { describe('TagsMixin inheritance', function() { it(`check if ${model.name} model has inherited methods from TagsMixin`, function() { expect(model.prototype.tags).not.toEqual(undefined); diff --git a/test/models/v2/server.spec.ts b/test/models/v2/server.spec.ts index ccb318834..3a43ee1da 100644 --- a/test/models/v2/server.spec.ts +++ b/test/models/v2/server.spec.ts @@ -1,92 +1,61 @@ import { Server } from '../../../src/models/v2/server'; -import { Servers } from '../../../src/models/v2/servers'; + +import { + assertDescriptionMixinInheritance, + assertExtensionsMixinInheritance, +} from './mixins/inheritance'; const doc = { - 'development': { - protocol: 'mqtt', - protocolVersion: '1.0.0', - url: 'development.gigantic-server.com' - } + 'development': { + protocol: 'mqtt', + protocolVersion: '1.0.0', + url: 'development.gigantic-server.com' + } }; const docItem = new Server('development', doc.development); const emptyItem = new Server('',{}); -describe('Servers model', function () { - describe('.isEmpty()', function () { - it('should return true if collection is empty', function () { - const servers = new Servers([]); - expect(servers.isEmpty()).toBeTruthy(); - }); - - it('should return false if collection is not empty', function () { - const servers = new Servers([docItem]); - expect(servers.isEmpty()).toBeFalsy(); - }); - }) - - describe('.get(id)', function () { - it('should return a specific server Object if it is present', function () { - const servers = new Servers([docItem]); - expect(servers.get('development')).toBeTruthy(); - }); - - it('should return undefined if a server is said Id is missing ', function () { - const servers = new Servers([]); - expect(servers.get('development')).toBeUndefined(); - }); - }) - - describe('.has(id)', function () { - - const servers = new Servers([docItem]); - - it('should return true if the said name is available', function () { - expect(servers.has('development')).toBeTruthy(); - }) - - it('should return false if the server name is missing', function () { - expect(servers.has('production')).toBeFalsy(); - }) - }) -}) - describe('Server Model', function () { - - describe('.id()', function () { - it('should return name if present', function () { - expect(docItem.id()).toMatch('development'); - }); + describe('.id()', function () { + it('should return name if present', function () { + expect(docItem.id()).toMatch('development'); }); + }); - describe('protocol()', function () { - it('should return protocol ', function () { - expect(docItem.protocol()).toMatch(doc.development.protocol); - }); + describe('protocol()', function () { + it('should return protocol ', function () { + expect(docItem.protocol()).toMatch(doc.development.protocol); }); + }); - describe('.hasProtocolVersion()', function () { - it('should return true if protocolVersion is not missing', function () { - expect(docItem.hasProtocolVersion()).toBeTruthy(); - }); + describe('.hasProtocolVersion()', function () { + it('should return true if protocolVersion is not missing', function () { + expect(docItem.hasProtocolVersion()).toBeTruthy(); + }); - it('should be false when protocolVersion is missing', function () { - expect(emptyItem.hasProtocolVersion()).toBeFalsy(); - }); - }) + it('should be false when protocolVersion is missing', function () { + expect(emptyItem.hasProtocolVersion()).toBeFalsy(); + }); + }) - describe('.protocolVersion()', function () { - it('should return protocolVersion', function () { - expect(docItem.protocolVersion()).toMatch(doc.development.protocolVersion); - }); + describe('.protocolVersion()', function () { + it('should return value', function () { + expect(docItem.protocolVersion()).toMatch(doc.development.protocolVersion); + }); - it('should return undefined protocolVersion when protocolVersion is missing', function () { - expect(emptyItem.protocolVersion()).toBeUndefined(); - }) + it('should return undefined when protocolVersion is missing', function () { + expect(emptyItem.protocolVersion()).toBeUndefined(); }) + }) - describe('.url()', function () { - it('should return url', function () { - expect(docItem.url()).toMatch(doc.development.url); - }); + describe('.url()', function () { + it('should return value', function () { + expect(docItem.url()).toMatch(doc.development.url); }); + }); + + describe('mixins inheritance', function() { + assertDescriptionMixinInheritance(Server); + assertExtensionsMixinInheritance(Server); + }); }) \ No newline at end of file diff --git a/test/models/v2/servers.spec.ts b/test/models/v2/servers.spec.ts new file mode 100644 index 000000000..f76ff7f3c --- /dev/null +++ b/test/models/v2/servers.spec.ts @@ -0,0 +1,49 @@ +import { Servers } from '../../../src/models/v2/servers'; +import { Server } from '../../../src/models/v2/server'; + +const doc = { + 'development': { + protocol: 'mqtt', + protocolVersion: '1.0.0', + url: 'development.gigantic-server.com' + } +}; +const docItem = new Server('development', doc.development); + +describe('Servers model', function () { + describe('.isEmpty()', function () { + it('should return true if collection is empty', function () { + const servers = new Servers([]); + expect(servers.isEmpty()).toBeTruthy(); + }); + + it('should return false if collection is not empty', function () { + const servers = new Servers([docItem]); + expect(servers.isEmpty()).toBeFalsy(); + }); + }); + + describe('.get(id)', function () { + it('should return a specific server Object if it is present', function () { + const servers = new Servers([docItem]); + expect(servers.get('development')).toBeTruthy(); + }); + + it('should return undefined if a server is said Id is missing ', function () { + const servers = new Servers([]); + expect(servers.get('development')).toBeUndefined(); + }); + }); + + describe('.has(id)', function () { + const servers = new Servers([docItem]); + + it('should return true if the said name is available', function () { + expect(servers.has('development')).toBeTruthy(); + }) + + it('should return false if the server name is missing', function () { + expect(servers.has('production')).toBeFalsy(); + }) + }) +}) \ No newline at end of file