integration.test.tsā¢13 kB
#!/usr/bin/env node
/**
* Integration Test Script
*
* This script tests the complete ACI MCP server against either a real APIC
* or the mock server. It exercises all major tools and validates responses.
*/
import { ACIApiService } from '../src/services/aciApi.js';
import { loadConfig } from '../src/utils/config.js';
import { MockAPICServer } from './mock-server.js';
interface TestResult {
name: string;
passed: boolean;
error?: string;
duration: number;
}
class IntegrationTester {
private aciApi: ACIApiService;
private mockServer?: MockAPICServer;
private results: TestResult[] = [];
constructor(useMockServer: boolean = true) {
if (useMockServer) {
this.mockServer = new MockAPICServer(8443);
// Configure for mock server
const config = {
apicUrl: 'http://localhost:8443',
username: 'admin',
password: 'admin',
validateCerts: false,
timeout: 5000
};
this.aciApi = new ACIApiService(config);
} else {
// Use real APIC configuration
const config = loadConfig();
this.aciApi = new ACIApiService(config);
}
}
async runTests(): Promise<TestResult[]> {
console.log('š Starting ACI MCP Server Integration Tests\n');
if (this.mockServer) {
console.log('š” Starting mock APIC server...');
this.mockServer.start();
// Wait a moment for server to start
await this.sleep(1000);
}
try {
// Authentication tests
await this.runTest('Authentication', () => this.testAuthentication());
// Tenant management tests
await this.runTest('List Tenants', () => this.testListTenants());
await this.runTest('Get Tenant', () => this.testGetTenant());
await this.runTest('Create Tenant', () => this.testCreateTenant());
// Health monitoring tests
await this.runTest('Fabric Health', () => this.testFabricHealth());
await this.runTest('List Faults', () => this.testListFaults());
await this.runTest('List Faults by Severity', () => this.testListFaultsBySeverity());
// Infrastructure tests
await this.runTest('List Nodes', () => this.testListNodes());
await this.runTest('System Info', () => this.testSystemInfo());
// Configuration object tests
await this.runTest('List Application Profiles', () => this.testListApplicationProfiles());
await this.runTest('List Endpoint Groups', () => this.testListEndpointGroups());
await this.runTest('List Bridge Domains', () => this.testListBridgeDomains());
await this.runTest('List VRFs', () => this.testListVRFs());
await this.runTest('List Contracts', () => this.testListContracts());
} catch (error) {
console.error('ā Test suite failed:', error);
} finally {
if (this.mockServer) {
console.log('\nš Stopping mock APIC server...');
this.mockServer.stop();
}
}
this.printResults();
return this.results;
}
private async runTest(name: string, testFunction: () => Promise<void>): Promise<void> {
const startTime = Date.now();
try {
await testFunction();
const duration = Date.now() - startTime;
this.results.push({ name, passed: true, duration });
console.log(`ā
${name} (${duration}ms)`);
} catch (error) {
const duration = Date.now() - startTime;
const errorMsg = error instanceof Error ? error.message : String(error);
this.results.push({ name, passed: false, error: errorMsg, duration });
console.log(`ā ${name} (${duration}ms): ${errorMsg}`);
}
}
private async testAuthentication(): Promise<void> {
await this.aciApi.authenticate();
// If we get here without throwing, authentication succeeded
}
private async testListTenants(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (!tenants || !tenants.imdata || !Array.isArray(tenants.imdata)) {
throw new Error('Invalid tenants response format');
}
if (tenants.imdata.length === 0) {
console.log(' ā ļø No tenants found (this might be expected)');
} else {
console.log(` š Found ${tenants.imdata.length} tenants`);
}
}
private async testGetTenant(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const firstTenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (firstTenantName) {
const tenant = await this.aciApi.getTenant(firstTenantName);
if (!tenant) {
throw new Error(`Failed to get tenant ${firstTenantName}`);
}
console.log(` š¢ Retrieved tenant: ${firstTenantName}`);
} else {
throw new Error('No valid tenant found to test with');
}
} else {
console.log(' ā ļø No tenants to test with');
}
}
private async testCreateTenant(): Promise<void> {
const testTenantName = `test-tenant-${Date.now()}`;
try {
await this.aciApi.createTenant(testTenantName, 'Integration test tenant');
console.log(` š Created tenant: ${testTenantName}`);
// Clean up - try to delete the test tenant
try {
await this.aciApi.deleteTenant(testTenantName);
console.log(` šļø Cleaned up tenant: ${testTenantName}`);
} catch (cleanupError) {
console.log(` ā ļø Could not clean up tenant: ${testTenantName}`);
}
} catch (error) {
throw new Error(`Failed to create tenant: ${error}`);
}
}
private async testFabricHealth(): Promise<void> {
const health = await this.aciApi.getFabricHealth();
if (!health || !health.imdata || !Array.isArray(health.imdata)) {
throw new Error('Invalid fabric health response format');
}
if (health.imdata.length > 0) {
const healthScore = health.imdata[0].fabricHealthTotal?.attributes?.cur;
console.log(` š Fabric health score: ${healthScore}`);
} else {
console.log(' ā ļø No fabric health data available');
}
}
private async testListFaults(): Promise<void> {
const faults = await this.aciApi.listFaults();
if (!faults || !faults.imdata || !Array.isArray(faults.imdata)) {
throw new Error('Invalid faults response format');
}
console.log(` šØ Found ${faults.imdata.length} total faults`);
}
private async testListFaultsBySeverity(): Promise<void> {
const criticalFaults = await this.aciApi.listFaults('critical');
if (!criticalFaults || !criticalFaults.imdata || !Array.isArray(criticalFaults.imdata)) {
throw new Error('Invalid critical faults response format');
}
console.log(` š“ Found ${criticalFaults.imdata.length} critical faults`);
const warningFaults = await this.aciApi.listFaults('warning');
if (!warningFaults || !warningFaults.imdata || !Array.isArray(warningFaults.imdata)) {
throw new Error('Invalid warning faults response format');
}
console.log(` š” Found ${warningFaults.imdata.length} warning faults`);
}
private async testListNodes(): Promise<void> {
const nodes = await this.aciApi.listNodes();
if (!nodes || !nodes.imdata || !Array.isArray(nodes.imdata)) {
throw new Error('Invalid nodes response format');
}
console.log(` š„ļø Found ${nodes.imdata.length} fabric nodes`);
}
private async testSystemInfo(): Promise<void> {
const sysInfo = await this.aciApi.getSystemInfo();
if (!sysInfo) {
throw new Error('No system information returned');
}
const name = sysInfo.topSystem?.attributes?.name;
const version = sysInfo.topSystem?.attributes?.version;
console.log(` ā¹ļø System: ${name}, Version: ${version}`);
}
private async testListApplicationProfiles(): Promise<void> {
// Get first tenant to test with
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (tenantName) {
const aps = await this.aciApi.listApplicationProfiles(tenantName);
if (!aps || !aps.imdata || !Array.isArray(aps.imdata)) {
throw new Error('Invalid application profiles response format');
}
console.log(` š± Found ${aps.imdata.length} application profiles in ${tenantName}`);
}
} else {
console.log(' ā ļø No tenants available to test application profiles');
}
}
private async testListEndpointGroups(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (tenantName) {
const aps = await this.aciApi.listApplicationProfiles(tenantName);
if (aps.imdata && aps.imdata.length > 0) {
const apName = aps.imdata[0].fvAp?.attributes?.name;
if (apName) {
const epgs = await this.aciApi.listEndpointGroups(tenantName, apName);
if (!epgs || !epgs.imdata || !Array.isArray(epgs.imdata)) {
throw new Error('Invalid endpoint groups response format');
}
console.log(` šÆ Found ${epgs.imdata.length} EPGs in ${tenantName}/${apName}`);
return;
}
}
}
}
console.log(' ā ļø No application profiles available to test EPGs');
}
private async testListBridgeDomains(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (tenantName) {
const bds = await this.aciApi.listBridgeDomains(tenantName);
if (!bds || !bds.imdata || !Array.isArray(bds.imdata)) {
throw new Error('Invalid bridge domains response format');
}
console.log(` š Found ${bds.imdata.length} bridge domains in ${tenantName}`);
}
} else {
console.log(' ā ļø No tenants available to test bridge domains');
}
}
private async testListVRFs(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (tenantName) {
const vrfs = await this.aciApi.listVRFs(tenantName);
if (!vrfs || !vrfs.imdata || !Array.isArray(vrfs.imdata)) {
throw new Error('Invalid VRFs response format');
}
console.log(` š£ļø Found ${vrfs.imdata.length} VRFs in ${tenantName}`);
}
} else {
console.log(' ā ļø No tenants available to test VRFs');
}
}
private async testListContracts(): Promise<void> {
const tenants = await this.aciApi.listTenants();
if (tenants.imdata && tenants.imdata.length > 0) {
const tenantName = tenants.imdata[0].fvTenant?.attributes?.name;
if (tenantName) {
const contracts = await this.aciApi.listContracts(tenantName);
if (!contracts || !contracts.imdata || !Array.isArray(contracts.imdata)) {
throw new Error('Invalid contracts response format');
}
console.log(` š Found ${contracts.imdata.length} contracts in ${tenantName}`);
}
} else {
console.log(' ā ļø No tenants available to test contracts');
}
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
private printResults(): void {
console.log('\nš Test Results Summary:');
console.log('=' .repeat(50));
const passed = this.results.filter(r => r.passed).length;
const failed = this.results.filter(r => !r.passed).length;
const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
console.log(`ā
Passed: ${passed}`);
console.log(`ā Failed: ${failed}`);
console.log(`ā±ļø Total time: ${totalDuration}ms`);
console.log(`š Success rate: ${((passed / this.results.length) * 100).toFixed(1)}%`);
if (failed > 0) {
console.log('\nā Failed Tests:');
this.results
.filter(r => !r.passed)
.forEach(r => console.log(` - ${r.name}: ${r.error}`));
}
console.log('\n' + '=' .repeat(50));
}
}
// Run tests if this script is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
const useMock = process.argv.includes('--mock') || !process.env.ACI_APIC_URL;
console.log(`š§ Running tests with ${useMock ? 'Mock APIC Server' : 'Real APIC'}`);
const tester = new IntegrationTester(useMock);
tester.runTests()
.then((results) => {
const failed = results.filter(r => !r.passed).length;
process.exit(failed > 0 ? 1 : 0);
})
.catch((error) => {
console.error('š„ Test suite crashed:', error);
process.exit(1);
});
}
export { IntegrationTester };