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 handlers5│ │ ├── middlewares/ # Request/response middleware6│ │ └── exceptions/ # API error types7│ └── cli/ # CLI application (oclif)8│ ├── commands/ # CLI command implementations9│ └── internal/ # Shared CLI utilities, flags, base classes10├── domains/11│ ├── models/ # Domain models (SpecificationFile, Context, etc.)12│ └── services/ # Business logic services13├── errors/ # Custom error classes14├── utils/ # Utility functions15└── interfaces/ # TypeScript interfaces1617test/18├── fixtures/ # Test data files (AsyncAPI specs, etc.)19├── helpers/ # Test utilities20├── integration/ # Integration tests for CLI commands21└── unit/ # Unit tests for services and controllers22 ├── controllers/ # API controller tests23 ├── services/ # Service layer tests24 └── utils/ # Utility function tests
Setting Up Your Environment
1. Install Dependencies
npm install
2. Build the Project
npm run build
3. Set Up Environment Variables for Debugging
Create a .env file or export variables:
1# Enable development mode (verbose logging)2export NODE_ENV=development34# Disable analytics during testing5export TEST=167# Set custom context file for testing8export 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 debugging2./bin/run validate ./path/to/asyncapi.yml34# With verbose output5DEBUG=* ./bin/run validate ./path/to/asyncapi.yml
Method 2: Using Node.js Inspector
1# Start with Node inspector2node --inspect-brk ./bin/run validate ./path/to/asyncapi.yml34# Then open Chrome DevTools at: chrome://inspect
Method 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": true34 }35 ]36}
Method 4: Adding Console Logs
For quick debugging, add logs in command files:
1// In src/apps/cli/commands/validate.ts2async run() {3 const { args, flags } = await this.parse(Validate);45 // Debug: Log parsed arguments6 console.log('DEBUG - Args:', JSON.stringify(args, null, 2));7 console.log('DEBUG - Flags:', JSON.stringify(flags, null, 2));89 // ... rest of the command10}
Debugging the API Server
Starting the API in Development Mode
1# Start with hot-reload2npm run api:dev34# Or manually with debugging5NODE_ENV=development DEBUG=* node ./lib/apps/api/server.js
Testing API Endpoints
1# Validate endpoint2curl -X POST http://localhost:3000/v1/validate \3 -H "Content-Type: application/json" \4 -d '{"asyncapi": "asyncapi: 3.1.0\ninfo:\n title: Test\n version: 1.0.0\nchannels: {}"}'56# Parse endpoint7curl -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.ts2// Logs are automatically enabled in development mode
Debugging Services
ValidationService
The ValidationService handles document validation. To debug:
1// Test the service directly2import { ValidationService } from '@services/validation.service';3import { load } from '@models/SpecificationFile';45async function debugValidation() {6 const service = new ValidationService();7 const specFile = await load('./path/to/spec.yml');89 const result = await service.validateDocument(specFile, {10 'fail-severity': 'error',11 'log-diagnostics': true,12 });1314 console.log('Validation Result:', JSON.stringify(result, null, 2));15}
ConversionService
1import { ConversionService } from '@services/convert.service';2import { load } from '@models/SpecificationFile';34async function debugConversion() {5 const service = new ConversionService();6 const specFile = await load('./path/to/spec.yml');78 const result = await service.convertDocument(specFile, {9 format: 'asyncapi',10 'target-version': '3.0.0',11 perspective: 'server',12 });1314 console.log('Conversion Result:', JSON.stringify(result, null, 2));15}
GeneratorService
1import { GeneratorService } from '@services/generator.service';23async function debugGenerator() {4 const service = new GeneratorService();5 // Add your debugging logic6}
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.ts2import { expect } from 'chai';3import { MyService } from '../../../src/domains/services/my.service';45describe('MyService', () => {6 let service: MyService;78 beforeEach(() => {9 service = new MyService();10 });1112 describe('methodName', () => {13 it('should do something when given valid input', async () => {14 // Arrange15 const input = { /* test data */ };1617 // Act18 const result = await service.methodName(input);1920 // Assert21 expect(result.success).to.be.true;22 expect(result.data).to.exist;23 });2425 it('should handle errors gracefully', async () => {26 // Arrange27 const invalidInput = null;2829 // Act30 const result = await service.methodName(invalidInput);3132 // Assert33 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.ts2import { expect, test } from '@oclif/test';3import path from 'path';45const validSpec = path.resolve(__dirname, '../fixtures/specification.yml');6const invalidSpec = path.resolve(__dirname, '../fixtures/specification-invalid.yml');78describe('mycommand', () => {9 describe('with valid input', () => {10 test11 .stdout()12 .command(['mycommand', validSpec])13 .it('should succeed with valid specification', (ctx) => {14 expect(ctx.stdout).to.contain('Success');15 });16 });1718 describe('with invalid input', () => {19 test20 .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 });2728 describe('with flags', () => {29 test30 .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.ts2import request from 'supertest';3import { App } from '../../../src/apps/api/app';4import { MyController } from '../../../src/apps/api/controllers/my.controller';56describe('MyController', () => {7 let app: App;89 beforeEach(async () => {10 app = new App([new MyController()]);11 await app.init();12 });1314 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 });2425 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';34// Load test fixtures5const fixturesPath = path.resolve(__dirname, '../../fixtures');67async function loadTestSpec(filename: string) {8 return load(path.join(fixturesPath, filename));9}1011// Usage in tests12describe('MyTest', () => {13 it('should handle v3 spec', async () => {14 const spec = await loadTestSpec('specification-v3.yml');15 // ... test logic16 });17});
Running Tests
Run All Tests
npm test
Run Only CLI Tests
npm run cli:test
Run Only Unit Tests
npm run unit:test
Run a Single Test File
npm run test:one -- test/integration/validate.test.ts
Run Tests with Coverage
1npm run cli:test2# Coverage report is generated in ./coverage/
Run Tests in Watch Mode (Development)
1# Using nodemon for file watching2npm run dev34# Then run tests manually when needed5npm run unit:test
Common Issues & Solutions
Issue 1: "Cannot find module '@utils/proxy'"
Cause: TypeScript path aliases not resolved.
Solution:
1# Rebuild the project2npm run build
Issue 2: Permission Denied Errors
Cause: Files created by root or different user.
Solution:
1# Fix permissions2sudo chown -R $(whoami) ./lib ./node_modules ./.nyc_output
Issue 3: Test Context File Conflicts
Cause: Tests using the same context file as development.
Solution:
1# Set test-specific context file2export 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 tests2const 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 tests2it('should handle slow operation', async function() {3 this.timeout(30000); // 30 seconds4 // ... test logic5});
Issue 6: ESLint Errors in Tests
Cause: Using testing patterns that trigger lint rules.
Solution:
1// Add eslint disable for specific patterns2/* eslint-disable @typescript-eslint/no-unused-expressions */3expect(result).to.be.true; // Chai assertions
Debugging Tools & Tips
1. Enable Verbose Logging
DEBUG=* ./bin/run validate spec.yml
2. Use Node.js Inspector
1node --inspect-brk ./bin/run validate spec.yml2# Open chrome://inspect in Chrome
3. Print Stack Traces
1try {2 // risky operation3} 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": true4 }5}
5. Debug Parser Output
1import { Parser } from '@asyncapi/parser';23const parser = new Parser();4const { document, diagnostics } = await parser.parse(specContent);56console.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}67// Always check both success and data8if (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 immediately2npm run build && ./bin/run validate ./test/fixtures/specification.yml
Quick 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