manual-test.tsโข10.8 kB
#!/usr/bin/env node
/**
* Manual Testing Script for ACI MCP Server
*
* This script provides an interactive way to test individual MCP tools
* and see their responses. Perfect for manual testing and debugging.
*/
import readline from 'readline';
import { ACIApiService } from '../src/services/aciApi.js';
import { loadConfig } from '../src/utils/config.js';
import { MockAPICServer } from './mock-server.js';
interface ToolTest {
name: string;
description: string;
args?: any;
handler: () => Promise<any>;
}
class ManualTester {
private aciApi: ACIApiService;
private mockServer?: MockAPICServer;
private rl: readline.Interface;
private tools: ToolTest[];
constructor(useMockServer: boolean = true) {
if (useMockServer) {
this.mockServer = new MockAPICServer(8443);
const config = {
apicUrl: 'http://localhost:8443',
username: 'admin',
password: 'admin',
validateCerts: false,
timeout: 5000
};
this.aciApi = new ACIApiService(config);
} else {
const config = loadConfig();
this.aciApi = new ACIApiService(config);
}
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
this.tools = this.initializeTools();
}
private initializeTools(): ToolTest[] {
return [
{
name: 'auth',
description: 'Test authentication',
handler: () => this.aciApi.authenticate()
},
{
name: 'tenants',
description: 'List all tenants',
handler: () => this.aciApi.listTenants()
},
{
name: 'health',
description: 'Get fabric health',
handler: () => this.aciApi.getFabricHealth()
},
{
name: 'faults',
description: 'List all faults',
handler: () => this.aciApi.listFaults()
},
{
name: 'critical-faults',
description: 'List critical faults only',
handler: () => this.aciApi.listFaults('critical')
},
{
name: 'nodes',
description: 'List fabric nodes',
handler: () => this.aciApi.listNodes()
},
{
name: 'system',
description: 'Get system information',
handler: () => this.aciApi.getSystemInfo()
},
{
name: 'create-tenant',
description: 'Create a test tenant',
handler: () => this.createTestTenant()
},
{
name: 'apps',
description: 'List application profiles (requires tenant)',
handler: () => this.listApplicationProfiles()
},
{
name: 'epgs',
description: 'List endpoint groups (requires tenant and app profile)',
handler: () => this.listEndpointGroups()
},
{
name: 'bridge-domains',
description: 'List bridge domains (requires tenant)',
handler: () => this.listBridgeDomains()
},
{
name: 'vrfs',
description: 'List VRFs (requires tenant)',
handler: () => this.listVRFs()
},
{
name: 'contracts',
description: 'List contracts (requires tenant)',
handler: () => this.listContracts()
}
];
}
async start(): Promise<void> {
console.log('๐งช ACI MCP Server Manual Testing Tool\n');
if (this.mockServer) {
console.log('๐ก Starting mock APIC server...');
this.mockServer.start();
await this.sleep(1000);
console.log('โ
Mock server ready!\n');
}
try {
console.log('๐ Testing authentication...');
await this.aciApi.authenticate();
console.log('โ
Authentication successful!\n');
} catch (error) {
console.log(`โ Authentication failed: ${error}\n`);
}
this.showMenu();
this.startInteractiveSession();
}
private showMenu(): void {
console.log('๐ Available Tests:');
console.log('=' .repeat(50));
this.tools.forEach((tool, index) => {
console.log(`${index + 1}. ${tool.name.padEnd(20)} - ${tool.description}`);
});
console.log('\n๐๏ธ Special Commands:');
console.log('menu - Show this menu');
console.log('clear - Clear screen');
console.log('quit / exit - Exit the tool');
console.log('');
}
private async startInteractiveSession(): Promise<void> {
return new Promise((resolve) => {
const prompt = () => {
this.rl.question('๐ธ Enter test name or number: ', async (input) => {
const trimmed = input.trim().toLowerCase();
if (trimmed === 'quit' || trimmed === 'exit') {
console.log('๐ Goodbye!');
this.cleanup();
resolve();
return;
}
if (trimmed === 'menu') {
this.showMenu();
prompt();
return;
}
if (trimmed === 'clear') {
console.clear();
this.showMenu();
prompt();
return;
}
if (trimmed === '') {
prompt();
return;
}
await this.executeTest(trimmed);
prompt();
});
};
prompt();
});
}
private async executeTest(input: string): Promise<void> {
let tool: ToolTest | undefined;
// Try to find by number
const num = parseInt(input);
if (!isNaN(num) && num >= 1 && num <= this.tools.length) {
tool = this.tools[num - 1];
} else {
// Try to find by name
tool = this.tools.find(t => t.name === input || t.name.includes(input));
}
if (!tool) {
console.log(`โ Test '${input}' not found. Type 'menu' to see available tests.\n`);
return;
}
console.log(`\n๐ Running: ${tool.name} - ${tool.description}`);
console.log('โ'.repeat(50));
const startTime = Date.now();
try {
const result = await tool.handler();
const duration = Date.now() - startTime;
console.log('โ
Success!');
console.log(`โฑ๏ธ Duration: ${duration}ms`);
if (result) {
console.log('๐ Response:');
if (typeof result === 'string') {
console.log(result);
} else {
console.log(JSON.stringify(result, null, 2));
}
}
} catch (error) {
const duration = Date.now() - startTime;
console.log('โ Failed!');
console.log(`โฑ๏ธ Duration: ${duration}ms`);
console.log(`๐ฅ Error: ${error}`);
}
console.log('โ'.repeat(50));
console.log('');
}
// Helper methods for complex tests
private async createTestTenant(): Promise<any> {
const tenantName = `test-tenant-${Date.now()}`;
console.log(`Creating tenant: ${tenantName}`);
const result = await this.aciApi.createTenant(tenantName, 'Manual test tenant');
// Schedule cleanup
setTimeout(async () => {
try {
await this.aciApi.deleteTenant(tenantName);
console.log(`๐๏ธ Auto-cleaned up tenant: ${tenantName}`);
} catch (error) {
console.log(`โ ๏ธ Could not clean up tenant: ${tenantName}`);
}
}, 10000); // Clean up after 10 seconds
return result;
}
private async listApplicationProfiles(): Promise<any> {
const tenants = await this.aciApi.listTenants();
if (!tenants.imdata || tenants.imdata.length === 0) {
throw new Error('No tenants found. Create a tenant first.');
}
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (!tenantName) {
throw new Error('Invalid tenant data');
}
console.log(`Using tenant: ${tenantName}`);
return await this.aciApi.listApplicationProfiles(tenantName);
}
private async listEndpointGroups(): Promise<any> {
const tenants = await this.aciApi.listTenants();
if (!tenants.imdata || tenants.imdata.length === 0) {
throw new Error('No tenants found');
}
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (!tenantName) {
throw new Error('Invalid tenant data');
}
const aps = await this.aciApi.listApplicationProfiles(tenantName);
if (!aps.imdata || aps.imdata.length === 0) {
throw new Error(`No application profiles found in tenant ${tenantName}`);
}
const apName = aps.imdata[0].fvAp?.attributes?.name;
if (!apName) {
throw new Error('Invalid application profile data');
}
console.log(`Using tenant: ${tenantName}, app profile: ${apName}`);
return await this.aciApi.listEndpointGroups(tenantName, apName);
}
private async listBridgeDomains(): Promise<any> {
const tenants = await this.aciApi.listTenants();
if (!tenants.imdata || tenants.imdata.length === 0) {
throw new Error('No tenants found');
}
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (!tenantName) {
throw new Error('Invalid tenant data');
}
console.log(`Using tenant: ${tenantName}`);
return await this.aciApi.listBridgeDomains(tenantName);
}
private async listVRFs(): Promise<any> {
const tenants = await this.aciApi.listTenants();
if (!tenants.imdata || tenants.imdata.length === 0) {
throw new Error('No tenants found');
}
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (!tenantName) {
throw new Error('Invalid tenant data');
}
console.log(`Using tenant: ${tenantName}`);
return await this.aciApi.listVRFs(tenantName);
}
private async listContracts(): Promise<any> {
const tenants = await this.aciApi.listTenants();
if (!tenants.imdata || tenants.imdata.length === 0) {
throw new Error('No tenants found');
}
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (!tenantName) {
throw new Error('Invalid tenant data');
}
console.log(`Using tenant: ${tenantName}`);
return await this.aciApi.listContracts(tenantName);
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private cleanup(): void {
this.rl.close();
if (this.mockServer) {
this.mockServer.stop();
}
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
const useMock = process.argv.includes('--mock') || !process.env.ACI_APIC_URL;
console.log(`๐ง Manual testing with ${useMock ? 'Mock APIC Server' : 'Real APIC'}`);
const tester = new ManualTester(useMock);
tester.start().catch((error) => {
console.error('๐ฅ Manual tester crashed:', error);
process.exit(1);
});
// Handle Ctrl+C
process.on('SIGINT', () => {
console.log('\n\n๐ Received SIGINT, exiting gracefully...');
process.exit(0);
});
}
export { ManualTester };