-
-
Notifications
You must be signed in to change notification settings - Fork 663
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
feat: add test for combine tools script #3136
base: master
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for asyncapi-website ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify site configuration. |
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-3136--asyncapi-website.netlify.app/ |
@@ -106,7 +105,7 @@ const getFinalTool = async (toolObject) => { | |||
|
|||
// Combine the automated tools and manual tools list into single JSON object file, and | |||
// lists down all the language and technology tags in one JSON file. | |||
const combineTools = async (automatedTools, manualTools) => { | |||
const combineTools = async (automatedTools, manualTools, toolsPath, tagsPath) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does tagspath
signify??
scripts/tools/combine-tools.js
Outdated
JSON.stringify({ languages: languageList, technologies: technologyList }), | ||
) | ||
fs.writeFileSync(toolsPath,JSON.stringify(finalTools)); | ||
fs.writeFileSync(tagsPath,JSON.stringify({ languages: languageList, technologies: technologyList }),) | ||
} | ||
|
||
module.exports = { combineTools } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix this linter error
tests/tools/combine-tools.test.js
Outdated
const tagsData = readJSON(tagsPath); | ||
expect(tagsData.languages).toContainEqual(expect.objectContaining({ name: 'NewLanguage' })); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fix this one also
scripts/build-tools.js
Outdated
@@ -14,7 +14,7 @@ const buildTools = async () => { | |||
resolve(__dirname, '../config', 'tools-automated.json'), | |||
JSON.stringify(automatedTools, null, ' ') | |||
); | |||
await combineTools(automatedTools, manualTools); | |||
await combineTools(automatedTools, manualTools, resolve(__dirname, '../../config', 'tools.json'), resolve(__dirname, '../../config', 'all-tags.json')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we do it in a more better way, instead of passing filePath here? Try to refactor tools scripts instead of manually passing filePaths for tags file.
}); | ||
|
||
it('should combine tools and create correct JSON files', async () => { | ||
await combineTools(automatedTools, manualTools, toolsPath, tagsPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try to add this function call in a try/catch
block here to monitor if we are receiving any error or not.
}); | ||
|
||
|
||
it('should log validation errors to console.error', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors shouldn't be validated via console messages. You should formally return a error using Promise.reject
.
scripts/build-tools.js
Outdated
@@ -6,15 +6,19 @@ const manualTools = require('../config/tools-manual.json') | |||
const fs = require('fs'); | |||
const { resolve } = require('path'); | |||
|
|||
let toolsPath = resolve(__dirname, '../../config', 'tools.json') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is using let
a good option here?
…ebsite into combineToolsTest
languages: [ | ||
{ | ||
name: 'JavaScript', | ||
color: 'bg-[#57f281]', | ||
borderColor: 'border-[#37f069]' | ||
}, | ||
{ | ||
name: 'Python', | ||
color: 'bg-[#3572A5]', | ||
borderColor: 'border-[#3572A5]' | ||
} | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we directly use tagsData instead of creating this duplicate data?
|
||
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); | ||
}); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't we using resolveAllMocks()
function here in beforeEach function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tests are failing because of it so i have used clearAllMocks
@coderabbitai resolve |
✅ Actions performedComments resolved. |
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Outside diff range and nitpick comments (3)
tests/fixtures/tools/automated-tools.json (1)
1-17
: LGTM! Consider enhancing test data coverage.The JSON structure is well-formed and provides a good basic test case. However, since this is test data, consider adding more diverse scenarios to improve test coverage:
- Multiple tools in the category
- Tools with multiple technologies
- Tools with missing optional fields
Example addition:
{ "category1": { "description": "Sample Category", "toolsList": [ { "title": "Tool B", "filters": { "language": "Python", "technology": ["Flask"] }, "links": { "repoUrl": "https://github.com/asyncapi/tool-b" } } + ,{ + "title": "Tool C", + "filters": { + "language": "JavaScript", + "technology": ["Node.js", "Express"] + }, + "links": { + "repoUrl": "https://github.com/asyncapi/tool-c", + "docsUrl": "https://docs.example.com" + } + } ] } }tests/fixtures/combineToolsData.js (2)
84-114
: Consider adding validation for new language/technology format.While the test cases cover adding new languages and technologies, consider adding validation to ensure they follow the expected format (e.g., proper casing, no special characters).
138-177
: Consider adding more URL validation test cases.While the current test cases cover missing and invalid URLs, consider adding tests for:
- URLs with special characters
- URLs with different protocols (e.g., http://, git://)
- URLs with different GitHub formats (e.g., [email protected])
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (5)
scripts/tools/combine-tools.js
(1 hunks)tests/fixtures/combineToolsData.js
(1 hunks)tests/fixtures/tools/automated-tools.json
(1 hunks)tests/fixtures/tools/manual-tools.json
(1 hunks)tests/tools/combine-tools.test.js
(1 hunks)
🧰 Additional context used
🪛 Biome
scripts/tools/combine-tools.js
[error] 117-117: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (8)
tests/fixtures/tools/manual-tools.json (2)
1-12
: LGTM! Valid test fixture structure.
The JSON structure follows the expected schema with all required fields (title, filters, links) properly formatted.
9-9
: Verify if mock repository URL is intentional.
The repository URL "https://github.com/asyncapi/tool-a" appears to be a non-existent repository. If this is test data, this might be intentional. However, if this is meant to reference a real tool, please update it to the correct repository URL.
Let's verify if this repository exists:
✅ Verification successful
Let me check if this is a test fixture by examining the context around this file.
Let me check the content of the test file to confirm if this is indeed test data.
Mock repository URL is intentional - this is test data
The URL "https://github.com/asyncapi/tool-a" is used in test fixtures and is referenced in tests/fixtures/combineToolsData.js
as test data. The non-existence of this repository is intentional as it's used for testing purposes along with other mock repository URLs like "tool-c", "tool-z", and others in the test suite.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if the repository exists in the AsyncAPI organization
gh repo view asyncapi/tool-a 2>/dev/null
if [ $? -eq 0 ]; then
echo "Repository exists"
else
echo "Repository does not exist"
fi
Length of output: 109
Script:
#!/bin/bash
# Check the content of the file and its parent directory
echo "=== Directory structure ==="
ls -la tests/fixtures/tools/
echo -e "\n=== Content of manual-tools.json ==="
cat tests/fixtures/tools/manual-tools.json
echo -e "\n=== Looking for test-related files ==="
rg -l "tool-a" tests/
Length of output: 860
Script:
#!/bin/bash
# Check the content of the test file
echo "=== Content of combineToolsData.js ==="
cat tests/fixtures/combineToolsData.js
Length of output: 4705
tests/fixtures/combineToolsData.js (5)
1-26
: Consider reusing existing tagsData
instead of duplicating color definitions.
The color and border color values for languages and technologies appear to be duplicated from the main application's tagsData
. To maintain consistency and reduce duplication, consider importing and reusing the existing data.
28-82
: LGTM! Well-structured test cases for validation.
The test data effectively covers edge cases with missing and invalid data, which is crucial for testing error handling in the combine tools function.
116-136
: LGTM! Comprehensive test cases for valid tools.
The test data provides good coverage for properly formatted tools, testing both automated and manual tool scenarios.
179-193
: LGTM! Good coverage for circular reference handling.
The test case effectively validates the handling of potential circular dependencies in the tools data.
195-212
: LGTM! Well-organized exports with clear naming.
The module exports are comprehensive and follow a consistent naming convention, making it easy to access different test scenarios.
tests/tools/combine-tools.test.js (1)
61-62
: Verify that shared test data is not mutated across tests
manualTools
and automatedTools
are initialized once in beforeAll
and shared across tests. If any test mutates these objects, it could inadvertently affect other tests. To ensure test isolation, verify that these objects are not mutated, or consider cloning them before each test.
Run the following script to check for mutations of manualTools
and automatedTools
:
If mutations are found, consider cloning the objects in a beforeEach
block:
beforeEach(() => {
manualTools = JSON.parse(JSON.stringify(originalManualTools));
automatedTools = JSON.parse(JSON.stringify(originalAutomatedTools));
});
"language": "Python", | ||
"technology": ["Flask"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using consistent array format for both language and technology.
For better consistency and future extensibility, consider making the language
field an array like technology
. This would allow tools to support multiple programming languages.
"filters": {
- "language": "Python",
+ "language": ["Python"],
"technology": ["Flask"]
},
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
language can take values in terms of both string and array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshatnema, understood. Thank you for the clarification.
✏️ Learnings added
Learnt from: akshatnema
PR: asyncapi/website#3136
File: tests/fixtures/tools/automated-tools.json:8-9
Timestamp: 2024-11-01T12:48:21.851Z
Learning: In the `tests/fixtures/tools/automated-tools.json` file, the `language` field within the `filters` object can be either a string or an array. Both formats are acceptable and supported in the codebase.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
console.error('Script is not failing, it is just dropping errors for further investigation'); | ||
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | ||
console.error(`Located in manual-tools.json file`); | ||
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve validation error logging structure
The current error logging could be more structured and consistent.
-console.error('Script is not failing, it is just dropping errors for further investigation');
-console.error(`Invalid ${tool.title} .asyncapi-tool file.`);
-console.error(`Located in manual-tools.json file`);
-console.error('Validation errors:', JSON.stringify(validate.errors, null, 2));
+console.error({
+ message: 'Tool validation failed',
+ tool: tool.title,
+ source: 'manual-tools.json',
+ errors: validate.errors,
+ note: 'Script continues execution, error logged for investigation'
+});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
console.error('Script is not failing, it is just dropping errors for further investigation'); | |
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | |
console.error(`Located in manual-tools.json file`); | |
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | |
} | |
console.error({ | |
message: 'Tool validation failed', | |
tool: tool.title, | |
source: 'manual-tools.json', | |
errors: validate.errors, | |
note: 'Script continues execution, error logged for investigation' | |
}); |
scripts/tools/combine-tools.js
Outdated
if (manualTools[key] && manualTools[key].toolsList.length) { | ||
for (const tool of manualTools[key].toolsList) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use optional chaining for better null safety
The static analysis tool correctly suggests using optional chaining here.
-if (manualTools[key] && manualTools[key].toolsList.length) {
+if (manualTools[key]?.toolsList?.length) {
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (manualTools[key] && manualTools[key].toolsList.length) { | |
for (const tool of manualTools[key].toolsList) { | |
if (manualTools[key]?.toolsList?.length) { | |
for (const tool of manualTools[key].toolsList) { |
🧰 Tools
🪛 Biome
[error] 117-117: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vishvamsinh28 Kindly apply this suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
if (tool?.links?.repoUrl) { | ||
const url = new URL(tool.links.repoUrl) | ||
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | ||
} else isAsyncAPIrepo = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance URL validation robustness
The URL validation could be more robust by handling potential URL parsing errors.
if (tool?.links?.repoUrl) {
- const url = new URL(tool.links.repoUrl)
- isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/")
+ try {
+ const url = new URL(tool.links.repoUrl)
+ isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/")
+ } catch (urlError) {
+ console.error(`Invalid repository URL for tool ${tool.title}: ${tool.links.repoUrl}`);
+ isAsyncAPIrepo = false;
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (tool?.links?.repoUrl) { | |
const url = new URL(tool.links.repoUrl) | |
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | |
} else isAsyncAPIrepo = false | |
if (tool?.links?.repoUrl) { | |
try { | |
const url = new URL(tool.links.repoUrl) | |
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | |
} catch (urlError) { | |
console.error(`Invalid repository URL for tool ${tool.title}: ${tool.links.repoUrl}`); | |
isAsyncAPIrepo = false; | |
} | |
} else isAsyncAPIrepo = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't suggest this. It is not an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshatnema Understood, thank you for the clarification.
✏️ Learnings added
Learnt from: akshatnema
PR: asyncapi/website#3136
File: scripts/tools/combine-tools.js:122-125
Timestamp: 2024-11-01T12:49:32.625Z
Learning: In `scripts/tools/combine-tools.js`, the existing URL parsing logic for `repoUrl` without additional error handling is acceptable and does not require changes.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); | ||
fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement safer file operations
The file writing operations should be more robust with atomic writes to prevent partial updates.
-fs.writeFileSync(toolsPath, JSON.stringify(finalTools));
-fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),);
+const writeFileSafely = (path, content) => {
+ const tempPath = `${path}.tmp`;
+ fs.writeFileSync(tempPath, JSON.stringify(content, null, 2));
+ fs.renameSync(tempPath, path);
+};
+
+writeFileSafely(toolsPath, finalTools);
+writeFileSafely(tagsPath, {
+ languages: languageList,
+ technologies: technologyList
+});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); | |
fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) | |
const writeFileSafely = (path, content) => { | |
const tempPath = `${path}.tmp`; | |
fs.writeFileSync(tempPath, JSON.stringify(content, null, 2)); | |
fs.renameSync(tempPath, path); | |
}; | |
writeFileSafely(toolsPath, finalTools); | |
writeFileSafely(tagsPath, { | |
languages: languageList, | |
technologies: technologyList | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does it preventing partial updates?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshatnema, by writing the data to a temporary file first and then renaming it to the desired file, we prevent partial updates. If an error occurs or the process is interrupted during the write operation, the original file remains unchanged because the temporary file hasn't replaced it yet. The fs.renameSync
operation is atomic on most file systems, ensuring that the target file is only updated if the write to the temporary file completes successfully. This approach helps maintain file integrity by avoiding situations where a file could be left incomplete or corrupted due to a failed write operation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the target file already exists, can there be possibility that a duplicate file is created with another name and the previous one is not removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshatnema, you make a good point. If the process is interrupted after writing the temporary file but before the fs.renameSync
operation, there's a possibility that the temporary file (${path}.tmp
) remains, and the original file isn't updated. This could lead to leftover temporary files in the directory.
Additionally, if multiple instances of the script run simultaneously, they might use the same temporary file name, causing conflicts. To mitigate these issues, we can:
-
Use unique temporary file names: Incorporate a unique identifier in the temp file name, such as a timestamp or a random string.
const writeFileSafely = (path, content) => { const tempPath = `${path}.${Date.now()}.tmp`; fs.writeFileSync(tempPath, JSON.stringify(content, null, 2)); fs.renameSync(tempPath, path); };
-
Handle cleanup of temporary files: Implement a mechanism to clean up any leftover temp files on startup or before writing new files.
-
Use atomic write libraries: Consider using a library like
write-file-atomic
which handles these edge cases and ensures safer writes.const writeFileAtomic = require('write-file-atomic'); const writeFileSafely = (path, content) => { writeFileAtomic.sync(path, JSON.stringify(content, null, 2)); };
These approaches help prevent the possibility of duplicate files and ensure that the original file is safely updated. Let me know your thoughts on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can there be a possibility that even scripts gets executed successfully but the temporary file is not able to replace the target file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@akshatnema, yes, there is a possibility that even if the script executes successfully, the temporary file may not be able to replace the target file. This could happen due to various reasons such as file system permissions, file locks, insufficient disk space, or other OS-level issues that prevent the fs.renameSync
operation from completing successfully.
To mitigate this, we can:
-
Add Error Handling: Wrap the
fs.renameSync
operation in a try-catch block to handle any exceptions that might occur during the rename process.const writeFileSafely = (path, content) => { const tempPath = `${path}.tmp`; fs.writeFileSync(tempPath, JSON.stringify(content, null, 2)); try { fs.renameSync(tempPath, path); } catch (error) { console.error(`Failed to rename temporary file: ${error.message}`); // Handle the error accordingly, possibly retry or abort } };
-
Check File System Constraints: Ensure that the script has the necessary permissions and that there are no locks on the target file.
-
Evaluate Necessity: If the risk of partial writes or corrupted files is minimal in our context, and the current implementation hasn't caused issues, we might opt to keep the existing code for simplicity.
Let me know your thoughts on whether you'd like to proceed with additional error handling or prefer to retain the current implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
scripts/tools/combine-tools.js
Outdated
try { | ||
for (const key in automatedTools) { | ||
let finalToolsList = []; | ||
if (automatedTools[key].toolsList.length) { | ||
for (const tool of automatedTools[key].toolsList) { | ||
finalToolsList.push(await getFinalTool(tool)) | ||
} | ||
} | ||
} | ||
if (manualTools[key] && manualTools[key].toolsList.length) { | ||
for (const tool of manualTools[key].toolsList) { | ||
let isAsyncAPIrepo; | ||
const isValid = await validate(tool) | ||
if (isValid) { | ||
if (tool?.links?.repoUrl) { | ||
const url = new URL(tool.links.repoUrl) | ||
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | ||
} else isAsyncAPIrepo = false | ||
let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) | ||
finalToolsList.push(await getFinalTool(toolObject)) | ||
} else { | ||
console.error('Script is not failing, it is just dropping errors for further investigation'); | ||
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | ||
console.error(`Located in manual-tools.json file`); | ||
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | ||
if (manualTools[key] && manualTools[key].toolsList.length) { | ||
for (const tool of manualTools[key].toolsList) { | ||
let isAsyncAPIrepo; | ||
const isValid = await validate(tool) | ||
if (isValid) { | ||
if (tool?.links?.repoUrl) { | ||
const url = new URL(tool.links.repoUrl) | ||
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | ||
} else isAsyncAPIrepo = false | ||
let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) | ||
finalToolsList.push(await getFinalTool(toolObject)) | ||
} else { | ||
console.error('Script is not failing, it is just dropping errors for further investigation'); | ||
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | ||
console.error(`Located in manual-tools.json file`); | ||
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | ||
} | ||
} | ||
} | ||
finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); | ||
finalTools[key].toolsList = finalToolsList | ||
} | ||
finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); | ||
finalTools[key].toolsList = finalToolsList | ||
fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); | ||
fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) | ||
} catch (err) { | ||
throw new Error(`Error combining tools: ${err}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider enhancing error handling with structured errors
While the try-catch block is a good addition, consider creating specific error types for different failure scenarios (e.g., validation errors, file system errors) to help with error tracking and debugging.
+class ToolsCombineError extends Error {
+ constructor(operation, details, cause) {
+ super(`Failed to ${operation}: ${details}`);
+ this.name = 'ToolsCombineError';
+ this.cause = cause;
+ }
+}
try {
// ... existing code ...
} catch (err) {
- throw new Error(`Error combining tools: ${err}`);
+ throw new ToolsCombineError('combine tools',
+ err.message,
+ err);
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
try { | |
for (const key in automatedTools) { | |
let finalToolsList = []; | |
if (automatedTools[key].toolsList.length) { | |
for (const tool of automatedTools[key].toolsList) { | |
finalToolsList.push(await getFinalTool(tool)) | |
} | |
} | |
} | |
if (manualTools[key] && manualTools[key].toolsList.length) { | |
for (const tool of manualTools[key].toolsList) { | |
let isAsyncAPIrepo; | |
const isValid = await validate(tool) | |
if (isValid) { | |
if (tool?.links?.repoUrl) { | |
const url = new URL(tool.links.repoUrl) | |
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | |
} else isAsyncAPIrepo = false | |
let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) | |
finalToolsList.push(await getFinalTool(toolObject)) | |
} else { | |
console.error('Script is not failing, it is just dropping errors for further investigation'); | |
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | |
console.error(`Located in manual-tools.json file`); | |
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | |
if (manualTools[key] && manualTools[key].toolsList.length) { | |
for (const tool of manualTools[key].toolsList) { | |
let isAsyncAPIrepo; | |
const isValid = await validate(tool) | |
if (isValid) { | |
if (tool?.links?.repoUrl) { | |
const url = new URL(tool.links.repoUrl) | |
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | |
} else isAsyncAPIrepo = false | |
let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) | |
finalToolsList.push(await getFinalTool(toolObject)) | |
} else { | |
console.error('Script is not failing, it is just dropping errors for further investigation'); | |
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | |
console.error(`Located in manual-tools.json file`); | |
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | |
} | |
} | |
} | |
finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); | |
finalTools[key].toolsList = finalToolsList | |
} | |
finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); | |
finalTools[key].toolsList = finalToolsList | |
fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); | |
fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) | |
} catch (err) { | |
throw new Error(`Error combining tools: ${err}`); | |
class ToolsCombineError extends Error { | |
constructor(operation, details, cause) { | |
super(`Failed to ${operation}: ${details}`); | |
this.name = 'ToolsCombineError'; | |
this.cause = cause; | |
} | |
} | |
try { | |
for (const key in automatedTools) { | |
let finalToolsList = []; | |
if (automatedTools[key].toolsList.length) { | |
for (const tool of automatedTools[key].toolsList) { | |
finalToolsList.push(await getFinalTool(tool)) | |
} | |
} | |
if (manualTools[key] && manualTools[key].toolsList.length) { | |
for (const tool of manualTools[key].toolsList) { | |
let isAsyncAPIrepo; | |
const isValid = await validate(tool) | |
if (isValid) { | |
if (tool?.links?.repoUrl) { | |
const url = new URL(tool.links.repoUrl) | |
isAsyncAPIrepo = url.href.startsWith("https://github.com/asyncapi/") | |
} else isAsyncAPIrepo = false | |
let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) | |
finalToolsList.push(await getFinalTool(toolObject)) | |
} else { | |
console.error('Script is not failing, it is just dropping errors for further investigation'); | |
console.error(`Invalid ${tool.title} .asyncapi-tool file.`); | |
console.error(`Located in manual-tools.json file`); | |
console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); | |
} | |
} | |
} | |
finalToolsList.sort((tool, anotherTool) => tool.title.localeCompare(anotherTool.title)); | |
finalTools[key].toolsList = finalToolsList | |
} | |
fs.writeFileSync(toolsPath, JSON.stringify(finalTools)); | |
fs.writeFileSync(tagsPath, JSON.stringify({ languages: languageList, technologies: technologyList }),) | |
} catch (err) { | |
throw new ToolsCombineError('combine tools', | |
err.message, | |
err); |
🧰 Tools
🪛 Biome
[error] 117-117: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
tests/tools/combine-tools.test.js
Outdated
let error; | ||
let invalidPath = "this/is/not/valid" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use const
instead of let
for variables that are not reassigned
Variables like invalidPath
are declared using let
but are not reassigned after their initial assignment. For better code clarity and to follow best practices, use const
for variables that remain constant.
Apply the following changes:
- let invalidPath = "this/is/not/valid"
+ const invalidPath = "this/is/not/valid"
This change should be applied in the following tests:
- Lines 211-212: Test for
fs.writeFileSync
failure. - Line 224: Test for invalid category.
- Line 236: Test for URL parsing failure.
- Line 248: Test for circular references.
Also applies to: 224-224, 236-236, 248-248
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vishvamsinh28 Apply this suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
tests/tools/combine-tools.test.js
Outdated
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Move console.error
mock setup to beforeEach
and reset mocks between tests
Currently, the console.error
mock is set up in beforeAll
and not reset between tests. This can lead to cross-test pollution and affect the reliability of your tests. Consider moving the mock setup to a beforeEach
block and resetting it in an afterEach
block to ensure test isolation.
Apply the following changes:
beforeAll(() => {
manualTools = readJSON(manualToolsPath);
automatedTools = readJSON(automatedToolsPath);
- consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});
});
+ beforeEach(() => {
+ consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); | |
}); | |
manualTools = readJSON(manualToolsPath); | |
automatedTools = readJSON(automatedToolsPath); | |
}); | |
beforeEach(() => { | |
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => {}); | |
}); | |
afterEach(() => { | |
jest.clearAllMocks(); | |
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vishvamsinh28 Apply this suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (11)
tests/fixtures/combineToolsData.js (6)
28-34
: Consider adding more error test cases.The current error test cases cover missing data, invalid URLs, and invalid categories. Consider adding these additional scenarios:
- Empty strings for required fields
- Malformed filter objects
- Special characters in URLs
- Cross-site scripting (XSS) attempts in titles
Example additions:
const additionalErrorCases = { emptyFields: { title: '', filters: { language: '' }, links: { repoUrl: '' } }, malformedFilters: { title: 'Malformed Filters', filters: null, links: { repoUrl: 'https://github.com/example/tool' } }, xssAttempt: { title: '<script>alert("xss")</script>', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/example/tool' } } };Also applies to: 138-177
🧰 Tools
🪛 eslint
[error] 29-29: Delete
··
(prettier/prettier)
[error] 30-30: Replace
········
with····
(prettier/prettier)
[error] 31-31: Delete
····
(prettier/prettier)
[error] 32-32: Replace
········
with····
(prettier/prettier)
[error] 33-33: Delete
··
(prettier/prettier)
36-52
: Enhance sorting test cases.The current sorting test case is basic. Consider adding these scenarios:
- Tools with same titles but different languages
- Special characters in titles
- Case sensitivity handling
- Multiple categories with varying tool counts
Example enhancement:
const manualToolsToSort = { category1: { description: 'Sample Category', toolsList: [ { title: 'Tool A', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/asyncapi/tool-a-js' } }, { title: 'Tool A', // Same title, different language filters: { language: 'Python' }, links: { repoUrl: 'https://github.com/asyncapi/tool-a-py' } }, { title: '@Special Tool', // Special character filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/asyncapi/special-tool' } } ] }, category2: { // Additional category description: 'Another Category', toolsList: [/* ... */] } };🧰 Tools
🪛 eslint
[error] 37-37: Delete
··
(prettier/prettier)
[error] 38-38: Replace
········
with····
(prettier/prettier)
[error] 39-39: Delete
····
(prettier/prettier)
[error] 40-40: Replace
············
with······
(prettier/prettier)
[error] 41-41: Replace
················
with········
(prettier/prettier)
[error] 42-42: Delete
········
(prettier/prettier)
[error] 43-43: Replace
················
with········
(prettier/prettier)
[error] 44-44: Delete
······
(prettier/prettier)
[error] 45-45: Replace
············
with······
(prettier/prettier)
[error] 46-46: Replace
················
with········
(prettier/prettier)
[error] 47-47: Delete
········
(prettier/prettier)
[error] 48-48: Replace
················
with········
(prettier/prettier)
[error] 49-49: Delete
······
(prettier/prettier)
[error] 50-50: Delete
····
(prettier/prettier)
[error] 51-51: Delete
··
(prettier/prettier)
54-114
: Add edge cases for language/technology combinations.Consider adding these scenarios:
- Empty arrays for languages/technologies
- Duplicate entries in arrays
- Case sensitivity in language/technology names
- Very long arrays of languages/technologies
Example additions:
const edgeCaseTools = { emptyArrays: { title: 'Empty Arrays Tool', filters: { language: [], technology: [] }, links: { repoUrl: 'https://github.com/example/empty-arrays-tool' } }, duplicateEntries: { title: 'Duplicate Entries Tool', filters: { language: ['JavaScript', 'javascript', 'JAVASCRIPT'], technology: ['Node.js', 'node.js'] }, links: { repoUrl: 'https://github.com/example/duplicate-entries-tool' } } };🧰 Tools
🪛 eslint
[error] 55-55: Delete
··
(prettier/prettier)
[error] 56-56: Delete
··
(prettier/prettier)
[error] 57-57: Replace
········
with····
(prettier/prettier)
[error] 58-58: Delete
····
(prettier/prettier)
[error] 59-59: Delete
··
(prettier/prettier)
[error] 60-60: Delete
··
(prettier/prettier)
[error] 64-64: Replace
··'category1'
withcategory1
(prettier/prettier)
[error] 65-65: Replace
········
with····
(prettier/prettier)
[error] 66-66: Replace
········
with····
(prettier/prettier)
[error] 67-67: Delete
··
(prettier/prettier)
[error] 73-73: Replace
··'category1'
withcategory1
(prettier/prettier)
[error] 74-74: Delete
····
(prettier/prettier)
[error] 75-75: Delete
····
(prettier/prettier)
[error] 76-76: Delete
··
(prettier/prettier)
[error] 79-79: Replace
··'category1'
withcategory1
(prettier/prettier)
[error] 80-80: Replace
········
with····
(prettier/prettier)
[error] 81-81: Delete
··
(prettier/prettier)
[error] 85-85: Replace
····
with··
(prettier/prettier)
[error] 86-86: Delete
··
(prettier/prettier)
[error] 87-87: Replace
········
with····
(prettier/prettier)
[error] 88-88: Replace
········
with····
(prettier/prettier)
[error] 89-89: Delete
··
(prettier/prettier)
[error] 90-90: Delete
··
(prettier/prettier)
[error] 94-94: Replace
····'category1'
with··category1
(prettier/prettier)
[error] 95-95: Delete
····
(prettier/prettier)
[error] 96-96: Replace
········
with····
(prettier/prettier)
[error] 97-97: Delete
··
(prettier/prettier)
[error] 101-101: Delete
··
(prettier/prettier)
[error] 102-102: Delete
··
(prettier/prettier)
[error] 103-103: Delete
····
(prettier/prettier)
[error] 104-104: Delete
····
(prettier/prettier)
[error] 105-105: Delete
··
(prettier/prettier)
[error] 106-106: Delete
··
(prettier/prettier)
[error] 110-110: Replace
··'category1'
withcategory1
(prettier/prettier)
[error] 111-111: Delete
····
(prettier/prettier)
[error] 112-112: Delete
····
(prettier/prettier)
[error] 113-113: Delete
··
(prettier/prettier)
116-136
: Expand valid tool test cases.The current valid tool test cases are basic. Consider adding these scenarios:
- Tools with all optional fields populated
- Tools with minimum required fields
- Tools with various repository hosting services (not just GitHub)
Example additions:
const validToolsExtended = { fullTool: { title: 'Full Tool', description: 'Detailed description', filters: { language: 'JavaScript', technology: ['Node.js'], categories: ['API', 'Testing'] }, links: { repoUrl: 'https://github.com/example/full-tool', docsUrl: 'https://docs.example.com', websiteUrl: 'https://example.com' } }, minimalTool: { title: 'Minimal Tool', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://gitlab.com/example/minimal-tool' } } };🧰 Tools
🪛 eslint
[error] 117-117: Delete
··
(prettier/prettier)
[error] 118-118: Replace
····
with··
(prettier/prettier)
[error] 119-119: Replace
········
with····
(prettier/prettier)
[error] 120-120: Replace
········
with····
(prettier/prettier)
[error] 121-121: Delete
··
(prettier/prettier)
[error] 122-122: Replace
····
with··
(prettier/prettier)
[error] 126-126: Delete
··
(prettier/prettier)
[error] 127-127: Delete
····
(prettier/prettier)
[error] 128-128: Replace
········
with····
(prettier/prettier)
[error] 129-129: Replace
····
with··
(prettier/prettier)
[error] 133-133: Delete
··
(prettier/prettier)
[error] 134-134: Replace
········
with····
(prettier/prettier)
[error] 135-135: Replace
····
with··
(prettier/prettier)
179-193
: Enhance circular reference test cases.Consider adding more complex circular reference scenarios:
- Nested circular references
- Multiple tools with circular dependencies
- Self-referential tools
Example enhancement:
const circularTools = { toolA: { title: 'Tool A', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/example/tool-a' }, dependencies: ['toolB'] }, toolB: { title: 'Tool B', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/example/tool-b' }, dependencies: ['toolC'] }, toolC: { title: 'Tool C', filters: { language: 'JavaScript' }, links: { repoUrl: 'https://github.com/example/tool-c' }, dependencies: ['toolA'] // Creates a circular dependency } };🧰 Tools
🪛 eslint
[error] 180-180: Replace
····
with··
(prettier/prettier)
[error] 181-181: Delete
··
(prettier/prettier)
[error] 182-182: Replace
········
with····
(prettier/prettier)
[error] 183-183: Replace
········
with····
(prettier/prettier)
[error] 184-184: Replace
····
with··
(prettier/prettier)
[error] 185-185: Delete
··
(prettier/prettier)
[error] 189-189: Delete
··
(prettier/prettier)
[error] 190-190: Replace
········
with····
(prettier/prettier)
[error] 191-191: Replace
········
with····
(prettier/prettier)
[error] 192-192: Replace
····
with··
(prettier/prettier)
195-212
: Consider organizing exports by test scenario.Group related exports together using object literals for better organization and maintainability.
Example reorganization:
module.exports = { // Basic data expectedDataT1, // Error cases errorCases: { missingData: manualToolsWithMissingData, invalidUrl: manualToolsWithInvalidURLT11, invalidCategory: invalidAutomatedToolsT10 }, // Sorting cases sortingCases: { manualToolsToSort }, // Language/Technology cases languageCases: { multipleLanguages: toolWithMultipleLanguages, newLanguage: toolWithNewLanguageT7, newTags: toolWithNewTagsT6 }, // Valid tool cases validCases: { automated: automatedToolsT8, manual: manualToolsT8 }, // Circular reference cases circularCases: { tool: circularTool, automated: automatedToolsT12 } };🧰 Tools
🪛 eslint
[error] 196-196: Replace
····
with··
(prettier/prettier)
[error] 197-197: Delete
··
(prettier/prettier)
[error] 198-198: Replace
····
with··
(prettier/prettier)
[error] 199-199: Replace
····
with··
(prettier/prettier)
[error] 200-200: Replace
····
with··
(prettier/prettier)
[error] 201-201: Delete
··
(prettier/prettier)
[error] 202-202: Replace
····
with··
(prettier/prettier)
[error] 203-203: Replace
····
with··
(prettier/prettier)
[error] 204-204: Replace
····
with··
(prettier/prettier)
[error] 205-205: Delete
··
(prettier/prettier)
[error] 206-206: Replace
····
with··
(prettier/prettier)
[error] 207-207: Delete
··
(prettier/prettier)
[error] 208-208: Replace
····
with··
(prettier/prettier)
[error] 209-209: Delete
··
(prettier/prettier)
[error] 210-210: Replace
····
with··
(prettier/prettier)
[error] 211-211: Delete
··
(prettier/prettier)
[error] 212-212: Insert
;
(prettier/prettier)
scripts/tools/combine-tools.js (3)
109-142
: Consider enhancing error handling with operation contextThe current error handling could be more informative by including the specific operation that failed.
try { // ... existing code ... } catch (err) { - throw new Error(`Error combining tools: ${err}`); + const operation = err.message.includes('validation') ? 'tool validation' : + err.message.includes('URL') ? 'URL parsing' : + err.message.includes('ENOENT') ? 'file operation' : 'tools combination'; + throw new Error(`Failed during ${operation}: ${err.message}`); }🧰 Tools
🪛 eslint
[error] 109-109: Delete
··
(prettier/prettier)
[error] 110-110: Delete
····
(prettier/prettier)
[error] 110-138: for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.
(no-restricted-syntax)
[error] 110-138: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
(guard-for-in)
[error] 111-111: Replace
············
with······
(prettier/prettier)
[error] 111-111: 'finalToolsList' is never reassigned. Use 'const' instead.
(prefer-const)
[error] 112-112: Delete
······
(prettier/prettier)
[error] 113-113: Replace
················
with········
(prettier/prettier)
[error] 113-115: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.
(no-restricted-syntax)
[error] 114-114: Replace
··········finalToolsList.push(await·getFinalTool(tool))
withfinalToolsList.push(await·getFinalTool(tool));
(prettier/prettier)
[error] 114-114: Unexpected
await
inside a loop.(no-await-in-loop)
[error] 115-115: Replace
················
with········
(prettier/prettier)
[error] 116-116: Replace
············
with······
(prettier/prettier)
[error] 117-117: Delete
······
(prettier/prettier)
[error] 118-118: Delete
········
(prettier/prettier)
[error] 118-134: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.
(no-restricted-syntax)
[error] 119-119: Delete
··········
(prettier/prettier)
[error] 120-120: Replace
····················const·isValid·=·await·validate(tool)
with··········const·isValid·=·await·validate(tool);
(prettier/prettier)
[error] 120-120: Unexpected
await
inside a loop.(no-await-in-loop)
[error] 121-121: Replace
····················
with··········
(prettier/prettier)
[error] 122-122: Replace
························
with············
(prettier/prettier)
[error] 123-123: Replace
····························const·url·=·new·URL(tool.links.repoUrl)
with··············const·url·=·new·URL(tool.links.repoUrl);
(prettier/prettier)
[error] 124-124: Replace
····························isAsyncAPIrepo·=·url.href.startsWith("https://github.com/asyncapi/")
with··············isAsyncAPIrepo·=·url.href.startsWith('https://github.com/asyncapi/');
(prettier/prettier)
[error] 125-125: Replace
························}·else·isAsyncAPIrepo·=·false
with············}·else·isAsyncAPIrepo·=·false;
(prettier/prettier)
[error] 126-126: Replace
························let·toolObject·=·await·createToolObject(tool,·"",·"",·isAsyncAPIrepo)
with············let·toolObject·=·await·createToolObject(tool,·'',·'',·isAsyncAPIrepo);
(prettier/prettier)
[error] 126-126: 'toolObject' is never reassigned. Use 'const' instead.
(prefer-const)
[error] 126-126: Unexpected
await
inside a loop.(no-await-in-loop)
[error] 127-127: Replace
························finalToolsList.push(await·getFinalTool(toolObject))
with············finalToolsList.push(await·getFinalTool(toolObject));
(prettier/prettier)
[error] 127-127: Unexpected
await
inside a loop.(no-await-in-loop)
[error] 128-128: Delete
··········
(prettier/prettier)
[error] 129-129: Replace
························
with············
(prettier/prettier)
[error] 130-130: Delete
············
(prettier/prettier)
[error] 131-131: Replace
························
with············
(prettier/prettier)
[error] 132-132: Delete
············
(prettier/prettier)
[error] 133-133: Replace
····················
with··········
(prettier/prettier)
[error] 134-134: Replace
················
with········
(prettier/prettier)
[error] 135-135: Replace
············
with······
(prettier/prettier)
[error] 136-136: Delete
······
(prettier/prettier)
[error] 137-137: Replace
············finalTools[key].toolsList·=·finalToolsList
with······finalTools[key].toolsList·=·finalToolsList;
(prettier/prettier)
[error] 138-138: Delete
····
(prettier/prettier)
[error] 139-139: Delete
····
(prettier/prettier)
[error] 140-140: Replace
········fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}),)
with····fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}));
(prettier/prettier)
[error] 141-141: Delete
··
(prettier/prettier)
[error] 142-142: Replace
········
with····
(prettier/prettier)
129-133
: Improve validation error logging structureThe current error logging could be more structured and consistent.
-console.error('Script is not failing, it is just dropping errors for further investigation'); -console.error(`Invalid ${tool.title} .asyncapi-tool file.`); -console.error(`Located in manual-tools.json file`); -console.error('Validation errors:', JSON.stringify(validate.errors, null, 2)); +console.error({ + message: 'Tool validation failed', + tool: tool.title, + source: 'manual-tools.json', + errors: validate.errors, + note: 'Script continues execution, error logged for investigation' +});🧰 Tools
🪛 eslint
[error] 129-129: Replace
························
with············
(prettier/prettier)
[error] 130-130: Delete
············
(prettier/prettier)
[error] 131-131: Replace
························
with············
(prettier/prettier)
[error] 132-132: Delete
············
(prettier/prettier)
[error] 133-133: Replace
····················
with··········
(prettier/prettier)
111-111
: Use const for variables that aren't reassignedThe variables
finalToolsList
andtoolObject
are never reassigned.-let finalToolsList = []; +const finalToolsList = []; -let toolObject = await createToolObject(tool, "", "", isAsyncAPIrepo) +const toolObject = await createToolObject(tool, '', '', isAsyncAPIrepo);Also applies to: 126-126
🧰 Tools
🪛 eslint
[error] 111-111: Replace
············
with······
(prettier/prettier)
[error] 111-111: 'finalToolsList' is never reassigned. Use 'const' instead.
(prefer-const)
tests/tools/combine-tools.test.js (2)
23-27
: Enhance AJV mock implementation for better test coverage.The current AJV mock is overly simplified, only validating the
title
field. Consider implementing a more comprehensive mock that validates all required fields according to your schema.jest.mock('ajv', () => { return jest.fn().mockImplementation(() => ({ - compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'), + compile: jest.fn().mockImplementation(() => (data) => { + const errors = []; + if (!data.title) errors.push({ message: 'title is required' }); + if (!data.description) errors.push({ message: 'description is required' }); + if (!data.filters) errors.push({ message: 'filters is required' }); + return errors.length === 0; + }), })); });🧰 Tools
🪛 eslint
[error] 23-23: 'jest' is not defined.
(no-undef)
[error] 24-24: 'jest' is not defined.
(no-undef)
[error] 25-25: 'jest' is not defined.
(no-undef)
[error] 25-25: Delete
,
(prettier/prettier)
84-94
: Improve test descriptions for better clarity.The test descriptions could be more specific about what they're testing. Consider updating them to clearly state the expected behavior and conditions being tested.
- it('should combine tools and create correct JSON files', async () => { + it('should combine automated and manual tools into JSON files with correct structure', async () => { - it('should handle tools with missing language or technology', async () => { + it('should successfully process tools when language or technology fields are missing', async () => { - it('should sort tools alphabetically by title', async () => { + it('should sort tools within each category alphabetically by title', async () => { - it('should log validation errors to console.error', async () => { + it('should log detailed validation errors to console.error without failing the script', async () => {Also applies to: 96-102, 104-111, 113-126
🧰 Tools
🪛 eslint
[error] 84-84: 'it' is not defined.
(no-undef)
[error] 93-93: Insert
;
(prettier/prettier)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (3)
scripts/tools/combine-tools.js
(1 hunks)tests/fixtures/combineToolsData.js
(1 hunks)tests/tools/combine-tools.test.js
(1 hunks)
🧰 Additional context used
📓 Learnings (1)
scripts/tools/combine-tools.js (1)
Learnt from: akshatnema
PR: asyncapi/website#3136
File: scripts/tools/combine-tools.js:122-125
Timestamp: 2024-11-01T12:49:32.806Z
Learning: In `scripts/tools/combine-tools.js`, the existing URL parsing logic for `repoUrl` without additional error handling is acceptable and does not require changes.
🪛 eslint
scripts/tools/combine-tools.js
[error] 109-109: Delete ··
(prettier/prettier)
[error] 110-110: Delete ····
(prettier/prettier)
[error] 110-138: for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.
(no-restricted-syntax)
[error] 110-138: The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
(guard-for-in)
[error] 111-111: Replace ············
with ······
(prettier/prettier)
[error] 111-111: 'finalToolsList' is never reassigned. Use 'const' instead.
(prefer-const)
[error] 112-112: Delete ······
(prettier/prettier)
[error] 113-113: Replace ················
with ········
(prettier/prettier)
[error] 113-115: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.
(no-restricted-syntax)
[error] 114-114: Replace ··········finalToolsList.push(await·getFinalTool(tool))
with finalToolsList.push(await·getFinalTool(tool));
(prettier/prettier)
[error] 114-114: Unexpected await
inside a loop.
(no-await-in-loop)
[error] 115-115: Replace ················
with ········
(prettier/prettier)
[error] 116-116: Replace ············
with ······
(prettier/prettier)
[error] 117-117: Delete ······
(prettier/prettier)
[error] 118-118: Delete ········
(prettier/prettier)
[error] 118-134: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.
(no-restricted-syntax)
[error] 119-119: Delete ··········
(prettier/prettier)
[error] 120-120: Replace ····················const·isValid·=·await·validate(tool)
with ··········const·isValid·=·await·validate(tool);
(prettier/prettier)
[error] 120-120: Unexpected await
inside a loop.
(no-await-in-loop)
[error] 121-121: Replace ····················
with ··········
(prettier/prettier)
[error] 122-122: Replace ························
with ············
(prettier/prettier)
[error] 123-123: Replace ····························const·url·=·new·URL(tool.links.repoUrl)
with ··············const·url·=·new·URL(tool.links.repoUrl);
(prettier/prettier)
[error] 124-124: Replace ····························isAsyncAPIrepo·=·url.href.startsWith("https://github.com/asyncapi/")
with ··············isAsyncAPIrepo·=·url.href.startsWith('https://github.com/asyncapi/');
(prettier/prettier)
[error] 125-125: Replace ························}·else·isAsyncAPIrepo·=·false
with ············}·else·isAsyncAPIrepo·=·false;
(prettier/prettier)
[error] 126-126: Replace ························let·toolObject·=·await·createToolObject(tool,·"",·"",·isAsyncAPIrepo)
with ············let·toolObject·=·await·createToolObject(tool,·'',·'',·isAsyncAPIrepo);
(prettier/prettier)
[error] 126-126: 'toolObject' is never reassigned. Use 'const' instead.
(prefer-const)
[error] 126-126: Unexpected await
inside a loop.
(no-await-in-loop)
[error] 127-127: Replace ························finalToolsList.push(await·getFinalTool(toolObject))
with ············finalToolsList.push(await·getFinalTool(toolObject));
(prettier/prettier)
[error] 127-127: Unexpected await
inside a loop.
(no-await-in-loop)
[error] 128-128: Delete ··········
(prettier/prettier)
[error] 129-129: Replace ························
with ············
(prettier/prettier)
[error] 130-130: Delete ············
(prettier/prettier)
[error] 131-131: Replace ························
with ············
(prettier/prettier)
[error] 132-132: Delete ············
(prettier/prettier)
[error] 133-133: Replace ····················
with ··········
(prettier/prettier)
[error] 134-134: Replace ················
with ········
(prettier/prettier)
[error] 135-135: Replace ············
with ······
(prettier/prettier)
[error] 136-136: Delete ······
(prettier/prettier)
[error] 137-137: Replace ············finalTools[key].toolsList·=·finalToolsList
with ······finalTools[key].toolsList·=·finalToolsList;
(prettier/prettier)
[error] 138-138: Delete ····
(prettier/prettier)
[error] 139-139: Delete ····
(prettier/prettier)
[error] 140-140: Replace ········fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}),)
with ····fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}));
(prettier/prettier)
[error] 141-141: Delete ··
(prettier/prettier)
[error] 142-142: Replace ········
with ····
(prettier/prettier)
[error] 143-143: Delete ··
(prettier/prettier)
[error] 144-144: Insert ;
(prettier/prettier)
[error] 146-146: Insert ;
(prettier/prettier)
tests/fixtures/combineToolsData.js
[error] 2-2: Delete ··
(prettier/prettier)
[error] 3-3: Delete ····
(prettier/prettier)
[error] 4-4: Replace ············
with ······
(prettier/prettier)
[error] 5-5: Replace ············
with ······
(prettier/prettier)
[error] 6-6: Delete ······
(prettier/prettier)
[error] 7-7: Replace ········
with ····
(prettier/prettier)
[error] 8-8: Replace ········
with ····
(prettier/prettier)
[error] 9-9: Replace ············
with ······
(prettier/prettier)
[error] 10-10: Replace ············
with ······
(prettier/prettier)
[error] 11-11: Delete ······
(prettier/prettier)
[error] 12-12: Delete ····
(prettier/prettier)
[error] 13-13: Delete ··
(prettier/prettier)
[error] 14-14: Delete ··
(prettier/prettier)
[error] 15-15: Delete ····
(prettier/prettier)
[error] 16-16: Replace ············
with ······
(prettier/prettier)
[error] 17-17: Delete ······
(prettier/prettier)
[error] 18-18: Replace ············
with ······
(prettier/prettier)
[error] 19-19: Replace ········
with ····
(prettier/prettier)
[error] 20-20: Replace ········
with ····
(prettier/prettier)
[error] 21-21: Replace ············
with ······
(prettier/prettier)
[error] 22-22: Replace ············
with ······
(prettier/prettier)
[error] 23-23: Delete ······
(prettier/prettier)
[error] 24-24: Replace ········
with ····
(prettier/prettier)
[error] 25-25: Delete ··
(prettier/prettier)
[error] 29-29: Delete ··
(prettier/prettier)
[error] 30-30: Replace ········
with ····
(prettier/prettier)
[error] 31-31: Delete ····
(prettier/prettier)
[error] 32-32: Replace ········
with ····
(prettier/prettier)
[error] 33-33: Delete ··
(prettier/prettier)
[error] 37-37: Delete ··
(prettier/prettier)
[error] 38-38: Replace ········
with ····
(prettier/prettier)
[error] 39-39: Delete ····
(prettier/prettier)
[error] 40-40: Replace ············
with ······
(prettier/prettier)
[error] 41-41: Replace ················
with ········
(prettier/prettier)
[error] 42-42: Delete ········
(prettier/prettier)
[error] 43-43: Replace ················
with ········
(prettier/prettier)
[error] 44-44: Delete ······
(prettier/prettier)
[error] 45-45: Replace ············
with ······
(prettier/prettier)
[error] 46-46: Replace ················
with ········
(prettier/prettier)
[error] 47-47: Delete ········
(prettier/prettier)
[error] 48-48: Replace ················
with ········
(prettier/prettier)
[error] 49-49: Delete ······
(prettier/prettier)
[error] 50-50: Delete ····
(prettier/prettier)
[error] 51-51: Delete ··
(prettier/prettier)
[error] 55-55: Delete ··
(prettier/prettier)
[error] 56-56: Delete ··
(prettier/prettier)
[error] 57-57: Replace ········
with ····
(prettier/prettier)
[error] 58-58: Delete ····
(prettier/prettier)
[error] 59-59: Delete ··
(prettier/prettier)
[error] 60-60: Delete ··
(prettier/prettier)
[error] 64-64: Replace ··'category1'
with category1
(prettier/prettier)
[error] 65-65: Replace ········
with ····
(prettier/prettier)
[error] 66-66: Replace ········
with ····
(prettier/prettier)
[error] 67-67: Delete ··
(prettier/prettier)
[error] 73-73: Replace ··'category1'
with category1
(prettier/prettier)
[error] 74-74: Delete ····
(prettier/prettier)
[error] 75-75: Delete ····
(prettier/prettier)
[error] 76-76: Delete ··
(prettier/prettier)
[error] 79-79: Replace ··'category1'
with category1
(prettier/prettier)
[error] 80-80: Replace ········
with ····
(prettier/prettier)
[error] 81-81: Delete ··
(prettier/prettier)
[error] 85-85: Replace ····
with ··
(prettier/prettier)
[error] 86-86: Delete ··
(prettier/prettier)
[error] 87-87: Replace ········
with ····
(prettier/prettier)
[error] 88-88: Replace ········
with ····
(prettier/prettier)
[error] 89-89: Delete ··
(prettier/prettier)
[error] 90-90: Delete ··
(prettier/prettier)
[error] 94-94: Replace ····'category1'
with ··category1
(prettier/prettier)
[error] 95-95: Delete ····
(prettier/prettier)
[error] 96-96: Replace ········
with ····
(prettier/prettier)
[error] 97-97: Delete ··
(prettier/prettier)
[error] 101-101: Delete ··
(prettier/prettier)
[error] 102-102: Delete ··
(prettier/prettier)
[error] 103-103: Delete ····
(prettier/prettier)
[error] 104-104: Delete ····
(prettier/prettier)
[error] 105-105: Delete ··
(prettier/prettier)
[error] 106-106: Delete ··
(prettier/prettier)
[error] 110-110: Replace ··'category1'
with category1
(prettier/prettier)
[error] 111-111: Delete ····
(prettier/prettier)
[error] 112-112: Delete ····
(prettier/prettier)
[error] 113-113: Delete ··
(prettier/prettier)
[error] 117-117: Delete ··
(prettier/prettier)
[error] 118-118: Replace ····
with ··
(prettier/prettier)
[error] 119-119: Replace ········
with ····
(prettier/prettier)
[error] 120-120: Replace ········
with ····
(prettier/prettier)
[error] 121-121: Delete ··
(prettier/prettier)
[error] 122-122: Replace ····
with ··
(prettier/prettier)
[error] 126-126: Delete ··
(prettier/prettier)
[error] 127-127: Delete ····
(prettier/prettier)
[error] 128-128: Replace ········
with ····
(prettier/prettier)
[error] 129-129: Replace ····
with ··
(prettier/prettier)
[error] 133-133: Delete ··
(prettier/prettier)
[error] 134-134: Replace ········
with ····
(prettier/prettier)
[error] 135-135: Replace ····
with ··
(prettier/prettier)
[error] 139-139: Delete ··
(prettier/prettier)
[error] 140-140: Replace ····
with ··
(prettier/prettier)
[error] 141-141: Delete ····
(prettier/prettier)
[error] 142-142: Replace ········
with ····
(prettier/prettier)
[error] 143-143: Delete ··
(prettier/prettier)
[error] 144-144: Replace ····
with ··
(prettier/prettier)
[error] 148-148: Delete ··
(prettier/prettier)
[error] 149-149: Replace ········
with ····
(prettier/prettier)
[error] 150-150: Delete ····
(prettier/prettier)
[error] 151-151: Replace ····
with ··
(prettier/prettier)
[error] 155-155: Delete ··
(prettier/prettier)
[error] 156-156: Replace ········
with ····
(prettier/prettier)
[error] 157-157: Delete ··
(prettier/prettier)
[error] 161-161: Delete ··
(prettier/prettier)
[error] 162-162: Delete ····
(prettier/prettier)
[error] 163-163: Replace ········
with ····
(prettier/prettier)
[error] 164-164: Delete ··
(prettier/prettier)
[error] 168-168: Delete ··
(prettier/prettier)
[error] 169-169: Replace ········
with ····
(prettier/prettier)
[error] 170-170: Delete ······
(prettier/prettier)
[error] 171-171: Delete ········
(prettier/prettier)
[error] 172-172: Delete ········
(prettier/prettier)
[error] 173-173: Delete ········
(prettier/prettier)
[error] 174-174: Delete ······
(prettier/prettier)
[error] 175-175: Replace ········
with ····
(prettier/prettier)
[error] 176-176: Delete ··
(prettier/prettier)
[error] 180-180: Replace ····
with ··
(prettier/prettier)
[error] 181-181: Delete ··
(prettier/prettier)
[error] 182-182: Replace ········
with ····
(prettier/prettier)
[error] 183-183: Replace ········
with ····
(prettier/prettier)
[error] 184-184: Replace ····
with ··
(prettier/prettier)
[error] 185-185: Delete ··
(prettier/prettier)
[error] 189-189: Delete ··
(prettier/prettier)
[error] 190-190: Replace ········
with ····
(prettier/prettier)
[error] 191-191: Replace ········
with ····
(prettier/prettier)
[error] 192-192: Replace ····
with ··
(prettier/prettier)
[error] 196-196: Replace ····
with ··
(prettier/prettier)
[error] 197-197: Delete ··
(prettier/prettier)
[error] 198-198: Replace ····
with ··
(prettier/prettier)
[error] 199-199: Replace ····
with ··
(prettier/prettier)
[error] 200-200: Replace ····
with ··
(prettier/prettier)
[error] 201-201: Delete ··
(prettier/prettier)
[error] 202-202: Replace ····
with ··
(prettier/prettier)
[error] 203-203: Replace ····
with ··
(prettier/prettier)
[error] 204-204: Replace ····
with ··
(prettier/prettier)
[error] 205-205: Delete ··
(prettier/prettier)
[error] 206-206: Replace ····
with ··
(prettier/prettier)
[error] 207-207: Delete ··
(prettier/prettier)
[error] 208-208: Replace ····
with ··
(prettier/prettier)
[error] 209-209: Delete ··
(prettier/prettier)
[error] 210-210: Replace ····
with ··
(prettier/prettier)
[error] 211-211: Delete ··
(prettier/prettier)
[error] 212-212: Insert ;
(prettier/prettier)
tests/tools/combine-tools.test.js
[error] 23-23: 'jest' is not defined.
(no-undef)
[error] 24-24: 'jest' is not defined.
(no-undef)
[error] 25-25: 'jest' is not defined.
(no-undef)
[error] 25-25: Delete ,
(prettier/prettier)
[error] 27-28: Delete ⏎
(prettier/prettier)
[error] 30-30: 'jest' is not defined.
(no-undef)
[error] 31-31: 'jest' is not defined.
(no-undef)
[error] 34-34: 'jest' is not defined.
(no-undef)
[error] 45-45: 'jest' is not defined.
(no-undef)
[error] 54-54: 'describe' is not defined.
(no-undef)
[error] 64-64: 'beforeAll' is not defined.
(no-undef)
[error] 69-69: 'afterAll' is not defined.
(no-undef)
[error] 76-76: 'beforeEach' is not defined.
(no-undef)
[error] 77-77: 'jest' is not defined.
(no-undef)
[error] 77-77: Delete ·
(prettier/prettier)
[error] 80-80: 'afterEach' is not defined.
(no-undef)
[error] 81-81: 'jest' is not defined.
(no-undef)
[error] 84-84: 'it' is not defined.
(no-undef)
[error] 93-93: Insert ;
(prettier/prettier)
[error] 96-96: 'it' is not defined.
(no-undef)
[error] 96-97: Delete ⏎
(prettier/prettier)
[error] 104-104: 'it' is not defined.
(no-undef)
[error] 105-106: Delete ⏎
(prettier/prettier)
[error] 109-109: Replace tool
with (tool)
(prettier/prettier)
[error] 113-113: 'it' is not defined.
(no-undef)
[error] 113-114: Delete ⏎
(prettier/prettier)
[error] 128-128: 'it' is not defined.
(no-undef)
[error] 128-129: Delete ⏎
(prettier/prettier)
[error] 144-144: 'it' is not defined.
(no-undef)
[error] 145-146: Delete ⏎
(prettier/prettier)
[error] 170-170: 'it' is not defined.
(no-undef)
[error] 171-172: Delete ⏎
(prettier/prettier)
[error] 188-188: 'it' is not defined.
(no-undef)
[error] 188-189: Delete ⏎
(prettier/prettier)
[error] 192-193: Delete ⏎
(prettier/prettier)
[error] 206-206: 'it' is not defined.
(no-undef)
[error] 206-207: Delete ⏎
(prettier/prettier)
[error] 216-216: 'it' is not defined.
(no-undef)
[error] 218-218: Replace "this/is/not/valid"
with 'this/is/not/valid';
(prettier/prettier)
[error] 229-229: 'it' is not defined.
(no-undef)
[error] 241-241: 'it' is not defined.
(no-undef)
[error] 253-253: 'it' is not defined.
(no-undef)
🔇 Additional comments (2)
scripts/tools/combine-tools.js (1)
139-140
: LGTM: File operations are appropriate for this use case
The synchronous file operations are suitable for this script's requirements.
🧰 Tools
🪛 eslint
[error] 139-139: Delete ····
(prettier/prettier)
[error] 140-140: Replace ········fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}),)
with ····fs.writeFileSync(tagsPath,·JSON.stringify({·languages:·languageList,·technologies:·technologyList·}));
(prettier/prettier)
tests/tools/combine-tools.test.js (1)
216-227
: 🛠️ Refactor suggestion
Refactor error handling tests using Jest's async utilities.
These tests use verbose try-catch blocks. Consider using Jest's .rejects.toThrow()
for cleaner and more idiomatic error handling tests.
Also applies to: 229-239, 241-251, 253-264
🧰 Tools
🪛 eslint
[error] 216-216: 'it' is not defined.
(no-undef)
[error] 218-218: Replace "this/is/not/valid"
with 'this/is/not/valid';
(prettier/prettier)
const expectedDataT1 = { | ||
languages: [ | ||
{ | ||
name: 'JavaScript', | ||
color: 'bg-[#57f281]', | ||
borderColor: 'border-[#37f069]' | ||
}, | ||
{ | ||
name: 'Python', | ||
color: 'bg-[#3572A5]', | ||
borderColor: 'border-[#3572A5]' | ||
} | ||
], | ||
technologies: [ | ||
{ | ||
name: 'Node.js', | ||
color: 'bg-[#61d0f2]', | ||
borderColor: 'border-[#40ccf7]' | ||
}, | ||
{ | ||
name: 'Flask', | ||
color: 'bg-[#000000]', | ||
borderColor: 'border-[#FFFFFF]' | ||
} | ||
] | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider reusing tagsData
instead of duplicating data.
The color and border color data for languages and technologies appears to be duplicated from tagsData
. To maintain consistency and reduce maintenance overhead, consider importing and reusing the existing data.
Example approach:
const { tagsData } = require('../path/to/tagsData');
const expectedDataT1 = {
languages: tagsData.languages.map(({ name, color, borderColor }) => ({
name,
color,
borderColor
})).filter(lang => ['JavaScript', 'Python'].includes(lang.name)),
technologies: tagsData.technologies.map(({ name, color, borderColor }) => ({
name,
color,
borderColor
})).filter(tech => ['Node.js', 'Flask'].includes(tech.name))
};
🧰 Tools
🪛 eslint
[error] 2-2: Delete ··
(prettier/prettier)
[error] 3-3: Delete ····
(prettier/prettier)
[error] 4-4: Replace ············
with ······
(prettier/prettier)
[error] 5-5: Replace ············
with ······
(prettier/prettier)
[error] 6-6: Delete ······
(prettier/prettier)
[error] 7-7: Replace ········
with ····
(prettier/prettier)
[error] 8-8: Replace ········
with ····
(prettier/prettier)
[error] 9-9: Replace ············
with ······
(prettier/prettier)
[error] 10-10: Replace ············
with ······
(prettier/prettier)
[error] 11-11: Delete ······
(prettier/prettier)
[error] 12-12: Delete ····
(prettier/prettier)
[error] 13-13: Delete ··
(prettier/prettier)
[error] 14-14: Delete ··
(prettier/prettier)
[error] 15-15: Delete ····
(prettier/prettier)
[error] 16-16: Replace ············
with ······
(prettier/prettier)
[error] 17-17: Delete ······
(prettier/prettier)
[error] 18-18: Replace ············
with ······
(prettier/prettier)
[error] 19-19: Replace ········
with ····
(prettier/prettier)
[error] 20-20: Replace ········
with ····
(prettier/prettier)
[error] 21-21: Replace ············
with ······
(prettier/prettier)
[error] 22-22: Replace ············
with ······
(prettier/prettier)
[error] 23-23: Delete ······
(prettier/prettier)
[error] 24-24: Replace ········
with ····
(prettier/prettier)
[error] 25-25: Delete ··
(prettier/prettier)
const fs = require('fs'); | ||
const path = require('path'); | ||
const { combineTools } = require('../../scripts/tools/combine-tools'); | ||
const { | ||
expectedDataT1, | ||
manualToolsWithMissingData, | ||
manualToolsToSort, | ||
automatedToolsT5, | ||
automatedToolsT4, | ||
manualToolsT4, | ||
automatedToolsT6, | ||
automatedToolsT7, | ||
automatedToolsT8, | ||
manualToolsT8, | ||
automatedToolsT9, | ||
manualToolsT9, | ||
automatedToolsT12, | ||
invalidAutomatedToolsT10, | ||
manualToolsWithInvalidURLT11, | ||
circularTool | ||
} = require('../fixtures/combineToolsData'); | ||
|
||
jest.mock('ajv', () => { | ||
return jest.fn().mockImplementation(() => ({ | ||
compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'), | ||
})); | ||
}); | ||
|
||
|
||
jest.mock('ajv-formats', () => { | ||
return jest.fn(); | ||
}); | ||
|
||
jest.mock('../../scripts/tools/tags-color', () => ({ | ||
languagesColor: [ | ||
{ name: 'JavaScript', color: 'bg-[#57f281]', borderColor: 'border-[#37f069]' }, | ||
{ name: 'Python', color: 'bg-[#3572A5]', borderColor: 'border-[#3572A5]' } | ||
], | ||
technologiesColor: [ | ||
{ name: 'Node.js', color: 'bg-[#61d0f2]', borderColor: 'border-[#40ccf7]' }, | ||
{ name: 'Flask', color: 'bg-[#000000]', borderColor: 'border-[#FFFFFF]' } | ||
] | ||
})); | ||
|
||
jest.mock('../../scripts/tools/categorylist', () => ({ | ||
categoryList: [ | ||
{ name: 'category1', description: 'Sample Category 1' }, | ||
{ name: 'category2', description: 'Sample Category 2' } | ||
] | ||
})); | ||
|
||
const readJSON = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf-8')); | ||
|
||
describe('combineTools function', () => { | ||
const toolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'tools.json'); | ||
const tagsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'tags.json'); | ||
const manualToolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'manual-tools.json'); | ||
const automatedToolsPath = path.join(__dirname, '../', 'fixtures', 'tools', 'automated-tools.json'); | ||
|
||
let manualTools; | ||
let automatedTools; | ||
let consoleErrorMock; | ||
|
||
beforeAll(() => { | ||
manualTools = readJSON(manualToolsPath); | ||
automatedTools = readJSON(automatedToolsPath); | ||
}); | ||
|
||
afterAll(() => { | ||
if (fs.existsSync(toolsPath)) fs.unlinkSync(toolsPath); | ||
if (fs.existsSync(tagsPath)) fs.unlinkSync(tagsPath); | ||
|
||
consoleErrorMock.mockRestore(); | ||
}); | ||
|
||
beforeEach(() => { | ||
consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => { }); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add ESLint configuration for Jest environment.
The static analysis shows multiple 'undefined' errors for Jest globals. This can be fixed by adding the Jest environment to your ESLint configuration.
Add this to your .eslintrc.js
or equivalent:
module.exports = {
env: {
jest: true
}
}
🧰 Tools
🪛 eslint
[error] 23-23: 'jest' is not defined.
(no-undef)
[error] 24-24: 'jest' is not defined.
(no-undef)
[error] 25-25: 'jest' is not defined.
(no-undef)
[error] 25-25: Delete ,
(prettier/prettier)
[error] 27-28: Delete ⏎
(prettier/prettier)
[error] 30-30: 'jest' is not defined.
(no-undef)
[error] 31-31: 'jest' is not defined.
(no-undef)
[error] 34-34: 'jest' is not defined.
(no-undef)
[error] 45-45: 'jest' is not defined.
(no-undef)
[error] 54-54: 'describe' is not defined.
(no-undef)
[error] 64-64: 'beforeAll' is not defined.
(no-undef)
[error] 69-69: 'afterAll' is not defined.
(no-undef)
[error] 76-76: 'beforeEach' is not defined.
(no-undef)
[error] 77-77: 'jest' is not defined.
(no-undef)
[error] 77-77: Delete ·
(prettier/prettier)
[error] 80-80: 'afterEach' is not defined.
(no-undef)
[error] 81-81: 'jest' is not defined.
(no-undef)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Outside diff range and nitpick comments (2)
tests/tools/combine-tools.test.js (2)
69-74
: Improve test file cleanupConsider adding file cleanup in
beforeAll
to ensure a clean state for each test run, and combine the file existence checks for better readability.afterAll(() => { - if (fs.existsSync(toolsPath)) fs.unlinkSync(toolsPath); - if (fs.existsSync(tagsPath)) fs.unlinkSync(tagsPath); + [toolsPath, tagsPath].forEach(file => { + if (fs.existsSync(file)) fs.unlinkSync(file); + }); consoleErrorMock.mockRestore(); });
84-215
: Improve test consistency and assertionsThe test cases are well-structured, but consider these improvements:
- Remove unnecessary blank lines between test blocks
- Use more specific assertions where possible
- Add test cases for edge cases (empty arrays, null values)
Example of more specific assertions:
- expect(combinedTools).toHaveProperty('category1'); + expect(combinedTools.category1).toBeDefined(); + expect(combinedTools.category1.toolsList).toBeInstanceOf(Array);🧰 Tools
🪛 eslint
[error] 93-93: Insert
;
(prettier/prettier)
[error] 96-97: Delete
⏎
(prettier/prettier)
[error] 104-105: Delete
⏎
(prettier/prettier)
[error] 109-109: Replace
tool
with(tool)
(prettier/prettier)
[error] 113-114: Delete
⏎
(prettier/prettier)
[error] 129-130: Delete
⏎
(prettier/prettier)
[error] 144-145: Delete
⏎
(prettier/prettier)
[error] 170-171: Delete
⏎
(prettier/prettier)
[error] 189-191: Replace
⏎····await·combineTools(automatedToolsT8,·manualToolsT8,·toolsPath,·tagsPath);⏎
with····await·combineTools(automatedToolsT8,·manualToolsT8,·toolsPath,·tagsPath);
(prettier/prettier)
[error] 207-208: Delete
⏎
(prettier/prettier)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (1)
tests/tools/combine-tools.test.js
(1 hunks)
🧰 Additional context used
🪛 eslint
tests/tools/combine-tools.test.js
[error] 25-25: Delete ,
(prettier/prettier)
[error] 27-28: Delete ⏎
(prettier/prettier)
[error] 77-77: Delete ·
(prettier/prettier)
[error] 93-93: Insert ;
(prettier/prettier)
[error] 96-97: Delete ⏎
(prettier/prettier)
[error] 104-105: Delete ⏎
(prettier/prettier)
[error] 109-109: Replace tool
with (tool)
(prettier/prettier)
[error] 113-114: Delete ⏎
(prettier/prettier)
[error] 129-130: Delete ⏎
(prettier/prettier)
[error] 144-145: Delete ⏎
(prettier/prettier)
[error] 170-171: Delete ⏎
(prettier/prettier)
[error] 189-191: Replace ⏎····await·combineTools(automatedToolsT8,·manualToolsT8,·toolsPath,·tagsPath);⏎
with ····await·combineTools(automatedToolsT8,·manualToolsT8,·toolsPath,·tagsPath);
(prettier/prettier)
[error] 207-208: Delete ⏎
(prettier/prettier)
[error] 218-218: Replace "this/is/not/valid"
with 'this/is/not/valid';
(prettier/prettier)
[error] 264-265: Delete ⏎··
(prettier/prettier)
jest.mock('ajv', () => { | ||
return jest.fn().mockImplementation(() => ({ | ||
compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'), | ||
})); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance the AJV validator mock implementation
The current mock only validates the title
field, which might miss potential validation issues. Consider implementing a more comprehensive mock that validates all required fields according to your schema.
jest.mock('ajv', () => {
return jest.fn().mockImplementation(() => ({
- compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'),
+ compile: jest.fn().mockImplementation(() => (data) => {
+ const errors = [];
+ if (!data.title) errors.push({ field: 'title', message: 'Title is required' });
+ if (!data.filters) errors.push({ field: 'filters', message: 'Filters are required' });
+ if (!data.category) errors.push({ field: 'category', message: 'Category is required' });
+ return errors.length === 0;
+ }),
}));
});
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
jest.mock('ajv', () => { | |
return jest.fn().mockImplementation(() => ({ | |
compile: jest.fn().mockImplementation(() => (data) => data.title !== 'Invalid Tool'), | |
})); | |
}); | |
jest.mock('ajv', () => { | |
return jest.fn().mockImplementation(() => ({ | |
compile: jest.fn().mockImplementation(() => (data) => { | |
const errors = []; | |
if (!data.title) errors.push({ field: 'title', message: 'Title is required' }); | |
if (!data.filters) errors.push({ field: 'filters', message: 'Filters are required' }); | |
if (!data.category) errors.push({ field: 'category', message: 'Category is required' }); | |
return errors.length === 0; | |
}), | |
})); | |
}); |
🧰 Tools
🪛 eslint
[error] 25-25: Delete ,
(prettier/prettier)
it('should throw an error when fs.writeFileSync fails', async () => { | ||
let error; | ||
const invalidPath = "this/is/not/valid" | ||
|
||
try { | ||
await combineTools(automatedTools, manualTools, invalidPath, invalidPath); | ||
} catch (err) { | ||
error = err; | ||
expect(err.message).toMatch(/ENOENT|EACCES/); | ||
} | ||
expect(error).toBeDefined(); | ||
}); | ||
|
||
it('should throw an error when there is an invalid category', async () => { | ||
let error; | ||
|
||
try { | ||
await combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath); | ||
} catch (err) { | ||
error = err; | ||
expect(err.message).toContain('Error combining tools'); | ||
} | ||
expect(error).toBeDefined(); | ||
}); | ||
|
||
it('should throw an error when URL parsing fails', async () => { | ||
let error; | ||
|
||
try { | ||
await combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath); | ||
} catch (err) { | ||
error = err; | ||
expect(err.message).toContain('Invalid URL'); | ||
} | ||
expect(error).toBeDefined(); | ||
}); | ||
|
||
it('should handle errors when processing tools with circular references', async () => { | ||
let error; | ||
circularTool.circular = circularTool; | ||
|
||
try { | ||
await combineTools(automatedToolsT12, {}, toolsPath, tagsPath); | ||
} catch (err) { | ||
error = err; | ||
expect(err.message).toContain('Converting circular structure to JSON'); | ||
} | ||
expect(error).toBeDefined(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Simplify error handling tests using Jest's .rejects.toThrow()
The current error handling tests use verbose try-catch blocks. Use Jest's built-in .rejects.toThrow()
for cleaner and more idiomatic tests.
- it('should throw an error when fs.writeFileSync fails', async () => {
- let error;
- const invalidPath = "this/is/not/valid"
-
- try {
- await combineTools(automatedTools, manualTools, invalidPath, invalidPath);
- } catch (err) {
- error = err;
- expect(err.message).toMatch(/ENOENT|EACCES/);
- }
- expect(error).toBeDefined();
- });
+ it('should throw an error when fs.writeFileSync fails', async () => {
+ const invalidPath = 'this/is/not/valid';
+ await expect(combineTools(automatedTools, manualTools, invalidPath, invalidPath))
+ .rejects.toThrow(/ENOENT|EACCES/);
+ });
- it('should throw an error when there is an invalid category', async () => {
- let error;
-
- try {
- await combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath);
- } catch (err) {
- error = err;
- expect(err.message).toContain('Error combining tools');
- }
- expect(error).toBeDefined();
- });
+ it('should throw an error when there is an invalid category', async () => {
+ await expect(combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath))
+ .rejects.toThrow('Error combining tools');
+ });
- it('should throw an error when URL parsing fails', async () => {
- let error;
-
- try {
- await combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath);
- } catch (err) {
- error = err;
- expect(err.message).toContain('Invalid URL');
- }
- expect(error).toBeDefined();
- });
+ it('should throw an error when URL parsing fails', async () => {
+ await expect(combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath))
+ .rejects.toThrow('Invalid URL');
+ });
- it('should handle errors when processing tools with circular references', async () => {
- let error;
- circularTool.circular = circularTool;
-
- try {
- await combineTools(automatedToolsT12, {}, toolsPath, tagsPath);
- } catch (err) {
- error = err;
- expect(err.message).toContain('Converting circular structure to JSON');
- }
- expect(error).toBeDefined();
- });
+ it('should handle errors when processing tools with circular references', async () => {
+ circularTool.circular = circularTool;
+ await expect(combineTools(automatedToolsT12, {}, toolsPath, tagsPath))
+ .rejects.toThrow('Converting circular structure to JSON');
+ });
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
it('should throw an error when fs.writeFileSync fails', async () => { | |
let error; | |
const invalidPath = "this/is/not/valid" | |
try { | |
await combineTools(automatedTools, manualTools, invalidPath, invalidPath); | |
} catch (err) { | |
error = err; | |
expect(err.message).toMatch(/ENOENT|EACCES/); | |
} | |
expect(error).toBeDefined(); | |
}); | |
it('should throw an error when there is an invalid category', async () => { | |
let error; | |
try { | |
await combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath); | |
} catch (err) { | |
error = err; | |
expect(err.message).toContain('Error combining tools'); | |
} | |
expect(error).toBeDefined(); | |
}); | |
it('should throw an error when URL parsing fails', async () => { | |
let error; | |
try { | |
await combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath); | |
} catch (err) { | |
error = err; | |
expect(err.message).toContain('Invalid URL'); | |
} | |
expect(error).toBeDefined(); | |
}); | |
it('should handle errors when processing tools with circular references', async () => { | |
let error; | |
circularTool.circular = circularTool; | |
try { | |
await combineTools(automatedToolsT12, {}, toolsPath, tagsPath); | |
} catch (err) { | |
error = err; | |
expect(err.message).toContain('Converting circular structure to JSON'); | |
} | |
expect(error).toBeDefined(); | |
}); | |
it('should throw an error when fs.writeFileSync fails', async () => { | |
const invalidPath = 'this/is/not/valid'; | |
await expect(combineTools(automatedTools, manualTools, invalidPath, invalidPath)) | |
.rejects.toThrow(/ENOENT|EACCES/); | |
}); | |
it('should throw an error when there is an invalid category', async () => { | |
await expect(combineTools(invalidAutomatedToolsT10, manualTools, toolsPath, tagsPath)) | |
.rejects.toThrow('Error combining tools'); | |
}); | |
it('should throw an error when URL parsing fails', async () => { | |
await expect(combineTools(automatedTools, manualToolsWithInvalidURLT11, toolsPath, tagsPath)) | |
.rejects.toThrow('Invalid URL'); | |
}); | |
it('should handle errors when processing tools with circular references', async () => { | |
circularTool.circular = circularTool; | |
await expect(combineTools(automatedToolsT12, {}, toolsPath, tagsPath)) | |
.rejects.toThrow('Converting circular structure to JSON'); | |
}); |
🧰 Tools
🪛 eslint
[error] 218-218: Replace "this/is/not/valid"
with 'this/is/not/valid';
(prettier/prettier)
This PR adds test for combine tools script.
Summary by CodeRabbit
New Features
Bug Fixes
Tests