Skip to content

Commit

Permalink
Merge branch 'master' into buildPostTest
Browse files Browse the repository at this point in the history
  • Loading branch information
vishvamsinh28 authored Dec 8, 2024
2 parents e0ac1e1 + 9976010 commit dc7e215
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 55 deletions.
113 changes: 58 additions & 55 deletions scripts/tools/tools-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ addFormats(ajv, ["uri"])
const validate = ajv.compile(schema)
const { convertToJson } = require('../utils');


// Config options set for the Fuse object
const options = {
includeScore: true,
Expand All @@ -25,8 +24,8 @@ const fuse = new Fuse(categoryList, options)
// isAsyncAPIrepo boolean variable to define whether the tool repository is under
// AsyncAPI organization or not, to create a JSON tool object as required in the frontend
// side to show ToolCard.
const createToolObject = async (toolFile, repositoryUrl='', repoDescription='', isAsyncAPIrepo='') => {
let resultantObject = {
const createToolObject = async (toolFile, repositoryUrl = '', repoDescription = '', isAsyncAPIrepo = '') => {
const resultantObject = {
title: toolFile.title,
description: toolFile?.description ? toolFile.description : repoDescription,
links: {
Expand All @@ -47,67 +46,71 @@ const createToolObject = async (toolFile, repositoryUrl='', repoDescription='',
// and creating a JSON tool object in which all the tools are listed in defined
// categories order, which is then updated in `automated-tools.json` file.
async function convertTools(data) {
let finalToolsObject = {};
const dataArray = data.items;

// initialising finalToolsObject with all categories inside it with proper elements in each category
for (var index in categoryList) {
finalToolsObject[categoryList[index].name] = {
description: categoryList[index].description,
toolsList: []
};
}
try {
let finalToolsObject = {};
const dataArray = data.items;

for (let tool of dataArray) {
try {
if (tool.name.startsWith('.asyncapi-tool')) {
// extracting the reference id of the repository which will be used to extract the path of the .asyncapi-tool file in the Tools repository
// ex: for a url = "https://api.github.com/repositories/351453552/contents/.asyncapi-tool?ref=61855e7365a881e98c2fe667a658a0005753d873"
// the text (id) present after '=' gives us a reference id for the repo
let reference_id = tool.url.split("=")[1];
let download_url = `https://raw.githubusercontent.com/${tool.repository.full_name}/${reference_id}/${tool.path}`;
// initialising finalToolsObject with all categories inside it with proper elements in each category
finalToolsObject = Object.fromEntries(
categoryList.map((category) => [
category.name,
{
description: category.description,
toolsList: []
}
])
);

const { data: toolFileContent } = await axios.get(download_url);
await Promise.all(dataArray.map(async (tool) => {
try {
if (tool.name.startsWith('.asyncapi-tool')) {
const referenceId = tool.url.split('=')[1];
const downloadUrl = `https://raw.githubusercontent.com/${tool.repository.full_name}/${referenceId}/${tool.path}`;

//some stuff can be YAML
const jsonToolFileContent = await convertToJson(toolFileContent)
const { data: toolFileContent } = await axios.get(downloadUrl);

//validating against JSON Schema for tools file
const isValid = await validate(jsonToolFileContent)
//some stuff can be YAML
const jsonToolFileContent = await convertToJson(toolFileContent)

if (isValid) {
let repositoryUrl = tool.repository.html_url;
let repoDescription = tool.repository.description;
let isAsyncAPIrepo = tool.repository.owner.login === "asyncapi";
let toolObject = await createToolObject(jsonToolFileContent, repositoryUrl, repoDescription, isAsyncAPIrepo);
//validating against JSON Schema for tools file
const isValid = await validate(jsonToolFileContent)

// Tool Object is appended to each category array according to Fuse search for categories inside Tool Object
jsonToolFileContent.filters.categories.forEach(async (category) => {
const categorySearch = await fuse.search(category);
if (isValid) {
const repositoryUrl = tool.repository.html_url;
const repoDescription = tool.repository.description;
const isAsyncAPIrepo = tool.repository.owner.login === 'asyncapi';
const toolObject = await createToolObject(
jsonToolFileContent,
repositoryUrl,
repoDescription,
isAsyncAPIrepo
);

if (categorySearch.length) {
let searchedCategoryName = categorySearch[0].item.name
if (!finalToolsObject[searchedCategoryName].toolsList.find((element => element === toolObject)))
finalToolsObject[searchedCategoryName].toolsList.push(toolObject);
} else {
// if Tool object has a category, not defined in our categorylist, then this provides a `other` category to the tool.
if (!finalToolsObject['Others'].toolsList.find((element => element === toolObject)))
finalToolsObject['Others'].toolsList.push(toolObject);
}
});
} else {
console.error('Script is not failing, it is just dropping errors for further investigation');
console.error('Invalid .asyncapi-tool file.');
console.error(`Located in: ${tool.html_url}`);
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2));
// Tool Object is appended to each category array according to Fuse search for categories inside Tool Object
await Promise.all(jsonToolFileContent.filters.categories.map(async (category) => {
const categorySearch = await fuse.search(category);
const targetCategory = categorySearch.length ? categorySearch[0].item.name : 'Others';
const { toolsList } = finalToolsObject[targetCategory];
if (!toolsList.includes(toolObject)) {
toolsList.push(toolObject);
}
}));
} else {
console.error('Script is not failing, it is just dropping errors for further investigation');
console.error('Invalid .asyncapi-tool file.');
console.error(`Located in: ${tool.html_url}`);
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2));
}
}
} catch (err) {
console.error(err)
throw err;
}
} catch (err) {
console.error(err)
throw err;
}
}))
return finalToolsObject;
} catch (err) {
throw new Error(`Error processing tool: ${err.message}`)
}
return finalToolsObject;
}

module.exports = {convertTools, createToolObject}
module.exports = { convertTools, createToolObject }
79 changes: 79 additions & 0 deletions tests/helper/toolsObjectData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const createToolRepositoryData = ({
name = '.asyncapi-tool',
refId = '61855e7365a881e98c2fe667a658a0005753d873',
owner = 'asyncapi',
repoName = 'example-repo',
description = 'Example repository',
path = '.asyncapi-tool'
} = {}) => ({
name,
url: `https://api.github.com/repositories/351453552/contents/${path}?ref=${refId}`,
repository: {
full_name: `${owner}/${repoName}`,
html_url: `https://github.com/${owner}/${repoName}`,
description,
owner: { login: owner }
},
path
});

const createToolFileContent = ({
title = 'Example Tool',
description = 'This is an example tool.',
repoUrl = null,
categories = ['Category1'],
hasCommercial = false,
additionalLinks = {},
additionalFilters = {}
} = {}) => ({
title,
description,
links: {
repoUrl: repoUrl || `https://github.com/asyncapi/${encodeURIComponent(title.toLowerCase().replace(/\s+/g, '-'))}`,
...additionalLinks
},
filters: { categories, hasCommercial, ...additionalFilters }
});

const createExpectedToolObject = ({
title = 'Example Tool',
description = 'This is an example tool.',
repoUrl = null,
categories = ['Category1'],
hasCommercial = false,
isAsyncAPIOwner = true,
additionalLinks = {},
additionalFilters = {}
} = {}) =>
createToolFileContent({
title,
description,
repoUrl,
categories,
hasCommercial,
additionalLinks,
additionalFilters: { isAsyncAPIOwner, ...additionalFilters }
});

const createMockData = (tools = []) => ({
items: tools.map((tool) =>
typeof tool === 'string'
? createToolRepositoryData({ name: `.asyncapi-tool-${tool}`, repoName: tool })
: createToolRepositoryData(tool)
)
});

const createMalformedYAML = ({
title = 'Malformed Tool',
description = 'This tool has malformed YAML.',
repoUrl = 'https://github.com/asyncapi/malformed-repo' } = {}) => `
title: ${title}
description: ${description}
links:
repoUrl: ${repoUrl}
filters:
categories:
- Category1
`;

module.exports = { createToolFileContent, createExpectedToolObject, createMockData, createMalformedYAML };
161 changes: 161 additions & 0 deletions tests/tools/tools-object.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
const axios = require('axios');
const { convertTools, createToolObject } = require('../../scripts/tools/tools-object');
const {
createToolFileContent,
createExpectedToolObject,
createMockData,
createMalformedYAML
} = require('../helper/toolsObjectData');

jest.mock('axios');
jest.mock('../../scripts/tools/categorylist', () => ({
categoryList: [
{ name: 'Category1', tag: 'Category1', description: 'Description for Category1' },
{ name: 'Others', tag: 'Others', description: 'Other tools category' },
]
}));

describe('Tools Object', () => {
beforeEach(() => {
axios.get.mockClear();
console.error = jest.fn();
});

const mockToolData = (toolContent, toolNames = ['valid-tool']) => {
const mockData = createMockData(toolNames.map((name) => ({ name: `.asyncapi-tool-${name}`, repoName: name })));
axios.get.mockResolvedValue({ data: toolContent });
return mockData;
};

it('should create a tool object with provided parameters', async () => {
const toolFile = createToolFileContent({
title: 'Test Tool',
description: 'Test Description',
hasCommercial: true,
additionalLinks: { docsUrl: 'https://docs.example.com' }
});

const expected = createExpectedToolObject({
title: 'Test Tool',
description: 'Test Description',
hasCommercial: true,
additionalLinks: { docsUrl: 'https://docs.example.com' }
});

const result = await createToolObject(
toolFile,
expected.links.repoUrl,
'Repository Description',
true
);

expect(result).toEqual(expected);
});

it('should convert tools data correctly', async () => {
const toolContent = createToolFileContent({ title: 'Valid Tool', categories: ['Category1'] });
const mockData = mockToolData(toolContent);

const result = await convertTools(mockData);

expect(result.Category1.toolsList).toHaveLength(1);
expect(result.Category1.toolsList[0].title).toBe('Valid Tool');
});

it('should assign tool to Others category if no matching category is found', async () => {
const toolContent = createToolFileContent({ title: 'Unknown Category Tool', categories: ['UnknownCategory'] });
const mockData = mockToolData(toolContent);

const result = await convertTools(mockData);

expect(result.Others.toolsList).toHaveLength(1);
expect(result.Others.toolsList[0].title).toBe('Unknown Category Tool');
});

it('should log errors for invalid .asyncapi-tool file', async () => {
const invalidContent = createToolFileContent({
title: 'Invalid Tool',
additionalFilters: { invalidField: true }
});
const mockData = mockToolData(invalidContent);

await convertTools(mockData);

expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Script is not failing'));
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Invalid .asyncapi-tool file'));
});

it('should add duplicate tool objects to the same category', async () => {
const toolContent = createToolFileContent({
title: 'Duplicate Tool',
categories: ['Category1']
});

const mockData = createMockData([
{ name: '.asyncapi-tool-dup1', repoName: 'dup1' },
{ name: '.asyncapi-tool-dup2', repoName: 'dup2' }
]);

axios.get.mockResolvedValue({ data: toolContent });

const result = await convertTools(mockData);

expect(result.Category1.toolsList).toHaveLength(2);
expect(result.Category1.toolsList[0].title).toBe('Duplicate Tool');
expect(result.Category1.toolsList[1].title).toBe('Duplicate Tool');
});

it('should add tool to Others category only once', async () => {
const toolContent = createToolFileContent({
title: 'Duplicate Tool in Others',
categories: ['UnknownCategory']
});

const mockData = mockToolData(toolContent);

const result = await convertTools(mockData);

expect(result.Others.toolsList).toHaveLength(1);
expect(result.Others.toolsList[0].title).toBe('Duplicate Tool in Others');
});

it('should throw an error if axios.get fails', async () => {
const mockData = createMockData([{
name: '.asyncapi-tool-error',
repoName: 'error-tool'
}]);

axios.get.mockRejectedValue(new Error('Network Error'));

await expect(convertTools(mockData)).rejects.toThrow('Network Error');
});

it('should handle malformed JSON in tool file', async () => {
const malformedContent = createMalformedYAML();
await expect(convertTools(malformedContent)).rejects.toThrow();
});

it('should use repository description when tool description is missing', async () => {
const toolFile = createToolFileContent({
title: 'No Description Tool',
description: '',
});

const repositoryDescription = 'Fallback Repository Description';
const mockData = createMockData([{
name: '.asyncapi-tool-no-description',
repoName: 'no-description',
description: repositoryDescription
}]);

axios.get.mockResolvedValue({ data: toolFile });

const result = await convertTools(mockData);

const toolObject = result.Category1.toolsList[0];

expect(toolObject.description).toBe(repositoryDescription);
expect(toolObject.title).toBe('No Description Tool');
});

});

0 comments on commit dc7e215

Please sign in to comment.