diff --git a/src/document.ts b/src/document.ts new file mode 100644 index 000000000..d37112fb8 --- /dev/null +++ b/src/document.ts @@ -0,0 +1,41 @@ +import { newAsyncAPIDocument, AsyncAPIDocumentV2, AsyncAPIDocumentV3 } from './models'; +import { unstringify } from './stringify'; +import { createDetailedAsyncAPI } from './utils'; + +import { + xParserSpecParsed, + xParserSpecStringified, +} from './constants'; + +import type { AsyncAPIDocumentInterface } from './models'; + +export function toAsyncAPIDocument(maybeDoc: unknown): AsyncAPIDocumentInterface | undefined { + if (isAsyncAPIDocument(maybeDoc)) { + return maybeDoc; + } + if (!isParsedDocument(maybeDoc)) { + return; + } + return unstringify(maybeDoc) || newAsyncAPIDocument(createDetailedAsyncAPI(maybeDoc, maybeDoc as any)); +} + +export function isAsyncAPIDocument(maybeDoc: unknown): maybeDoc is AsyncAPIDocumentInterface { + return maybeDoc instanceof AsyncAPIDocumentV2 || maybeDoc instanceof AsyncAPIDocumentV3; +} + +export function isParsedDocument(maybeDoc: unknown): maybeDoc is Record { + if (typeof maybeDoc !== 'object' || maybeDoc === null) { + return false; + } + return Boolean((maybeDoc as Record)[xParserSpecParsed]); +} + +export function isStringifiedDocument(maybeDoc: unknown): maybeDoc is Record { + if (typeof maybeDoc !== 'object' || maybeDoc === null) { + return false; + } + return ( + Boolean((maybeDoc as Record)[xParserSpecParsed]) && + Boolean((maybeDoc as Record)[xParserSpecStringified]) + ); +} diff --git a/src/lint.ts b/src/lint.ts index d35b8210b..07aaef547 100644 --- a/src/lint.ts +++ b/src/lint.ts @@ -1,6 +1,7 @@ import { Document } from '@stoplight/spectral-core'; import { Yaml } from '@stoplight/spectral-parsers'; -import { toAsyncAPIDocument, normalizeInput, hasWarningDiagnostic, hasErrorDiagnostic } from './utils'; +import { toAsyncAPIDocument } from './document'; +import { normalizeInput, hasWarningDiagnostic, hasErrorDiagnostic } from './utils'; import type { IRunOpts } from '@stoplight/spectral-core'; import type { Parser } from './parser'; diff --git a/src/models/v2/schema.ts b/src/models/v2/schema.ts index 9d42fe3fd..9adf1d765 100644 --- a/src/models/v2/schema.ts +++ b/src/models/v2/schema.ts @@ -1,7 +1,9 @@ import { BaseModel } from '../base'; import { extensions, hasExternalDocs, externalDocs } from './mixins'; +import { retrievePossibleRef, hasRef } from '../../utils'; +import type { ModelMetadata } from '../base'; import type { ExtensionsInterface } from '../extensions'; import type { ExternalDocumentationInterface } from '../external-docs'; import type { SchemaInterface } from '../schema'; @@ -9,6 +11,14 @@ import type { SchemaInterface } from '../schema'; import type { v2 } from '../../spec-types'; export class Schema extends BaseModel implements SchemaInterface { + constructor( + _json: v2.AsyncAPISchemaObject, + _meta: ModelMetadata & { id: string, parent?: Schema } = {} as any, + ) { + _json = retrievePossibleRef(_json, _meta.pointer, _meta.asyncapi?.parsed); + super(_json, _meta); + } + uid(): string { return this._meta.id as any; } @@ -151,6 +161,7 @@ export class Schema extends BaseModel); validatedDoc[String(xParserSpecParsed)] = true; const detailed = createDetailedAsyncAPI(asyncapi as string | Record, validatedDoc); diff --git a/src/stringify.ts b/src/stringify.ts index 556400d9c..4c786ef19 100644 --- a/src/stringify.ts +++ b/src/stringify.ts @@ -1,6 +1,7 @@ import { AsyncAPIDocumentInterface, newAsyncAPIDocument } from './models'; -import { createDetailedAsyncAPI, isAsyncAPIDocument, isParsedDocument, isStringifiedDocument } from './utils'; +import { isAsyncAPIDocument, isParsedDocument, isStringifiedDocument } from './document'; +import { createDetailedAsyncAPI } from './utils'; import { xParserSpecStringified } from './constants'; import type { DetailedAsyncAPI } from './types'; @@ -45,10 +46,17 @@ export function unstringify(document: unknown): AsyncAPIDocumentInterface | unde // remove `x-parser-spec-stringified` extension delete (>parsed)[String(xParserSpecStringified)]; - traverseStringifiedDoc(document, undefined, document, new Map(), new Map()); + traverseStringifiedData(document, undefined, document, new Map(), new Map()); return newAsyncAPIDocument(createDetailedAsyncAPI(document as string, parsed as DetailedAsyncAPI['parsed'])); } +export function unfreeze(data: Record) { + const stringifiedData = JSON.stringify(data, refReplacer()); + const unstringifiedData = JSON.parse(stringifiedData); + traverseStringifiedData(unstringifiedData, undefined, unstringifiedData, new Map(), new Map()); + return unstringifiedData; +} + function refReplacer() { const modelPaths = new Map(); const paths = new Map(); @@ -82,7 +90,7 @@ function refReplacer() { } const refRoot = '$ref:$'; -function traverseStringifiedDoc(parent: any, field: string | undefined, root: any, objToPath: Map, pathToObj: Map) { +function traverseStringifiedData(parent: any, field: string | undefined, root: any, objToPath: Map, pathToObj: Map) { let objOrPath = parent; let path = refRoot; @@ -107,7 +115,7 @@ function traverseStringifiedDoc(parent: any, field: string | undefined, root: an // traverse all keys, only if object is array/object if (objOrPath === Object(objOrPath)) { for (const f in objOrPath) { - traverseStringifiedDoc(objOrPath, f, root, objToPath, pathToObj); + traverseStringifiedData(objOrPath, f, root, objToPath, pathToObj); } } } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index 1df9b86ca..c2e5d5bc9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,45 +1,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; -import { newAsyncAPIDocument, AsyncAPIDocumentInterface, AsyncAPIDocumentV2, AsyncAPIDocumentV3 } from './models'; -import { unstringify } from './stringify'; - -import { - xParserSpecParsed, - xParserSpecStringified, -} from './constants'; import type { ISpectralDiagnostic } from '@stoplight/spectral-core'; -import type { AsyncAPISemver, AsyncAPIObject, DetailedAsyncAPI, MaybeAsyncAPI } from 'types'; - -export function toAsyncAPIDocument(maybeDoc: unknown): AsyncAPIDocumentInterface | undefined { - if (isAsyncAPIDocument(maybeDoc)) { - return maybeDoc; - } - if (!isParsedDocument(maybeDoc)) { - return; - } - return unstringify(maybeDoc) || newAsyncAPIDocument(createDetailedAsyncAPI(maybeDoc, maybeDoc as any)); -} - -export function isAsyncAPIDocument(maybeDoc: unknown): maybeDoc is AsyncAPIDocumentInterface { - return maybeDoc instanceof AsyncAPIDocumentV2 || maybeDoc instanceof AsyncAPIDocumentV3; -} - -export function isParsedDocument(maybeDoc: unknown): maybeDoc is Record { - if (typeof maybeDoc !== 'object' || maybeDoc === null) { - return false; - } - return Boolean((maybeDoc as Record)[xParserSpecParsed]); -} - -export function isStringifiedDocument(maybeDoc: unknown): maybeDoc is Record { - if (typeof maybeDoc !== 'object' || maybeDoc === null) { - return false; - } - return ( - Boolean((maybeDoc as Record)[xParserSpecParsed]) && - Boolean((maybeDoc as Record)[xParserSpecStringified]) - ); -} +import type { AsyncAPISemver, AsyncAPIObject, DetailedAsyncAPI, MaybeAsyncAPI } from './types'; export function createDetailedAsyncAPI(source: string | Record, parsed: AsyncAPIObject): DetailedAsyncAPI { return { @@ -105,6 +67,10 @@ export function isObject(value: unknown): value is Record { return Boolean(value) && typeof value === 'object' && Array.isArray(value) === false; } +export function hasRef(value: unknown): value is { $ref: string } { + return isObject(value) && '$ref' in value && typeof value.$ref === 'string'; +} + export function tilde(str: string) { return str.replace(/[~/]{1}/g, (sub) => { switch (sub) { @@ -124,4 +90,38 @@ export function untilde(str: string) { } return sub; }); +} + +export function retrievePossibleRef(data: any & { $ref?: string }, pathOfData: string, spec: any = {}): any { + if (!hasRef(data)) { + return data; + } + + const refPath = serializePath(data.$ref); + if (pathOfData.startsWith(refPath)) { // starts by given path + return retrieveDeepData(spec, splitPath(refPath)) || data; + } else if (pathOfData.includes(refPath)) { // circular path in substring of path + const substringPath = pathOfData.split(refPath)[0]; + return retrieveDeepData(spec, splitPath(`${substringPath}${refPath}`)) || data; + } + return data; +} + +function retrieveDeepData(value: Record, path: string[]) { + let index = 0; + const length = path.length; + + while (typeof value === 'object' && value && index < length) { + value = value[path[index++]]; + } + return index === length ? value : undefined; +} + +function serializePath(path: string) { + if (path.startsWith('#')) return path.substring(1); + return path; +} + +function splitPath(path: string): string[] { + return path.split('/').filter(Boolean).map(untilde); } \ No newline at end of file diff --git a/test/document.spec.ts b/test/document.spec.ts new file mode 100644 index 000000000..d5ceb8c98 --- /dev/null +++ b/test/document.spec.ts @@ -0,0 +1,145 @@ +import { xParserSpecParsed, xParserSpecStringified } from '../src/constants'; +import { newAsyncAPIDocument, BaseModel, AsyncAPIDocumentV2 } from '../src/models'; +import { + toAsyncAPIDocument, + isAsyncAPIDocument, + isParsedDocument, + isStringifiedDocument, +} from '../src/document'; +import { createDetailedAsyncAPI } from '../src/utils'; + +describe('utils', function() { + class Model extends BaseModel {} + + describe('toAsyncAPIDocument()', function() { + it('normal object should not return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument({})).toEqual(undefined); + }); + + it('null object should not return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument(null)).toEqual(undefined); + }); + + it('primitive should not return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument('AsyncAPI rocks!')).toEqual(undefined); + }); + + it('BaseModel instance should not return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument(new Model({}))).toEqual(undefined); + }); + + it('AsyncAPIDocument instance should return AsyncAPIDocument instance', function() { + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(toAsyncAPIDocument(newAsyncAPIDocument(detailed))).toBeInstanceOf(AsyncAPIDocumentV2); + }); + + it('parsed document should return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true })).toBeInstanceOf(AsyncAPIDocumentV2); + }); + + it('stringified document should return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true })).toBeInstanceOf(AsyncAPIDocumentV2); + }); + + it('stringified document (with missed parsed extension) should not return AsyncAPIDocument instance', function() { + expect(toAsyncAPIDocument({ [xParserSpecStringified]: true })).toEqual(undefined); + }); + }); + + describe('isAsyncAPIDocument()', function() { + it('normal object should not be AsyncAPI document', function() { + expect(isAsyncAPIDocument({})).toEqual(false); + }); + + it('null object should not be AsyncAPI document', function() { + expect(isAsyncAPIDocument(null)).toEqual(false); + }); + + it('primitive should not be AsyncAPI document', function() { + expect(isAsyncAPIDocument('AsyncAPI rocks!')).toEqual(false); + }); + + it('BaseModel instance should not be AsyncAPI document', function() { + expect(isAsyncAPIDocument(new Model({}))).toEqual(false); + }); + + it('AsyncAPIDocument instance should be AsyncAPI document', function() { + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(isAsyncAPIDocument(newAsyncAPIDocument(detailed))).toEqual(true); + }); + }); + + describe('isParsedDocument()', function() { + it('normal object should not be parsed document', function() { + expect(isParsedDocument({})).toEqual(false); + }); + + it('null object should not be parsed document', function() { + expect(isParsedDocument(null)).toEqual(false); + }); + + it('primitive should not be parsed document', function() { + expect(isParsedDocument('AsyncAPI rocks!')).toEqual(false); + }); + + it('BaseModel instance should not be AsyncAPI document', function() { + expect(isParsedDocument(new Model({}))).toEqual(false); + }); + + it('AsyncAPIDocument instance should not be parsed document', function() { + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); + }); + + it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { + const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); + }); + + it('object with proper extension should be parsed document', function() { + expect(isParsedDocument({ [xParserSpecParsed]: true })).toEqual(true); + }); + }); + + describe('isStringifiedDocument()', function() { + it('normal object should not be parsed document', function() { + expect(isStringifiedDocument({})).toEqual(false); + }); + + it('null object should not be parsed document', function() { + expect(isStringifiedDocument(null)).toEqual(false); + }); + + it('primitive should not be parsed document', function() { + expect(isStringifiedDocument('AsyncAPI rocks!')).toEqual(false); + }); + + it('BaseModel instance should not be AsyncAPI document', function() { + expect(isStringifiedDocument(new Model({}))).toEqual(false); + }); + + it('AsyncAPIDocument instance should not be parsed document', function() { + const doc = { asyncapi: '2.0.0' }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); + }); + + it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { + const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true }; + const detailed = createDetailedAsyncAPI(doc, doc as any); + expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); + }); + + it('object with only stringified extension should not be parsed document', function() { + expect(isStringifiedDocument({ [xParserSpecStringified]: true })).toEqual(false); + }); + + it('object with proper extensions should be parsed document', function() { + expect(isStringifiedDocument({ [xParserSpecParsed]: true, [xParserSpecStringified]: true })).toEqual(true); + }); + }); +}); diff --git a/test/mocks/parse/circular-ref.yaml b/test/mocks/parse/circular-ref.yaml new file mode 100644 index 000000000..6e26efca0 --- /dev/null +++ b/test/mocks/parse/circular-ref.yaml @@ -0,0 +1,9 @@ +type: 'object' +properties: + deepProperty: + type: 'object' + properties: + property: + type: 'number' + circular: + $ref: '#/properties/deepProperty' \ No newline at end of file diff --git a/test/parse.spec.ts b/test/parse.spec.ts index c4bad0ff4..91d92cc76 100644 --- a/test/parse.spec.ts +++ b/test/parse.spec.ts @@ -24,7 +24,7 @@ describe('parse()', function() { const document = { asyncapi: '2.0.0', info: { - title: 'Valid AsyncApi document', + title: 'Invalid AsyncApi document', version: '1.0', }, }; @@ -33,4 +33,123 @@ describe('parse()', function() { expect(parsed).toEqual(undefined); expect(diagnostics.length > 0).toEqual(true); }); + + it('should preserve references', async function() { + const document = { + asyncapi: '2.0.0', + info: { + title: 'Valid AsyncApi document', + version: '1.0', + }, + channels: { + channel: { + publish: { + operationId: 'publishOperation', + message: { + $ref: '#/components/messages/message' + } + }, + subscribe: { + operationId: 'subscribeOperation', + message: { + $ref: '#/components/messages/message' + } + } + } + }, + components: { + messages: { + message: { + payload: { + type: 'object', + } + } + } + } + }; + const { parsed } = await parse(parser, document); + + const publishMessage = parsed?.channels().get('channel')?.operations().get('publishOperation')?.messages()[0]; + const subscribeMessage = parsed?.channels().get('channel')?.operations().get('subscribeOperation')?.messages()[0]; + const componentsMessage = parsed?.components()?.messages()?.get('message'); + expect(publishMessage?.json() !== undefined).toEqual(true); + expect(subscribeMessage?.json() !== undefined).toEqual(true); + expect(componentsMessage?.json() !== undefined).toEqual(true); + expect(componentsMessage?.json() === publishMessage?.json()).toEqual(true); + expect(componentsMessage?.json() === subscribeMessage?.json()).toEqual(true); + expect(publishMessage?.json() === subscribeMessage?.json()).toEqual(true); + }); + + it('should parse circular references (in this same document)', async function() { + const document = { + asyncapi: '2.0.0', + info: { + title: 'Valid AsyncApi document', + version: '1.0', + }, + channels: { + channel: { + publish: { + operationId: 'someId', + message: { + payload: { + properties: { + nonCircular: { + type: 'string', + }, + circular: { + $ref: '#/channels/channel/publish/message/payload' + } + } + } + } + } + } + } + }; + const { parsed } = await parse(parser, document); + + const messagePayload = parsed?.channels().get('channel')?.operations().get('someId')?.messages()[0].payload(); + expect(messagePayload?.json() !== undefined).toEqual(true); + expect(messagePayload?.properties()?.['circular'].json() !== undefined).toEqual(true); + expect(messagePayload?.properties()?.['circular'].json() === messagePayload?.json()).toEqual(true); // expect that same reference + }); + + it('should parse circular references (in external file)', async function() { + const document = { + asyncapi: '2.0.0', + info: { + title: 'Valid AsyncApi document', + version: '1.0', + }, + channels: { + channel: { + publish: { + operationId: 'someId', + message: { + payload: { + properties: { + nonCircular: { + type: 'string', + }, + circular: { + $ref: './mocks/parse/circular-ref.yaml' + } + } + } + } + } + } + } + }; + const { parsed } = await parse(parser, document, { validateOptions: { path: __filename } }); + + const messagePayload = parsed?.channels().get('channel')?.operations().get('someId')?.messages()[0].payload(); + const circular = messagePayload?.properties()?.['circular']; + const deepProperty = circular?.properties()?.['deepProperty']; + const deepCircular = deepProperty?.properties()?.['circular']; + expect(deepProperty?.json() !== undefined).toEqual(true); + expect(deepCircular?.json() !== undefined).toEqual(true); + expect(deepProperty?.json() === deepCircular?.json()).toEqual(true); // expect that same reference + }); }); diff --git a/test/utils.spec.ts b/test/utils.spec.ts index 746646c7a..723de2174 100644 --- a/test/utils.spec.ts +++ b/test/utils.spec.ts @@ -1,13 +1,7 @@ import { DiagnosticSeverity } from '@stoplight/types'; import { cloneDeep } from 'lodash'; -import { xParserSpecParsed, xParserSpecStringified } from '../src/constants'; -import { newAsyncAPIDocument, BaseModel, AsyncAPIDocumentV2 } from '../src/models'; import { - toAsyncAPIDocument, - isAsyncAPIDocument, - isParsedDocument, - isStringifiedDocument, hasErrorDiagnostic, hasWarningDiagnostic, createDetailedAsyncAPI, @@ -19,140 +13,6 @@ 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); - }); - - it('null object should not return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument(null)).toEqual(undefined); - }); - - it('primitive should not return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument('AsyncAPI rocks!')).toEqual(undefined); - }); - - it('BaseModel instance should not return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument(new Model({}))).toEqual(undefined); - }); - - it('AsyncAPIDocument instance should return AsyncAPIDocument instance', function() { - const doc = { asyncapi: '2.0.0' }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(toAsyncAPIDocument(newAsyncAPIDocument(detailed))).toBeInstanceOf(AsyncAPIDocumentV2); - }); - - it('parsed document should return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true })).toBeInstanceOf(AsyncAPIDocumentV2); - }); - - it('stringified document should return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument({ asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true })).toBeInstanceOf(AsyncAPIDocumentV2); - }); - - it('stringified document (with missed parsed extension) should not return AsyncAPIDocument instance', function() { - expect(toAsyncAPIDocument({ [xParserSpecStringified]: true })).toEqual(undefined); - }); - }); - - describe('isAsyncAPIDocument()', function() { - it('normal object should not be AsyncAPI document', function() { - expect(isAsyncAPIDocument({})).toEqual(false); - }); - - it('null object should not be AsyncAPI document', function() { - expect(isAsyncAPIDocument(null)).toEqual(false); - }); - - it('primitive should not be AsyncAPI document', function() { - expect(isAsyncAPIDocument('AsyncAPI rocks!')).toEqual(false); - }); - - it('BaseModel instance should not be AsyncAPI document', function() { - expect(isAsyncAPIDocument(new Model({}))).toEqual(false); - }); - - it('AsyncAPIDocument instance should be AsyncAPI document', function() { - const doc = { asyncapi: '2.0.0' }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(isAsyncAPIDocument(newAsyncAPIDocument(detailed))).toEqual(true); - }); - }); - - describe('isParsedDocument()', function() { - it('normal object should not be parsed document', function() { - expect(isParsedDocument({})).toEqual(false); - }); - - it('null object should not be parsed document', function() { - expect(isParsedDocument(null)).toEqual(false); - }); - - it('primitive should not be parsed document', function() { - expect(isParsedDocument('AsyncAPI rocks!')).toEqual(false); - }); - - it('BaseModel instance should not be AsyncAPI document', function() { - expect(isParsedDocument(new Model({}))).toEqual(false); - }); - - it('AsyncAPIDocument instance should not be parsed document', function() { - const doc = { asyncapi: '2.0.0' }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); - }); - - it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { - const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(isParsedDocument(newAsyncAPIDocument(detailed))).toEqual(false); - }); - - it('object with proper extension should be parsed document', function() { - expect(isParsedDocument({ [xParserSpecParsed]: true })).toEqual(true); - }); - }); - - describe('isStringifiedDocument()', function() { - it('normal object should not be parsed document', function() { - expect(isStringifiedDocument({})).toEqual(false); - }); - - it('null object should not be parsed document', function() { - expect(isStringifiedDocument(null)).toEqual(false); - }); - - it('primitive should not be parsed document', function() { - expect(isStringifiedDocument('AsyncAPI rocks!')).toEqual(false); - }); - - it('BaseModel instance should not be AsyncAPI document', function() { - expect(isStringifiedDocument(new Model({}))).toEqual(false); - }); - - it('AsyncAPIDocument instance should not be parsed document', function() { - const doc = { asyncapi: '2.0.0' }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); - }); - - it('AsyncAPIDocument instance with proper extension should not be parsed document', function() { - const doc = { asyncapi: '2.0.0', [xParserSpecParsed]: true, [xParserSpecStringified]: true }; - const detailed = createDetailedAsyncAPI(doc, doc as any); - expect(isStringifiedDocument(newAsyncAPIDocument(detailed))).toEqual(false); - }); - - it('object with only stringified extension should not be parsed document', function() { - expect(isStringifiedDocument({ [xParserSpecStringified]: true })).toEqual(false); - }); - - it('object with proper extensions should be parsed document', function() { - expect(isStringifiedDocument({ [xParserSpecParsed]: true, [xParserSpecStringified]: true })).toEqual(true); - }); - }); - describe('hasErrorDiagnostic()', function() { const simpleDiagnostic: Diagnostic = { code: 'test-code',