Skip to content
Open
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
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ npm run test:example
```
We added jest snapshots to cover the integration tests, and you can run:
```bash
npm test
npm test:snapshot
```

We setup unit tests and use jest as the runner, and you use the command:
```bash
npm test:unit
```

For local development, you need different variations of this command. First of all, you need to know about three important CLI flags:
Expand Down
2 changes: 1 addition & 1 deletion components/OperationFunction.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function GenerateReceiveFunctions({ operations, className}) {
The Comments Service defined in the AsyncAPI file processes events using the MQTT protocol
to handle different operations such as Send/Publish for comments like and unlike and Receive/Subscribe views.

OperactionFunction code includes two major functions one to GenerateSendFunctions and the other to GenerateReceiveFunctions.
OperationFunction code includes two major functions one to GenerateSendFunctions and the other to GenerateReceiveFunctions.
These functions generate send and receive according to the definition of the AsyncAPI file. These functions will then create the client
implementation `client.py`

Expand Down
19 changes: 19 additions & 0 deletions components/helpers/channel-topics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import filenamify from 'filenamify';

/**
* Converts a channel address into a safe filename.
* - Splits the address by '/' and formats each part to lowercase.
* - Uses `filenamify` to remove invalid filename characters.
*
* @param {string} channelAddress - The original channel address.
* @returns {string} A sanitized and formatted filename.
*/

export function convertChannelToFilename(channelAddress) {
if (!channelAddress) return '';

const formatted = channelAddress
.split('/').map(word => word.charAt(0).toLowerCase() + word.slice(1)).join('');

return filenamify(formatted, { replacement: '-', maxLength: 255 });
}
12 changes: 8 additions & 4 deletions components/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { convertChannelToFilename } from './channel-topics';

// Function to generate service client name
export function getServiceClientName(asyncapi) {
return `${asyncapi.info().title()} Client`;
};

// Function to generate service client description
export function getServiceClientDescription(asyncapi) {
return `${asyncapi.info().description()}`;
const hasDesc = asyncapi.info().hasDescription();
const description = hasDesc ? `${asyncapi.info().description()}` : '';
return description;
};

// Function to import client service
Expand All @@ -15,9 +19,9 @@ export function getClientClassName(asyncapi) {

// Returns functionName
export function getFunctionName(operation) {

const str = operation.operationId() || operation.id();
return str.split('/').map(word => word.charAt(0).toLowerCase() + word.slice(1)).join('');
const hasOpId = operation.hasOperationId();
const operationId = hasOpId ? operation.operationId() : convertChannelToFilename(operation.id()); // convertChannelToFilename is used when operationId is not available
return operationId;
}

// Extracts and returns topic from a list of operations.
Expand Down
3,906 changes: 2,138 additions & 1,768 deletions package-lock.json

Large diffs are not rendered by default.

22 changes: 20 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,38 @@
},
"devDependencies": {
"@asyncapi/generator": "^2.0.3",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"babel-jest": "^29.7.0",
"jest": "^27.5.1"
},
"jest": {
"moduleNameMapper": {
"^nimma/legacy$": "<rootDir>/node_modules/nimma/dist/legacy/cjs/index.js",
"^nimma/(.*)": "<rootDir>/node_modules/nimma/dist/cjs/$1"
},
"transform": {
"^.+\\.jsx?$": "babel-jest"
}
},
"babel": {
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{
"runtime": "automatic"
}
]
]
},
"scripts": {
"test:snapshot": "jest --modulePathIgnorePatterns='./template'",
"test:snapshot": "jest --modulePathIgnorePatterns='./template' --testPathIgnorePatterns=unit -u",
"test:unit": "jest --modulePathIgnorePatterns='./template' --testPathIgnorePatterns=integration -u",
"test:clean": "npx rimraf@5.0.0 test/project/client.py test/project/requirements.txt test/IntegrationSnaps",
"test:generate": "npx -p @asyncapi/cli@1.6.3 asyncapi generate fromTemplate test/fixtures/asyncapi.yml ./ --output test/project --force-write --param server=dev",
"test:start": "python test/project/test.py",
"test:example": "npm run test:clean && npm run test:generate && npm run test:start",
"test": "npm run test:snapshot -- -u"
"test": "npm run test:snapshot && npm run test:unit"
}
}
117 changes: 117 additions & 0 deletions test/unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
getServiceClientName,
getServiceClientDescription,
getClientClassName,
getFunctionName,
getFunctionDetails,
getSendOperations,
getReceiveOperations
} from '../components/helpers/utils';

describe('Service Client Helpers', () => {
const mockAsyncAPI = {
info: jest.fn(() => ({
title: jest.fn(() => 'TestService'),
description: jest.fn(() => 'Test Service Description'),
hasDescription: jest.fn(() => true)
})),
};

const mockAsyncAPIWithoutDesc = {
info: jest.fn(() => ({
title: jest.fn(() => 'TestService'),
description: jest.fn(() => undefined),
hasDescription: jest.fn(() => false)
})),
};

const mockOperations = [
{
hasOperationId: jest.fn(() => true),
operationId: jest.fn(() => 'sendTestOperation'),
id: jest.fn(() => 'sendTestOperation'),
channels: jest.fn(() => [{ address: jest.fn(() => 'test/topic') }]),
summary: jest.fn(() => 'Test Operation Summary'),
isSend: jest.fn(() => true),
isReceive: jest.fn(() => false),
},
{
hasOperationId: jest.fn(() => true),
operationId: jest.fn(() => 'receiveTestOperation'),
id: jest.fn(() => 'receiveTestOperation'),
channels: jest.fn(() => [{ address: jest.fn(() => 'test/topic') }]),
summary: jest.fn(() => 'Test Operation Summary'),
isSend: jest.fn(() => false),
isReceive: jest.fn(() => true),
}
];

beforeEach(() => {
jest.clearAllMocks();
});

describe('getServiceClientName', () => {
it('should return the correct service client name', () => {
const result = getServiceClientName(mockAsyncAPI);
expect(result).toEqual('TestService Client');
});
});

describe('getServiceClientDescription', () => {
it('should return the correct service client description', () => {
const result = getServiceClientDescription(mockAsyncAPI);
expect(result).toEqual('Test Service Description');
});

it('should return empty string if no description provided', () => {
const result = getServiceClientDescription(mockAsyncAPIWithoutDesc);
expect(result).toEqual('');
});
});

describe('getClientClassName', () => {
it('should return the correct client class name', () => {
const result = getClientClassName(mockAsyncAPI);
expect(result).toEqual('TestServiceClient');
});
});

describe('getFunctionName', () => {
it('should return the correct function name for a given operation', () => {
const result = getFunctionName(mockOperations[0]);
expect(result).toEqual('sendTestOperation');
});

it('should return the correct function name using convertChannelToFilename if operationId is not available', () => {
mockOperations[0].hasOperationId.mockReturnValue(false);
const result = getFunctionName(mockOperations[0]);
expect(result).toEqual('sendTestOperation');
});
});

describe('getFunctionDetails', () => {
it('should return the correct function details for operations', () => {
const result = getFunctionDetails(mockOperations);
expect(result).toEqual([
{ functionName: 'sendTestOperation', topic: 'test/topic', summary: 'Test Operation Summary' },
{ functionName: 'receiveTestOperation', topic: 'test/topic', summary: 'Test Operation Summary' }
]);
});
});

describe('getSendOperations', () => {
it('should return only send operations', () => {
const result = getSendOperations(mockOperations);
expect(result).toHaveLength(1);
expect(result[0].operationId()).toEqual('sendTestOperation');
});
});

describe('getReceiveOperations', () => {
it('should return only receive operations', () => {
const result = getReceiveOperations(mockOperations);
expect(result).toHaveLength(1);
expect(result[0].operationId()).toEqual('receiveTestOperation');
});
});
});