Skip to content

Commit c0dffa9

Browse files
authored
Merge branch 'master' into postmanClient-hopscotchClient
2 parents 1337ae5 + f02dc4d commit c0dffa9

11 files changed

+413
-14
lines changed

Diff for: apps/generator/docs/nunjucks-render-engine.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ async function asyncCamelCase(str, callback) {
7575
}
7676
```
7777

78-
In case you have more than one template and want to reuse filters, you can put them in a single library. You can configure such a library in the template configuration under `filters` property. To learn how to add such filters to configuration [read more about the configuration file](#configuration-file).
78+
In case you have more than one template and want to reuse filters, you can put them in a single library. You can configure such a library in the template configuration under `filters` property. To learn how to add such filters to configuration, [read more about the configuration file](configuration-file).
7979

8080

81-
You can also use the official AsyncAPI [nunjucks-filters](/apps/nunjucks-filters) that are by default included in the generator library.
81+
You can also use the official AsyncAPI [nunjucks-filters](https://github.com/asyncapi/generator/tree/master/apps/nunjucks-filters) which is included by default in the generator library.
82+
83+
> **Note:** The nunjucks-filters is deprecated, and you should migrate to react-renderer instead. For more details, read notes from release [@asyncapi/generator@2.6.0](https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0).

Diff for: apps/generator/lib/generator.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -943,10 +943,9 @@ class Generator {
943943
*/
944944
isNonRenderableFile(fileName) {
945945
const nonRenderableFiles = this.templateConfig.nonRenderableFiles || [];
946-
if (!Array.isArray(nonRenderableFiles)) return false;
947-
if (nonRenderableFiles.some(globExp => minimatch(fileName, globExp))) return true;
948-
if (isReactTemplate(this.templateConfig) && !isJsFile(fileName)) return true;
949-
return false;
946+
return Array.isArray(nonRenderableFiles) &&
947+
(nonRenderableFiles.some(globExp => minimatch(fileName, globExp)) ||
948+
(isReactTemplate(this.templateConfig) && !isJsFile(fileName)));
950949
}
951950

952951
/**

Diff for: apps/generator/lib/hooksRegistry.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const { exists, registerTypeScript } = require('./utils');
99
* @param {String} templateDir Directory where template is located.
1010
* @param {String} hooksDir Directory where local hooks are located.
1111
*/
12-
module.exports.registerHooks = async (hooks, templateConfig, templateDir, hooksDir) => {
12+
async function registerHooks (hooks, templateConfig, templateDir, hooksDir) {
1313
await registerLocalHooks(hooks, templateDir, hooksDir);
1414

1515
if (templateConfig && Object.keys(templateConfig).length > 0) await registerConfigHooks(hooks, templateDir, templateConfig);
@@ -122,3 +122,10 @@ function addHook(hooks, mod, config) {
122122
});
123123
return hooks;
124124
}
125+
126+
module.exports = {
127+
registerHooks,
128+
registerLocalHooks,
129+
registerConfigHooks,
130+
addHook
131+
};

Diff for: apps/generator/lib/parser.js

+10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ parser.getProperApiDocument = (asyncapiDocument, templateConfig = {}) => {
5050
return ConvertDocumentParserAPIVersion(asyncapiDocument, apiVersion);
5151
};
5252

53+
/**
54+
* Converts old parser options to the new format.
55+
*
56+
* @private - This function should not be used outside this module.
57+
* @param {object} oldOptions - The old options to convert.
58+
* @param {object} generator - The generator instance.
59+
* @returns {object} The converted options.
60+
*/
5361
// The new options for the v2 Parser are different from those for the v1 version, but in order not to release Generator v2, we are converting the old options of Parser to the new ones.
5462
function convertOldOptionsToNew(oldOptions, generator) {
5563
if (!oldOptions) return;
@@ -143,3 +151,5 @@ function canReadFn(uri, canRead) {
143151
}
144152
return false;
145153
}
154+
155+
module.exports.convertOldOptionsToNew = convertOldOptionsToNew;

Diff for: apps/generator/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@
4848
"license": "Apache-2.0",
4949
"homepage": "https://github.com/asyncapi/generator",
5050
"dependencies": {
51+
"@asyncapi/generator-hooks": "*",
5152
"@asyncapi/generator-react-sdk": "^1.1.2",
5253
"@asyncapi/multi-parser": "^2.1.1",
5354
"@asyncapi/nunjucks-filters": "*",
54-
"@asyncapi/generator-hooks": "*",
5555
"@asyncapi/parser": "^3.0.14",
5656
"@npmcli/arborist": "5.6.3",
5757
"@npmcli/config": "^8.0.2",

Diff for: apps/generator/test/hooksRegistry.test.js

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
const fs = require('fs');
5+
const path = require('path');
6+
const { addHook, registerLocalHooks, registerConfigHooks, registerHooks } = require('../lib/hooksRegistry');
7+
8+
jest.mock('fs');
9+
jest.mock('path');
10+
11+
describe('hooksRegistry', () => {
12+
let hooks;
13+
14+
beforeEach(() => {
15+
hooks = {}; // reset hooks for each test
16+
jest.clearAllMocks(); // Reset all mocks
17+
jest.resetModules(); // Reset modules
18+
});
19+
20+
describe('registerHooks', () => {
21+
it('registers both local and config hooks', async () => {
22+
const templateDir = path.join(__dirname, 'fixtures', 'template', 'hooks');
23+
const hooksDir = path.join(__dirname, 'hooks');
24+
25+
fs.mkdirSync(hooksDir, { recursive: true });
26+
fs.writeFileSync(path.join(hooksDir, 'preGenerate.js'), `
27+
module.exports = function localPreGenerateHook() {};
28+
`);
29+
30+
const templateConfig = {
31+
hooks: {
32+
'@asyncapi/hooks-module': ['configPreGenerateHook']
33+
}
34+
};
35+
36+
jest.mock('@asyncapi/hooks-module', () => ({
37+
preGenerate: [function configPreGenerateHook() {}]
38+
}), { virtual: true });
39+
40+
const result = await registerHooks(hooks, templateConfig, templateDir, 'hooks');
41+
42+
expect(result.preGenerate).toHaveLength(1);
43+
expect(result.preGenerate[0].name).toBe('configPreGenerateHook');
44+
45+
fs.rmSync(hooksDir, { recursive: true, force: true });
46+
});
47+
});
48+
49+
describe('registerLocalHooks', () => {
50+
const mockPreGenerateHook = function preGenerateHook() {};
51+
52+
beforeAll(() => {
53+
path.join.mockImplementation((...args) => args.join('/'));
54+
});
55+
56+
beforeEach(() => {
57+
fs.existsSync.mockReturnValue(true);
58+
fs.readdirSync.mockReturnValue(['preGenerate.js']);
59+
60+
jest.mock('fixtures/template/hooks/preGenerate.js', () => mockPreGenerateHook, { virtual: true });
61+
});
62+
63+
it('handles missing hooks directory', async () => {
64+
fs.existsSync.mockReturnValueOnce(false);
65+
66+
const result = await registerLocalHooks(hooks, '/non/existent/path', 'hooks');
67+
expect(result).toBe(hooks);
68+
expect(result.preGenerate).toBeUndefined();
69+
});
70+
71+
it('handles errors during hook loading', async () => {
72+
fs.existsSync.mockReturnValue(true);
73+
fs.readdirSync.mockReturnValue(['errorHook.js']);
74+
75+
jest.mock('fixtures/template/hooks/errorHook.js', () => {
76+
throw new Error('Mock import error');
77+
}, { virtual: true });
78+
79+
await expect(registerLocalHooks(hooks, 'fixtures/template', 'hooks'))
80+
.resolves.not.toThrow();
81+
82+
expect(hooks).toEqual({});
83+
});
84+
});
85+
86+
describe('registerConfigHooks', () => {
87+
it('registers hooks from template config', async () => {
88+
const templateDir = path.join(__dirname, 'fixtures', 'template');
89+
const templateConfig = {
90+
hooks: {
91+
'@asyncapi/hooks-module': ['preGenerateHook']
92+
}
93+
};
94+
95+
// Mock the hook module
96+
jest.mock('@asyncapi/hooks-module', () => ({
97+
preGenerate: [function preGenerateHook() {}]
98+
}), { virtual: true });
99+
100+
const result = await registerConfigHooks(hooks, templateDir, templateConfig);
101+
102+
expect(result.preGenerate).toHaveLength(1);
103+
expect(result.preGenerate[0].name).toBe('preGenerateHook');
104+
});
105+
106+
it('handles missing hooks in config', async () => {
107+
const result = await registerConfigHooks(hooks, '', {});
108+
expect(result).toBeUndefined();
109+
expect(hooks).toEqual({});
110+
});
111+
});
112+
113+
describe('addHooks', () => {
114+
it('adds hooks from module to hooks object', () => {
115+
const mod = {
116+
preGenerate: [function preGenerateHook() {}],
117+
postGenerate: [function postGenerateHook() {}]
118+
};
119+
120+
addHook(hooks, mod, null);
121+
122+
expect(hooks.preGenerate).toHaveLength(1);
123+
expect(hooks.postGenerate).toHaveLength(1);
124+
expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
125+
expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
126+
});
127+
128+
it('adds hooks from module.default if it exists', () => {
129+
const mod = {
130+
default: {
131+
preGenerate: [function preGenerateHook() {}],
132+
postGenerate: [function postGenerateHook() {}]
133+
}
134+
};
135+
136+
addHook(hooks, mod, null);
137+
138+
expect(hooks.preGenerate).toHaveLength(1);
139+
expect(hooks.postGenerate).toHaveLength(1);
140+
expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
141+
expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
142+
});
143+
144+
it('does not add hooks that are not in config', () => {
145+
const mod = {
146+
preGenerate: [function preGenerateHook() {}],
147+
postGenerate: [function postGenerateHook() {}]
148+
};
149+
const config = ['preGenerateHook'];
150+
151+
addHook(hooks, mod, config);
152+
153+
expect(hooks.preGenerate).toHaveLength(1);
154+
expect(hooks.postGenerate).toBeUndefined();
155+
expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
156+
});
157+
158+
it('adds hooks that are in config', () => {
159+
const mod = {
160+
preGenerate: [function preGenerateHook() {}],
161+
postGenerate: [function postGenerateHook() {}]
162+
};
163+
const config = ['preGenerateHook', 'postGenerateHook'];
164+
165+
addHook(hooks, mod, config);
166+
167+
expect(hooks.preGenerate).toHaveLength(1);
168+
expect(hooks.postGenerate).toHaveLength(1);
169+
expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
170+
expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
171+
});
172+
});
173+
});

Diff for: apps/generator/test/parser.test.js

+100-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const fs = require('fs');
22
const path = require('path');
3-
const { sanitizeTemplateApiVersion, usesNewAPI, parse } = require('../lib/parser');
3+
const { sanitizeTemplateApiVersion, usesNewAPI, parse, convertOldOptionsToNew } = require('../lib/parser');
44
const dummyV2Document = fs.readFileSync(path.resolve(__dirname, './docs/dummy.yml'), 'utf8');
55
const dummyV3Document = fs.readFileSync(path.resolve(__dirname, './docs/dummyV3.yml'), 'utf8');
66

@@ -99,9 +99,107 @@ describe('Parser', () => {
9999
});
100100
it('should be able to parse AsyncAPI v3 document for parser API v3', async () => {
101101
const parsedDocument = await parse(dummyV3Document, {}, {templateConfig: {apiVersion: 'v3'}});
102-
103102
expect(parsedDocument).toBeDefined();
104103
expect(parsedDocument.document.version()).toEqual('3.0.0');
105104
});
106105
});
106+
107+
describe('getProperApiDocument', () => {
108+
const parser_api_version = 'x-parser-api-version';
109+
const parser_spec_parsed = 'x-parser-spec-parsed';
110+
const parser_original_schema = 'x-parser-original-schema-format';
111+
const parser_original_payload = 'x-parser-original-payload';
112+
const parser_message = 'x-parser-message-parsed';
113+
it('should convert to old API if apiVersion is undefined', async () => {
114+
// parse calls the get properAPIDocument and returns it along with a diagnostic in a Object
115+
// so its like called the get properAPIDocument function and then returned the result
116+
const templateConfig = {};
117+
const properDocument = await parse(dummyV2Document, {}, { templateConfig });
118+
119+
// Validate that properDocument convert it to expected API version from the template
120+
expect(properDocument).toBeDefined();
121+
122+
expect(properDocument).toBeDefined();
123+
expect(properDocument.document._json).toBeDefined();
124+
expect(properDocument.document._json.asyncapi).toEqual('2.3.0');
125+
expect(properDocument.diagnostics).toBeDefined();
126+
expect(properDocument.diagnostics.length).toBeGreaterThan(0);
127+
128+
expect(properDocument.document._json[parser_api_version]).toBeDefined();
129+
expect(properDocument.document._json[parser_api_version]).toEqual(0);
130+
expect(properDocument.document._json[parser_spec_parsed]).toBeDefined();
131+
expect(properDocument.document._json[parser_spec_parsed]).toEqual(true);
132+
133+
expect(properDocument.document._meta).toBeDefined();
134+
expect(properDocument.document._meta).toEqual({});
135+
expect(properDocument.document._json.components.messages.dummyCreated).toBeDefined();
136+
expect(properDocument.document._json.components.messages.dummyCreated[parser_original_schema]).toBeDefined(); // Only old API includes this filed
137+
expect(properDocument.document._json.components.messages.dummyCreated[parser_original_payload]).toBeDefined();
138+
expect(properDocument.document._json.components.messages.dummyCreated[parser_message]).toBeDefined();
139+
});
140+
141+
it('should convert to specified API version', async () => {
142+
const templateConfig = { apiVersion: 'v2' };
143+
const properDocument = await parse(dummyV2Document, {}, { templateConfig });
144+
145+
// Validate that properDocument is defined
146+
expect(properDocument).toBeDefined();
147+
148+
// Validate that the document is converted to the specified API version
149+
expect(properDocument.document._json.asyncapi).toEqual('2.3.0');
150+
expect(properDocument.diagnostics).toBeDefined();
151+
expect(properDocument.diagnostics.length).toBeGreaterThan(0);
152+
153+
expect(properDocument.document._json[parser_api_version]).toBeDefined();
154+
expect(properDocument.document._json[parser_spec_parsed]).toBeDefined();
155+
expect(properDocument.document._json[parser_api_version]).toEqual(2);
156+
expect(properDocument.document._json[parser_spec_parsed]).toEqual(true);
157+
158+
expect(properDocument.document._meta).toBeDefined();
159+
expect(properDocument.document._json.components.messages.dummyCreated).toBeDefined();
160+
expect(properDocument.document._json.components.messages.dummyCreated[parser_original_schema]).toBeUndefined();
161+
expect(properDocument.document._json.components.messages.dummyCreated[parser_original_payload]).toBeUndefined();
162+
expect(properDocument.document._json.components.messages.dummyCreated[parser_message]).toBeUndefined();
163+
164+
// Validate that old API-specific functions and properties are not present
165+
expect(properDocument.oldApiSpecificFunction).toBeUndefined();
166+
expect(properDocument.oldApiSpecificProperty).toBeUndefined();
167+
expect(properDocument.anotherOldApiFunction).toBeUndefined();
168+
});
169+
});
170+
171+
describe('convertOldOptionsToNew', () => {
172+
it('should convert old options to new options', () => {
173+
const oldOptions = {
174+
path: './test/docs/',
175+
applyTraits: true,
176+
resolve: {
177+
http: {
178+
order: 1,
179+
canRead: true,
180+
read: jest.fn()
181+
}
182+
}
183+
};
184+
const generator = {
185+
mapBaseUrlToFolder: {
186+
url: 'https://schema.example.com/crm/',
187+
folder: './test/docs/'
188+
}
189+
};
190+
const newOptions = convertOldOptionsToNew(oldOptions, generator);
191+
192+
expect(newOptions).toBeDefined();
193+
expect(newOptions.source).toEqual(oldOptions.path);
194+
expect(newOptions.applyTraits).toEqual(oldOptions.applyTraits);
195+
expect(newOptions.__unstable).toBeDefined();
196+
expect(newOptions.__unstable.resolver.resolvers.length).toBeGreaterThan(0);
197+
});
198+
199+
it('should return undefined if oldOptions is not provided', () => {
200+
const newOptions = convertOldOptionsToNew(undefined, {});
201+
202+
expect(newOptions).toBeUndefined();
203+
});
204+
});
107205
});

Diff for: apps/hooks/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "Library with hooks function for templates using AsyncAPI Generator",
55
"main": "src/index.js",
66
"scripts": {
7-
"test": "echo There are no tests yet",
7+
"test": "npm run test:cleanup && jest --coverage",
8+
"test:cleanup": "rimraf \"test/temp\"",
89
"lint": "eslint --max-warnings 0 --config ../../.eslintrc --ignore-path ../../.eslintignore .",
910
"lint:fix": "eslint --fix --max-warnings 0 --config ../../.eslintrc --ignore-path ../../.eslintignore .",
1011
"generate:assets": "echo 'No additional assets need to be generated at the moment'"
@@ -32,6 +33,8 @@
3233
"devDependencies": {
3334
"eslint": "^6.8.0",
3435
"eslint-plugin-sonarjs": "^0.5.0",
35-
"jsdoc-to-markdown": "^7.1.1"
36+
"jest": "^27.3.1",
37+
"jsdoc-to-markdown": "^7.1.1",
38+
"rimraf": "^3.0.2"
3639
}
3740
}

0 commit comments

Comments
 (0)