aciApi.test.ts•9.29 kB
import { describe, test, expect, beforeEach, jest } from '@jest/globals';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { ACIApiService, ACIConfig } from '../src/services/aciApi.js';
describe('ACIApiService', () => {
let aciApi: ACIApiService;
let mockAxios: MockAdapter;
let config: ACIConfig;
beforeEach(() => {
config = {
apicUrl: 'https://test-apic.example.com',
username: 'testuser',
password: 'testpass',
validateCerts: false,
timeout: 10000
};
aciApi = new ACIApiService(config);
mockAxios = new MockAdapter(axios);
});
afterEach(() => {
mockAxios.restore();
});
describe('Authentication', () => {
test('should authenticate with password successfully', async () => {
const loginResponse = {
imdata: [{
aaaLogin: {
attributes: {
token: 'test-token-123',
sessionTimeoutSeconds: '600'
}
}
}]
};
mockAxios.onPost('/api/aaaLogin.json').reply(200, loginResponse);
await aciApi.authenticate();
expect(mockAxios.history.post).toHaveLength(1);
expect(mockAxios.history.post[0].url).toBe('/api/aaaLogin.json');
const requestData = JSON.parse(mockAxios.history.post[0].data);
expect(requestData.aaaUser.attributes.name).toBe('testuser');
expect(requestData.aaaUser.attributes.pwd).toBe('testpass');
});
test('should handle authentication failure', async () => {
mockAxios.onPost('/api/aaaLogin.json').reply(401, {
imdata: [{
error: {
attributes: {
code: '401',
text: 'Authentication failed'
}
}
}]
});
await expect(aciApi.authenticate()).rejects.toThrow();
});
test('should handle certificate authentication', async () => {
const certConfig: ACIConfig = {
apicUrl: 'https://test-apic.example.com',
username: 'testuser',
certificateName: 'test-cert',
privateKey: 'test-private-key',
validateCerts: false,
timeout: 10000
};
const certAciApi = new ACIApiService(certConfig);
const refreshResponse = {
imdata: [{
aaaLogin: {
attributes: {
token: 'cert-token-123',
sessionTimeoutSeconds: '600'
}
}
}]
};
mockAxios.onGet('/api/aaaRefresh.json').reply(200, refreshResponse);
await certAciApi.authenticate();
expect(mockAxios.history.get).toHaveLength(1);
expect(mockAxios.history.get[0].url).toBe('/api/aaaRefresh.json');
expect(mockAxios.history.get[0].headers?.['X-ACI-Certificate']).toBe('test-cert');
});
});
describe('Tenant Operations', () => {
beforeEach(async () => {
// Mock successful authentication
const loginResponse = {
imdata: [{
aaaLogin: {
attributes: {
token: 'test-token-123',
sessionTimeoutSeconds: '600'
}
}
}]
};
mockAxios.onPost('/api/aaaLogin.json').reply(200, loginResponse);
await aciApi.authenticate();
});
test('should list tenants successfully', async () => {
const tenantsResponse = {
imdata: [
{
fvTenant: {
attributes: {
name: 'tenant1',
dn: 'uni/tn-tenant1'
}
}
},
{
fvTenant: {
attributes: {
name: 'tenant2',
dn: 'uni/tn-tenant2'
}
}
}
]
};
mockAxios.onGet('/api/node/class/fvTenant.json').reply(200, tenantsResponse);
const tenants = await aciApi.listTenants();
expect(tenants).toEqual(tenantsResponse);
expect(mockAxios.history.get).toHaveLength(1);
expect(mockAxios.history.get[0].url).toBe('/api/node/class/fvTenant.json');
});
test('should get specific tenant', async () => {
const tenantResponse = {
imdata: [{
fvTenant: {
attributes: {
name: 'test-tenant',
dn: 'uni/tn-test-tenant'
}
}
}]
};
mockAxios.onGet('/api/node/class/fvTenant.json').reply(200, tenantResponse);
const tenant = await aciApi.getTenant('test-tenant');
expect(tenant).toEqual(tenantResponse.imdata[0]);
});
test('should create tenant successfully', async () => {
const createResponse = {
imdata: []
};
mockAxios.onPost('/api/node/mo/uni.json').reply(200, createResponse);
await aciApi.createTenant('new-tenant', 'Test tenant description');
expect(mockAxios.history.post).toHaveLength(2); // 1 for auth, 1 for create
const createRequest = JSON.parse(mockAxios.history.post[1].data);
expect(createRequest.fvTenant.attributes.name).toBe('new-tenant');
expect(createRequest.fvTenant.attributes.descr).toBe('Test tenant description');
});
test('should delete tenant successfully', async () => {
mockAxios.onDelete('/api/node/mo/uni/tn-test-tenant.json').reply(200, { imdata: [] });
await aciApi.deleteTenant('test-tenant');
expect(mockAxios.history.delete).toHaveLength(1);
expect(mockAxios.history.delete[0].url).toBe('/api/node/mo/uni/tn-test-tenant.json');
});
});
describe('Health Monitoring', () => {
beforeEach(async () => {
// Mock successful authentication
const loginResponse = {
imdata: [{
aaaLogin: {
attributes: {
token: 'test-token-123',
sessionTimeoutSeconds: '600'
}
}
}]
};
mockAxios.onPost('/api/aaaLogin.json').reply(200, loginResponse);
await aciApi.authenticate();
});
test('should get fabric health', async () => {
const healthResponse = {
imdata: [{
fabricHealthTotal: {
attributes: {
cur: '95',
dn: 'topology/health'
}
}
}]
};
mockAxios.onGet('/api/node/class/fabricHealthTotal.json').reply(200, healthResponse);
const health = await aciApi.getFabricHealth();
expect(health).toEqual(healthResponse);
expect(mockAxios.history.get).toHaveLength(1);
expect(mockAxios.history.get[0].url).toBe('/api/node/class/fabricHealthTotal.json');
});
test('should list faults with severity filter', async () => {
const faultsResponse = {
imdata: [{
faultInst: {
attributes: {
severity: 'critical',
descr: 'Test critical fault',
dn: 'topology/pod-1/node-101/fault-123'
}
}
}]
};
mockAxios.onGet('/api/node/class/faultInst.json?query-target-filter=eq(faultInst.severity,"critical")')
.reply(200, faultsResponse);
const faults = await aciApi.listFaults('critical');
expect(faults).toEqual(faultsResponse);
expect(mockAxios.history.get).toHaveLength(1);
});
test('should list all faults when no severity specified', async () => {
const faultsResponse = {
imdata: [
{
faultInst: {
attributes: {
severity: 'critical',
descr: 'Critical fault'
}
}
},
{
faultInst: {
attributes: {
severity: 'warning',
descr: 'Warning fault'
}
}
}
]
};
mockAxios.onGet('/api/node/class/faultInst.json').reply(200, faultsResponse);
const faults = await aciApi.listFaults();
expect(faults).toEqual(faultsResponse);
expect(mockAxios.history.get).toHaveLength(1);
expect(mockAxios.history.get[0].url).toBe('/api/node/class/faultInst.json');
});
});
describe('Configuration Management', () => {
test('should handle missing configuration gracefully', () => {
expect(() => {
new ACIApiService({} as ACIConfig);
}).not.toThrow();
});
test('should apply default configuration values', () => {
const minimalConfig = {
apicUrl: 'https://test.com',
username: 'user'
};
const api = new ACIApiService(minimalConfig);
// Check that defaults are applied via the constructor
expect(api).toBeDefined();
});
});
describe('Error Handling', () => {
test('should handle network errors gracefully', async () => {
mockAxios.onPost('/api/aaaLogin.json').networkError();
await expect(aciApi.authenticate()).rejects.toThrow();
});
test('should handle malformed responses', async () => {
mockAxios.onPost('/api/aaaLogin.json').reply(200, 'invalid json');
await expect(aciApi.authenticate()).rejects.toThrow();
});
test('should handle APIC errors', async () => {
mockAxios.onPost('/api/aaaLogin.json').reply(500, {
error: 'Internal server error'
});
await expect(aciApi.authenticate()).rejects.toThrow();
});
});
});