Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: add missed tests for custom operations #634

Merged
merged 1 commit into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added asyncapi-parser-2.0.0.tgz
Binary file not shown.
2 changes: 1 addition & 1 deletion src/custom-operations/anonymous-naming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function assignNameToAnonymousMessages(document: AsyncAPIDocumentInterface) {
let anonymousMessageCounter = 0;
document.messages().forEach(message => {
if (message.name() === undefined && message.extensions().get(xParserMessageName)?.value() === undefined) {
setExtension(xParserMessageName, `<anonymous-message-${++anonymousMessageCounter}>`, message);
setExtension(xParserMessageName, message.id() || `<anonymous-message-${++anonymousMessageCounter}>`, message);
}
});
}
Expand Down
11 changes: 9 additions & 2 deletions src/custom-operations/apply-traits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,24 @@ export function applyTraitsV3(asyncapi: v2.AsyncAPIObject) { // TODO: Change typ
}

function applyAllTraits(asyncapi: Record<string, any>, paths: string[]) {
const visited: Set<unknown> = new Set();
paths.forEach(path => {
JSONPath({
path,
json: asyncapi,
resultType: 'value',
callback(value) { applyTraits(value); },
callback(value) {
if (visited.has(value)) {
return;
}
visited.add(value);
applyTraits(value);
},
});
});
}

function applyTraits(value: Record<string, unknown>) {
function applyTraits(value: Record<string, unknown> & { traits?: any[] }) {
if (Array.isArray(value.traits)) {
for (const trait of value.traits) {
for (const key in trait) {
Expand Down
6 changes: 3 additions & 3 deletions src/custom-operations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ export async function customOperations(parser: Parser, document: AsyncAPIDocumen
}

async function operationsV2(parser: Parser, document: AsyncAPIDocumentInterface, detailed: DetailedAsyncAPI, options: ParseOptions): Promise<void> {
checkCircularRefs(document);
anonymousNaming(document);

if (options.applyTraits) {
applyTraitsV2(detailed.parsed);
}
if (options.parseSchemas) {
await parseSchemasV2(parser, detailed);
}

checkCircularRefs(document);
anonymousNaming(document);
}

4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ export function hasRef(value: unknown): value is { $ref: string } {
return isObject(value) && '$ref' in value && typeof value.$ref === 'string';
}

export function toJSONPathArray(jsonPath: string): Array<string | number> {
return jsonPath.split('/').map(untilde);
}

export function tilde(str: string) {
return str.replace(/[~/]{1}/g, (sub) => {
switch (sub) {
Expand Down
31 changes: 31 additions & 0 deletions test/custom-operations/anonymous-naming.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,37 @@ describe('custom operations - anonymous naming', function() {
expect(document?.components().messages()[0].extensions().get(xParserMessageName)?.value()).toEqual('message');
});

it('should try use messageId for x-parser-message-name', async function() {
const { document } = await parser.parse({
asyncapi: '2.4.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'operation',
message: {
$ref: '#/components/messages/message'
}
}
}
},
components: {
messages: {
message: {
messageId: 'someId',
payload: {}
}
}
}
});

expect(document?.messages()).toHaveLength(1);
expect(document?.messages()[0].extensions().get(xParserMessageName)?.value()).toEqual('someId');
});

it('should not override x-parser-message-name if it exists', async function() {
const { document } = await parser.parse({
asyncapi: '2.0.0',
Expand Down
186 changes: 186 additions & 0 deletions test/custom-operations/apply-traits.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { AsyncAPIDocumentV2 } from '../../src/models';
import { Parser } from '../../src/parser';

import type { v2 } from '../../src/spec-types';

describe('custom operations - apply traits', function() {
const parser = new Parser();

it('should apply traits to operations', async function() {
const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publishId',
traits: [
{
operationId: 'anotherPubId',
description: 'some description'
},
{
description: 'another description'
}
]
},
subscribe: {
operationId: 'subscribeId',
traits: [
{
operationId: 'anotherSubId',
description: 'some description'
},
{
description: 'another description'
}
]
}
}
}
};
const { document, diagnostics } = await parser.parse(documentRaw);

expect(document).toBeInstanceOf(AsyncAPIDocumentV2);
expect(diagnostics.length > 0).toEqual(true);

const publish = document?.json()?.channels?.channel?.publish;
delete publish?.traits;
expect(publish).toEqual({ operationId: 'anotherPubId', description: 'another description' });

const subscribe = document?.json()?.channels?.channel?.subscribe;
delete subscribe?.traits;
expect(subscribe).toEqual({ operationId: 'anotherSubId', description: 'another description' });
});

it('should apply traits to messages', async function() {
const documentRaw = {
asyncapi: '2.4.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'operationId',
message: {
messageId: 'messageId',
traits: [
{
messageId: 'anotherMessageId1',
description: 'some description'
},
{
description: 'another description'
}
]
}
},
subscribe: {
message: {
oneOf: [
{
messageId: 'messageId',
traits: [
{
messageId: 'anotherMessageId2',
description: 'some description'
},
{
description: 'another description'
}
]
},
{
messageId: 'messageId',
traits: [
{
messageId: 'anotherId',
description: 'some description'
},
{
description: 'another description'
},
{
messageId: 'anotherMessageId3',
description: 'simple description'
}
]
}
],
}
}
}
}
};
const { document, diagnostics } = await parser.parse(documentRaw);

expect(document).toBeInstanceOf(AsyncAPIDocumentV2);
expect(diagnostics.length > 0).toEqual(true);

const message = document?.json()?.channels?.channel?.publish?.message;
delete (message as v2.MessageObject)?.traits;
expect(message).toEqual({ messageId: 'anotherMessageId1', description: 'another description', 'x-parser-message-name': 'anotherMessageId1' });

const messageOneOf1 = (document?.json()?.channels?.channel?.subscribe?.message as { oneOf: Array<v2.MessageObject> }).oneOf[0];
delete messageOneOf1?.traits;
expect(messageOneOf1).toEqual({ messageId: 'anotherMessageId2', description: 'another description', 'x-parser-message-name': 'anotherMessageId2' });

const messageOneOf2 = (document?.json()?.channels?.channel?.subscribe?.message as { oneOf: Array<v2.MessageObject> }).oneOf[1];
delete messageOneOf2?.traits;
expect(messageOneOf2).toEqual({ messageId: 'anotherMessageId3', description: 'simple description', 'x-parser-message-name': 'anotherMessageId3' });
});

it('should preserve this same references', async function() {
const documentRaw = {
asyncapi: '2.4.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'publishId',
message: {
$ref: '#/components/messages/message',
}
},
}
},
components: {
messages: {
message: {
messageId: 'messageId',
traits: [
{
messageId: 'anotherId',
description: 'some description'
},
{
description: 'another description'
},
{
messageId: 'anotherMessageId3',
description: 'simple description'
}
]
}
}
}
};
const { document, diagnostics } = await parser.parse(documentRaw);

expect(document).toBeInstanceOf(AsyncAPIDocumentV2);
expect(diagnostics.length > 0).toEqual(true);

const message = document?.json()?.channels?.channel?.publish?.message;
delete (message as v2.MessageObject)?.traits;
expect(message).toEqual({ messageId: 'anotherMessageId3', description: 'simple description', 'x-parser-message-name': 'anotherMessageId3' });
expect(message === document?.json()?.components?.messages?.message).toEqual(true);
});
});
38 changes: 38 additions & 0 deletions test/custom-operations/parse-schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,44 @@ describe('custom operations - parse schemas', function() {
expect((document?.json()?.channels?.channel?.publish?.message as v2.MessageObject)?.payload).toEqual({ type: 'object', 'x-parser-schema-id': '<anonymous-schema-1>' });
});

