-
-
Notifications
You must be signed in to change notification settings - Fork 385
test: add comprehensive test suite in conditionalGeneration.js #2030
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
Open
shivansh-source
wants to merge
2
commits into
asyncapi:master
Choose a base branch
from
shivansh-source:shivansh#123
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+332
−0
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,332 @@ | ||
| /** | ||
| * Comprehensive unit tests for lib/conditionalGeneration.js | ||
| */ | ||
|
|
||
| 'use strict'; | ||
|
|
||
| const log = require('loglevel'); | ||
| const logMessage = require('../lib/logMessages'); | ||
| const { isGenerationConditionMet } = require('../lib/conditionalGeneration'); | ||
|
|
||
| jest.mock('loglevel'); | ||
| jest.mock('../lib/logMessages', () => ({ | ||
| relativeSourceFileNotGenerated: jest.fn((path, subject) => `${path} not generated: ${subject}`), | ||
| conditionalGenerationMatched: jest.fn((path) => `conditionalGeneration matched: ${path}`), | ||
| conditionalFilesMatched: jest.fn((path) => `conditionalFiles matched: ${path}`), | ||
| })); | ||
| /** | ||
| * Build a minimal templateConfig with conditionalGeneration entries. | ||
| * | ||
| * @param {string} path - The matched condition path key | ||
| * @param {object} conditionOpts - Any subset of { subject, parameter, validate } | ||
| */ | ||
| function makeConditionalGenerationConfig(path, conditionOpts) { | ||
| return { | ||
| conditionalGeneration: { | ||
| [path]: conditionOpts, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Build a minimal templateConfig with conditionalFiles (deprecated API). | ||
| */ | ||
| function makeConditionalFilesConfig(path, conditionOpts) { | ||
| return { | ||
| conditionalFiles: { | ||
| [path]: conditionOpts, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Build a minimal AsyncAPI document mock suitable for use with isGenerationConditionMet. | ||
| * | ||
| * @param {object} [jsonData={}] - Plain object returned by asyncapiDocument.json(). | ||
| * @param {object|null} [serverData=null] - Plain object returned by the matched server's | ||
| * json() call. When non-null a serverMock `{ json }` is created and returned by | ||
| * serversMock.get(); when null serversMock.get() returns undefined. | ||
| * @returns {{ json: jest.Mock, servers: jest.Mock }} A mock AsyncAPI document where | ||
| * servers() always returns the same serversMock instance, making | ||
| * `doc.servers().get` assertions reliable across multiple calls. | ||
| */ | ||
| function makeAsyncapiDocument(jsonData = {}, serverData = null) { | ||
| const serverMock = serverData ? { json: jest.fn(() => serverData) } : undefined; | ||
| const serversMock = { get: jest.fn(() => serverMock) }; | ||
| return { | ||
| json: jest.fn(() => jsonData), | ||
| servers: jest.fn(() => serversMock), | ||
| }; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // isGenerationConditionMet – dispatch logic | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| describe('isGenerationConditionMet', () => { | ||
| const PATH = 'some/file.txt'; | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| }); | ||
| describe('when no matching config exists for the given path', () => { | ||
| it('returns undefined when both conditionalGeneration and conditionalFiles are absent', async () => { | ||
| const result = await isGenerationConditionMet({}, PATH, {}, makeAsyncapiDocument()); | ||
| expect(result).toBeUndefined(); | ||
| }); | ||
|
|
||
| it('returns undefined when the path key is not present in conditionalGeneration', async () => { | ||
| const config = makeConditionalGenerationConfig('other/path.txt', { validate: () => true }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, makeAsyncapiDocument()); | ||
| expect(result).toBeUndefined(); | ||
| }); | ||
| }); | ||
| describe('conditionalParameterGeneration path (conditionalGeneration, no subject)', () => { | ||
| it('returns true when the parameter value passes the validate function', async () => { | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'myParam', | ||
| validate: (val) => val === 'enabled', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { myParam: 'enabled' }, makeAsyncapiDocument()); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false when the parameter value fails the validate function', async () => { | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'myParam', | ||
| validate: (val) => val === 'enabled', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { myParam: 'disabled' }, makeAsyncapiDocument()); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false (and logs) when parameter value fails validate', async () => { | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'myParam', | ||
| validate: () => false, | ||
| }); | ||
| await isGenerationConditionMet(config, PATH, { myParam: 'anything' }, makeAsyncapiDocument()); | ||
| expect(log.debug).toHaveBeenCalledWith(logMessage.conditionalGenerationMatched(PATH)); | ||
| }); | ||
|
|
||
| it('returns false when validate function is missing (no subject, no validate)', async () => { | ||
| // validateStatus is called with a parameter value but no validate fn → false | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'myParam', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { myParam: 'anything' }, makeAsyncapiDocument()); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when parameter is present but undefined in templateParams', async () => { | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'undefinedParam', | ||
| validate: (val) => val !== undefined, | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, makeAsyncapiDocument()); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('passes the correct parameter value to validate', async () => { | ||
| const validateMock = jest.fn(() => true); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| parameter: 'serverName', | ||
| validate: validateMock, | ||
| }); | ||
| await isGenerationConditionMet(config, PATH, { serverName: 'production' }, makeAsyncapiDocument()); | ||
| expect(validateMock).toHaveBeenCalledWith('production'); | ||
| }); | ||
| }); | ||
| describe('conditionalSubjectGeneration path (conditionalGeneration with subject)', () => { | ||
| it('returns true when jmespath finds a value that passes validate', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'My API' } }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'info.title', | ||
| validate: (val) => val === 'My API', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false (and logs not-generated) when jmespath returns null', async () => { | ||
| const doc = makeAsyncapiDocument({}); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'info.title', | ||
| validate: () => true, | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| expect(log.debug).toHaveBeenCalledWith(logMessage.relativeSourceFileNotGenerated(PATH, 'info.title')); | ||
| }); | ||
|
|
||
| it('returns false when jmespath finds a value but validate returns false', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'Wrong API' } }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'info.title', | ||
| validate: (val) => val === 'My API', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when validate is missing even though subject resolves', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'My API' } }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'info.title', | ||
| // no validate | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('merges templateParams.server into the JMESPath search object when server is found', async () => { | ||
| const serverData = { url: 'https://my.server.com', protocol: 'https' }; | ||
| const doc = makeAsyncapiDocument({ info: {} }, serverData); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'server.protocol', | ||
| validate: (val) => val === 'https', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { server: 'myServer' }, doc); | ||
| expect(result).toBe(true); | ||
| // Confirm asyncapiDocument.servers().get() was called with the server param | ||
| expect(doc.servers().get).toHaveBeenCalledWith('myServer'); | ||
| }); | ||
|
|
||
| it('sets server to undefined in JMESPath object when templateParams.server is not found in document', async () => { | ||
| const doc = makeAsyncapiDocument({ info: {} }, null /* no server found */); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'server.protocol', | ||
| validate: (val) => val === 'https', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { server: 'missingServer' }, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('does not include server key when templateParams.server is absent', async () => { | ||
| const doc = makeAsyncapiDocument({ channels: { '/user': {} } }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'channels."/user"', | ||
| validate: (val) => !!val, | ||
| }); | ||
| // No server param in templateParams → server branch skipped | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('searches deep nested AsyncAPI fields via JMESPath', async () => { | ||
| const doc = makeAsyncapiDocument({ | ||
| components: { schemas: { User: { type: 'object' } } }, | ||
| }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'components.schemas.User.type', | ||
| validate: (val) => val === 'object', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('handles a JMESPath expression returning an empty array as falsy (no source)', async () => { | ||
| const doc = makeAsyncapiDocument({ servers: [] }); | ||
| const config = makeConditionalGenerationConfig(PATH, { | ||
| subject: 'servers[0]', | ||
| validate: () => true, | ||
| }); | ||
| // jmespath returns undefined/null for out-of-bounds → treated as falsy | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
| }); | ||
| describe('deprecated conditionalFiles path (with subject)', () => { | ||
| it('returns true when conditionalFiles subject resolves and validate passes', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { version: '1.0.0' } }); | ||
| const config = makeConditionalFilesConfig(PATH, { | ||
| subject: 'info.version', | ||
| validate: (val) => val === '1.0.0', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false when conditionalFiles subject is not found in document', async () => { | ||
| const doc = makeAsyncapiDocument({}); | ||
| const config = makeConditionalFilesConfig(PATH, { | ||
| subject: 'info.nonexistent', | ||
| validate: () => true, | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('returns false when validate fails in conditionalFiles path and logs deprecated message', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'Test' } }); | ||
| const config = makeConditionalFilesConfig(PATH, { | ||
| subject: 'info.title', | ||
| validate: () => false, | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| expect(log.debug).toHaveBeenCalledWith(logMessage.conditionalFilesMatched(PATH)); | ||
| }); | ||
|
|
||
| it('prefers conditionalFiles over conditionalGeneration when both are defined for the same path and conditionalFiles has subject', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'Test' } }); | ||
| const config = { | ||
| conditionalFiles: { | ||
| [PATH]: { subject: 'info.title', validate: (val) => val === 'Test' }, | ||
| }, | ||
| conditionalGeneration: { | ||
| [PATH]: { parameter: 'myParam', validate: () => false }, | ||
| }, | ||
| }; | ||
| const result = await isGenerationConditionMet(config, PATH, { myParam: 'yes' }, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
|
|
||
| it('returns false when conditionalFiles validate is missing (silent false)', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'Test' } }); | ||
| const config = makeConditionalFilesConfig(PATH, { | ||
| subject: 'info.title', | ||
| // no validate | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| }); | ||
|
|
||
| it('merges templateParams.server into JMESPath object for deprecated conditionalFiles path', async () => { | ||
| const serverData = { protocol: 'mqtt' }; | ||
| const doc = makeAsyncapiDocument({}, serverData); | ||
| const config = makeConditionalFilesConfig(PATH, { | ||
| subject: 'server.protocol', | ||
| validate: (val) => val === 'mqtt', | ||
| }); | ||
| const result = await isGenerationConditionMet(config, PATH, { server: 'broker' }, doc); | ||
| expect(result).toBe(true); | ||
| }); | ||
| }); | ||
| describe('validateStatus – missing validate function', () => { | ||
| it('returns false silently when validate is missing on a conditionalGeneration parameter path', async () => { | ||
| const config = makeConditionalGenerationConfig(PATH, { parameter: 'flag' }); | ||
| const result = await isGenerationConditionMet(config, PATH, { flag: 'true' }, makeAsyncapiDocument()); | ||
| expect(result).toBe(false); | ||
| // Should NOT log a "matched" message because validation was never defined | ||
| expect(log.debug).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('returns false silently when validate is missing on a conditionalGeneration subject path', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'x' } }); | ||
| const config = makeConditionalGenerationConfig(PATH, { subject: 'info.title' }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| expect(log.debug).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('returns false silently when validate is missing on a deprecated conditionalFiles subject path', async () => { | ||
| const doc = makeAsyncapiDocument({ info: { title: 'x' } }); | ||
| const config = makeConditionalFilesConfig(PATH, { subject: 'info.title' }); | ||
| const result = await isGenerationConditionMet(config, PATH, {}, doc); | ||
| expect(result).toBe(false); | ||
| expect(log.debug).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.