Debugging & Testing Guide
This guide provides step-by-step instructions for debugging and testing the AsyncAPI CLI. Whether you're fixing a bug, adding a new feature, or understanding existing code, this document will help you navigate the debugging process effectively.
Table of Contents
- Project Structure Overview
- Setting Up Your Environment
- Debugging CLI Commands
- Debugging the API Server
- Debugging Services
- Writing Tests
- Running Tests
- Common Issues & Solutions
- Debugging Tools & Tips
Project Structure Overview
Understanding the codebase structure is essential for effective debugging:
1src/
2├── apps/
3│ ├── api/ # REST API server (Express.js)
4│ │ ├── controllers/ # API endpoint handlers
5│ │ ├── middlewares/ # Request/response middleware
6│ │ └── exceptions/ # API error types
7│ └── cli/ # CLI application (oclif)
8│ ├── commands/ # CLI command implementations
9│ └── internal/ # Shared CLI utilities, flags, base classes
10├── domains/
11│ ├── models/ # Domain models (SpecificationFile, Context, etc.)
12│ └── services/ # Business logic services
13├── errors/ # Custom error classes
14├── utils/ # Utility functions
15└── interfaces/ # TypeScript interfaces
16
17test/
18├── fixtures/ # Test data files (AsyncAPI specs, etc.)
19├── helpers/ # Test utilities
20├── integration/ # Integration tests for CLI commands
21└── unit/ # Unit tests for services and controllers
22 ├── controllers/ # API controller tests
23 ├── services/ # Service layer tests
24 └── utils/ # Utility function testsSetting Up Your Environment
1. Install Dependencies
npm install2. Build the Project
npm run build3. Set Up Environment Variables for Debugging
Create a .env file or export variables:
1# Enable development mode (verbose logging)
2export NODE_ENV=development
3
4# Disable analytics during testing
5export TEST=1
6
7# Set custom context file for testing
8export CUSTOM_CONTEXT_FILENAME="test.asyncapi-cli"
9export CUSTOM_CONTEXT_FILE_LOCATION=""Debugging CLI Commands
Method 1: Using bin/run (Development Mode)
The bin/run script runs the CLI in development mode with TypeScript directly:
1# Run any command with debugging
2./bin/run validate ./path/to/asyncapi.yml
3
4# With verbose output
5DEBUG=* ./bin/run validate ./path/to/asyncapi.ymlMethod 2: Using Node.js Inspector
1# Start with Node inspector
2node --inspect-brk ./bin/run validate ./path/to/asyncapi.yml
3
4# Then open Chrome DevTools at: chrome://inspectMethod 3: VS Code Debugging
Create .vscode/launch.json:
1{
2 "version": "0.2.0",
3 "configurations": [
4 {
5 "type": "node",
6 "request": "launch",
7 "name": "Debug CLI Command",
8 "program": "${workspaceFolder}/bin/run",
9 "args": ["validate", "./test/fixtures/specification.yml"],
10 "env": {
11 "NODE_ENV": "development",
12 "TEST": "1"
13 },
14 "sourceMaps": true,
15 "outFiles": ["${workspaceFolder}/lib/**/*.js"]
16 },
17 {
18 "type": "node",
19 "request": "launch",
20 "name": "Debug Current Test File",
21 "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
22 "args": [
23 "--require", "ts-node/register",
24 "--require", "tsconfig-paths/register",
25 "--timeout", "100000",
26 "${file}"
27 ],
28 "env": {
29 "NODE_ENV": "development",
30 "TEST": "1",
31 "CUSTOM_CONTEXT_FILENAME": "test.asyncapi-cli"
32 },
33 "sourceMaps": true
34 }
35 ]
36}Method 4: Adding Console Logs
For quick debugging, add logs in command files:
1// In src/apps/cli/commands/validate.ts
2async run() {
3 const { args, flags } = await this.parse(Validate);
4
5 // Debug: Log parsed arguments
6 console.log('DEBUG - Args:', JSON.stringify(args, null, 2));
7 console.log('DEBUG - Flags:', JSON.stringify(flags, null, 2));
8
9 // ... rest of the command
10}Debugging the API Server
Starting the API in Development Mode
1# Start with hot-reload
2npm run api:dev
3
4# Or manually with debugging
5NODE_ENV=development DEBUG=* node ./lib/apps/api/server.jsTesting API Endpoints
1# Validate endpoint
2curl -X POST http://localhost:3000/v1/validate \
3 -H "Content-Type: application/json" \
4 -d '{"asyncapi": "asyncapi: 3.0.0\ninfo:\n title: Test\n version: 1.0.0\nchannels: {}"}'
5
6# Parse endpoint
7curl -X POST http://localhost:3000/v1/parse \
8 -H "Content-Type: application/json" \
9 -d '{"asyncapi": "..."}'Debugging API Controllers
Add middleware logging:
1// In src/apps/api/middlewares/logger.middleware.ts
2// Logs are automatically enabled in development modeDebugging Services
ValidationService
The ValidationService handles document validation. To debug:
1// Test the service directly
2import { ValidationService } from '@services/validation.service';
3import { load } from '@models/SpecificationFile';
4
5async function debugValidation() {
6 const service = new ValidationService();
7 const specFile = await load('./path/to/spec.yml');
8
9 const result = await service.validateDocument(specFile, {
10 'fail-severity': 'error',
11 'log-diagnostics': true,
12 });
13
14 console.log('Validation Result:', JSON.stringify(result, null, 2));
15}ConversionService
1import { ConversionService } from '@services/convert.service';
2import { load } from '@models/SpecificationFile';
3
4async function debugConversion() {
5 const service = new ConversionService();
6 const specFile = await load('./path/to/spec.yml');
7
8 const result = await service.convertDocument(specFile, {
9 format: 'asyncapi',
10 'target-version': '3.0.0',
11 perspective: 'server',
12 });
13
14 console.log('Conversion Result:', JSON.stringify(result, null, 2));
15}GeneratorService
1import { GeneratorService } from '@services/generator.service';
2
3async function debugGenerator() {
4 const service = new GeneratorService();
5 // Add your debugging logic
6}Writing Tests
Test File Naming Convention
- Unit tests:
test/unit/<category>/<name>.test.ts - Integration tests:
test/integration/<command>.test.ts
Unit Test Structure
1// test/unit/services/my-service.test.ts
2import { expect } from 'chai';
3import { MyService } from '../../../src/domains/services/my.service';
4
5describe('MyService', () => {
6 let service: MyService;
7
8 beforeEach(() => {
9 service = new MyService();
10 });
11
12 describe('methodName', () => {
13 it('should do something when given valid input', async () => {
14 // Arrange
15 const input = { /* test data */ };
16
17 // Act
18 const result = await service.methodName(input);
19
20 // Assert
21 expect(result.success).to.be.true;
22 expect(result.data).to.exist;
23 });
24
25 it('should handle errors gracefully', async () => {
26 // Arrange
27 const invalidInput = null;
28
29 // Act
30 const result = await service.methodName(invalidInput);
31
32 // Assert
33 expect(result.success).to.be.false;
34 expect(result.error).to.include('error message');
35 });
36 });
37});Integration Test Structure (CLI Commands)
1// test/integration/mycommand.test.ts
2import { expect, test } from '@oclif/test';
3import path from 'path';
4
5const validSpec = path.resolve(__dirname, '../fixtures/specification.yml');
6const invalidSpec = path.resolve(__dirname, '../fixtures/specification-invalid.yml');
7
8describe('mycommand', () => {
9 describe('with valid input', () => {
10 test
11 .stdout()
12 .command(['mycommand', validSpec])
13 .it('should succeed with valid specification', (ctx) => {
14 expect(ctx.stdout).to.contain('Success');
15 });
16 });
17
18 describe('with invalid input', () => {
19 test
20 .stderr()
21 .command(['mycommand', invalidSpec])
22 .exit(1)
23 .it('should fail with invalid specification', (ctx) => {
24 expect(ctx.stderr).to.contain('Error');
25 });
26 });
27
28 describe('with flags', () => {
29 test
30 .stdout()
31 .command(['mycommand', validSpec, '--output', 'result.json'])
32 .it('should handle output flag', (ctx) => {
33 expect(ctx.stdout).to.contain('saved');
34 });
35 });
36});API Controller Test Structure
1// test/unit/controllers/my.controller.test.ts
2import request from 'supertest';
3import { App } from '../../../src/apps/api/app';
4import { MyController } from '../../../src/apps/api/controllers/my.controller';
5
6describe('MyController', () => {
7 let app: App;
8
9 beforeEach(async () => {
10 app = new App([new MyController()]);
11 await app.init();
12 });
13
14 describe('[POST] /v1/myendpoint', () => {
15 it('should return 200 with valid input', async () => {
16 return request(app.getServer())
17 .post('/v1/myendpoint')
18 .send({ data: 'valid' })
19 .expect(200)
20 .then((response) => {
21 expect(response.body).to.have.property('result');
22 });
23 });
24
25 it('should return 422 with invalid input', async () => {
26 return request(app.getServer())
27 .post('/v1/myendpoint')
28 .send({})
29 .expect(422);
30 });
31 });
32});Using Test Fixtures
1import path from 'path';
2import { load } from '@models/SpecificationFile';
3
4// Load test fixtures
5const fixturesPath = path.resolve(__dirname, '../../fixtures');
6
7async function loadTestSpec(filename: string) {
8 return load(path.join(fixturesPath, filename));
9}
10
11// Usage in tests
12describe('MyTest', () => {
13 it('should handle v3 spec', async () => {
14 const spec = await loadTestSpec('specification-v3.yml');
15 // ... test logic
16 });
17});Running Tests
Run All Tests
npm testRun Only CLI Tests
npm run cli:testRun Only Unit Tests
npm run unit:testRun a Single Test File
npm run test:one -- test/integration/validate.test.tsRun Tests with Coverage
1npm run cli:test
2# Coverage report is generated in ./coverage/Run Tests in Watch Mode (Development)
1# Using nodemon for file watching
2npm run dev
3
4# Then run tests manually when needed
5npm run unit:testCommon Issues & Solutions
Issue 1: "Cannot find module '@utils/proxy'"
Cause: TypeScript path aliases not resolved.
Solution:
1# Rebuild the project
2npm run buildIssue 2: Permission Denied Errors
Cause: Files created by root or different user.
Solution:
1# Fix permissions
2sudo chown -R $(whoami) ./lib ./node_modules ./.nyc_outputIssue 3: Test Context File Conflicts
Cause: Tests using the same context file as development.
Solution:
1# Set test-specific context file
2export CUSTOM_CONTEXT_FILENAME="test.asyncapi-cli"
3export CUSTOM_CONTEXT_FILE_LOCATION=""Issue 4: "ECONNREFUSED" in API Tests
Cause: API server not started or wrong port.
Solution:
1# Ensure the app is initialized in tests
2const app = new App([new MyController()]);
3await app.init(); // Don't forget this!Issue 5: Async Test Timeouts
Cause: Default timeout too short for async operations.
Solution:
1// Increase timeout for specific tests
2it('should handle slow operation', async function() {
3 this.timeout(30000); // 30 seconds
4 // ... test logic
5});Issue 6: ESLint Errors in Tests
Cause: Using testing patterns that trigger lint rules.
Solution:
1// Add eslint disable for specific patterns
2/* eslint-disable @typescript-eslint/no-unused-expressions */
3expect(result).to.be.true; // Chai assertionsDebugging Tools & Tips
1. Enable Verbose Logging
DEBUG=* ./bin/run validate spec.yml2. Use Node.js Inspector
1node --inspect-brk ./bin/run validate spec.yml
2# Open chrome://inspect in Chrome3. Print Stack Traces
1try {
2 // risky operation
3} catch (error) {
4 console.error('Stack trace:', error.stack);
5 throw error;
6}4. Use TypeScript Source Maps
Ensure tsconfig.json has:
1{
2 "compilerOptions": {
3 "sourceMap": true
4 }
5}5. Debug Parser Output
1import { Parser } from '@asyncapi/parser';
2
3const parser = new Parser();
4const { document, diagnostics } = await parser.parse(specContent);
5
6console.log('Parsed document:', JSON.stringify(document?.json(), null, 2));
7console.log('Diagnostics:', JSON.stringify(diagnostics, null, 2));6. Inspect Service Results
All services return a ServiceResult type:
1interface ServiceResult<T> {
2 success: boolean;
3 data?: T;
4 error?: string;
5}
6
7// Always check both success and data
8if (result.success && result.data) {
9 console.log('Success:', result.data);
10} else {
11 console.log('Error:', result.error);
12}7. Test Commands Interactively
1# Build and run immediately
2npm run build && ./bin/run validate ./test/fixtures/specification.ymlQuick Reference
| Task | Command |
|---|---|
| Build project | npm run build |
| Run all tests | npm test |
| Run CLI tests | npm run cli:test |
| Run unit tests | npm run unit:test |
| Run single test | npm run test:one -- <path> |
| Lint code | npm run lint |
| Fix lint issues | npm run lint:fix |
| Start API dev server | npm run api:dev |
| Debug CLI command | ./bin/run <command> <args> |
| Debug with inspector | node --inspect-brk ./bin/run <command> |
Getting Help
If you're still stuck:
- Check existing tests for similar functionality
- Look at the error messages and stack traces
- Search for similar issues in the GitHub Issues
- Ask in the AsyncAPI Slack
#toolingchannel