it('should preserve this same references', async function() {
const documentRaw = {
asyncapi: '2.0.0',
info: {
title: 'Valid AsyncApi document',
version: '1.0',
},
channels: {
channel: {
publish: {
operationId: 'operationId',
message: {
$ref: '#/components/messages/message'
}
}
}
},
components: {
messages: {
message: {
payload: {
type: 'object',
}
}
}
}
};
const { document, diagnostics } = await parser.parse(documentRaw);

expect(document).toBeInstanceOf(AsyncAPIDocumentV2);
expect(diagnostics.length > 0).toEqual(true);

expect((document?.json()?.channels?.channel?.publish?.message as v2.MessageObject)?.payload).toEqual({ type: 'object', 'x-parser-schema-id': '<anonymous-schema-1>' });
expect((document?.json().components?.messages?.message as v2.MessageObject)?.payload).toEqual({ type: 'object', 'x-parser-schema-id': '<anonymous-schema-1>' });
// check if logic preserves references
expect((document?.json()?.channels?.channel?.publish?.message as v2.MessageObject)?.payload === (document?.json().components?.messages?.message as v2.MessageObject)?.payload).toEqual(true);
});

it('should parse invalid schema format', async function() {
const documentRaw = {
asyncapi: '2.0.0',
Expand Down
2 changes: 1 addition & 1 deletion test/from.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe('fromURL() & fromFile()', function() {

describe('fromURL()', function() {
it('should operate on existing HTTP source', async function() {
const { document, diagnostics } = await fromURL(parser, 'https://raw.githubusercontent.com/asyncapi/spec/master/examples/simple.yml').parse();
const { document, diagnostics } = await fromURL(parser, 'https://raw.githubusercontent.com/asyncapi/spec/v2.0.0/examples/2.0.0/gitter-streaming.yml').parse();
expect(document).not.toBeUndefined();
expect(diagnostics.length > 0).toEqual(true);
expect(hasWarningDiagnostic(diagnostics)).toEqual(true);
Expand Down