Azure DevOps MCP Server
import { WebApi } from 'azure-devops-node-api';
import { AzureDevOpsClient } from '../../../src/api/client';
import { AzureDevOpsAuthenticationError, AzureDevOpsError } from '../../../src/api/errors';
import * as auth from '../../../src/api/auth';
// Mock the azure-devops-node-api module
jest.mock('azure-devops-node-api');
// Mock the auth module
jest.mock('../../../src/api/auth');
const MockWebApi = WebApi as jest.MockedClass<typeof WebApi>;
const mockCreateAuthenticatedClient = auth.createAuthenticatedClient as jest.MockedFunction<typeof auth.createAuthenticatedClient>;
describe('AzureDevOpsClient', () => {
const config = {
pat: 'validpat',
orgUrl: 'https://dev.azure.com/org'
};
let client: AzureDevOpsClient;
let mockWebApiInstance: any;
let mockGetCoreApi: jest.Mock;
let mockGetGitApi: jest.Mock;
let mockGetWorkItemTrackingApi: jest.Mock;
let mockGetBuildApi: jest.Mock;
let mockGetTestApi: jest.Mock;
let mockGetReleaseApi: jest.Mock;
let mockGetTaskAgentApi: jest.Mock;
let mockGetTaskApi: jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
// Setup mock API methods
mockGetCoreApi = jest.fn().mockResolvedValue({});
mockGetGitApi = jest.fn().mockResolvedValue({});
mockGetWorkItemTrackingApi = jest.fn().mockResolvedValue({});
mockGetBuildApi = jest.fn().mockResolvedValue({});
mockGetTestApi = jest.fn().mockResolvedValue({});
mockGetReleaseApi = jest.fn().mockResolvedValue({});
mockGetTaskAgentApi = jest.fn().mockResolvedValue({});
mockGetTaskApi = jest.fn().mockResolvedValue({});
mockWebApiInstance = {
getCoreApi: mockGetCoreApi,
getGitApi: mockGetGitApi,
getWorkItemTrackingApi: mockGetWorkItemTrackingApi,
getBuildApi: mockGetBuildApi,
getTestApi: mockGetTestApi,
getReleaseApi: mockGetReleaseApi,
getTaskAgentApi: mockGetTaskAgentApi,
getTaskApi: mockGetTaskApi
};
MockWebApi.mockImplementation(() => mockWebApiInstance);
// Mock the createAuthenticatedClient function to return our mockWebApiInstance
mockCreateAuthenticatedClient.mockResolvedValue(mockWebApiInstance);
client = new AzureDevOpsClient(config);
});
describe('getClient', () => {
it('should create WebApi client only once', async () => {
await client.getCoreApi();
await client.getCoreApi();
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
expect(mockCreateAuthenticatedClient).toHaveBeenCalledWith(config);
});
it('should re-use the cached client for multiple API calls', async () => {
await client.getCoreApi();
await client.getGitApi();
await client.getWorkItemTrackingApi();
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
it('should handle authentication errors when creating client', async () => {
mockCreateAuthenticatedClient.mockRejectedValueOnce(new AzureDevOpsAuthenticationError('Authentication failed'));
await expect(client.getCoreApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
it('should handle generic errors when creating client', async () => {
mockCreateAuthenticatedClient.mockRejectedValueOnce(new Error('Network error'));
await expect(client.getCoreApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
it('should handle non-Error instances when creating client', async () => {
mockCreateAuthenticatedClient.mockRejectedValueOnce('String error');
await expect(client.getCoreApi()).rejects.toThrow('Authentication failed: Unknown error');
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
});
describe('API client getters', () => {
it('should get Core API client', async () => {
const coreApi = await client.getCoreApi();
expect(coreApi).toBeDefined();
expect(mockGetCoreApi).toHaveBeenCalledTimes(1);
});
it('should get Git API client', async () => {
const gitApi = await client.getGitApi();
expect(gitApi).toBeDefined();
expect(mockGetGitApi).toHaveBeenCalledTimes(1);
});
it('should get Work Item Tracking API client', async () => {
const witApi = await client.getWorkItemTrackingApi();
expect(witApi).toBeDefined();
expect(mockGetWorkItemTrackingApi).toHaveBeenCalledTimes(1);
});
it('should get Build API client', async () => {
const buildApi = await client.getBuildApi();
expect(buildApi).toBeDefined();
expect(mockGetBuildApi).toHaveBeenCalledTimes(1);
});
it('should get Test API client', async () => {
const testApi = await client.getTestApi();
expect(testApi).toBeDefined();
expect(mockGetTestApi).toHaveBeenCalledTimes(1);
});
it('should get Release API client', async () => {
const releaseApi = await client.getReleaseApi();
expect(releaseApi).toBeDefined();
expect(mockGetReleaseApi).toHaveBeenCalledTimes(1);
});
it('should get Task Agent API client', async () => {
const taskAgentApi = await client.getTaskAgentApi();
expect(taskAgentApi).toBeDefined();
expect(mockGetTaskAgentApi).toHaveBeenCalledTimes(1);
});
it('should get Task API client', async () => {
const taskApi = await client.getTaskApi();
expect(taskApi).toBeDefined();
expect(mockGetTaskApi).toHaveBeenCalledTimes(1);
});
it('should throw authentication error if API call fails', async () => {
mockGetCoreApi.mockRejectedValue(new Error('API Error'));
await expect(client.getCoreApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
});
it('should rethrow AzureDevOpsError if API call fails with that error type', async () => {
const apiError = new AzureDevOpsError('API Error');
mockGetCoreApi.mockRejectedValue(apiError);
await expect(client.getCoreApi()).rejects.toThrow(apiError);
});
it('should handle error in getGitApi', async () => {
mockGetGitApi.mockRejectedValue(new Error('Git API Error'));
await expect(client.getGitApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getGitApi()).rejects.toThrow('Failed to get Git API');
});
it('should handle non-Error instance in getGitApi', async () => {
mockGetGitApi.mockRejectedValue('String error');
await expect(client.getGitApi()).rejects.toThrow('Failed to get Git API: Unknown error');
});
it('should handle error in getWorkItemTrackingApi', async () => {
mockGetWorkItemTrackingApi.mockRejectedValue(new Error('Work Item API Error'));
await expect(client.getWorkItemTrackingApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getWorkItemTrackingApi()).rejects.toThrow('Failed to get Work Item Tracking API');
});
it('should handle non-Error instance in getWorkItemTrackingApi', async () => {
mockGetWorkItemTrackingApi.mockRejectedValue('String error');
await expect(client.getWorkItemTrackingApi()).rejects.toThrow('Failed to get Work Item Tracking API: Unknown error');
});
it('should handle error in getBuildApi', async () => {
mockGetBuildApi.mockRejectedValue(new Error('Build API Error'));
await expect(client.getBuildApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getBuildApi()).rejects.toThrow('Failed to get Build API');
});
it('should handle non-Error instance in getBuildApi', async () => {
mockGetBuildApi.mockRejectedValue('String error');
await expect(client.getBuildApi()).rejects.toThrow('Failed to get Build API: Unknown error');
});
it('should handle error in getTestApi', async () => {
mockGetTestApi.mockRejectedValue(new Error('Test API Error'));
await expect(client.getTestApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getTestApi()).rejects.toThrow('Failed to get Test API');
});
it('should handle non-Error instance in getTestApi', async () => {
mockGetTestApi.mockRejectedValue('String error');
await expect(client.getTestApi()).rejects.toThrow('Failed to get Test API: Unknown error');
});
it('should handle error in getReleaseApi', async () => {
mockGetReleaseApi.mockRejectedValue(new Error('Release API Error'));
await expect(client.getReleaseApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getReleaseApi()).rejects.toThrow('Failed to get Release API');
});
it('should handle non-Error instance in getReleaseApi', async () => {
mockGetReleaseApi.mockRejectedValue('String error');
await expect(client.getReleaseApi()).rejects.toThrow('Failed to get Release API: Unknown error');
});
it('should handle error in getTaskAgentApi', async () => {
mockGetTaskAgentApi.mockRejectedValue(new Error('Task Agent API Error'));
await expect(client.getTaskAgentApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getTaskAgentApi()).rejects.toThrow('Failed to get Task Agent API');
});
it('should handle non-Error instance in getTaskAgentApi', async () => {
mockGetTaskAgentApi.mockRejectedValue('String error');
await expect(client.getTaskAgentApi()).rejects.toThrow('Failed to get Task Agent API: Unknown error');
});
it('should handle error in getTaskApi', async () => {
mockGetTaskApi.mockRejectedValue(new Error('Task API Error'));
await expect(client.getTaskApi()).rejects.toThrow(AzureDevOpsAuthenticationError);
await expect(client.getTaskApi()).rejects.toThrow('Failed to get Task API');
});
it('should handle non-Error instance in getTaskApi', async () => {
mockGetTaskApi.mockRejectedValue('String error');
await expect(client.getTaskApi()).rejects.toThrow('Failed to get Task API: Unknown error');
});
});
describe('isAuthenticated', () => {
it('should return true if Core API is accessible', async () => {
const isAuth = await client.isAuthenticated();
expect(isAuth).toBe(true);
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
it('should return false if Core API is not accessible', async () => {
mockCreateAuthenticatedClient.mockRejectedValueOnce(new Error('API Error'));
const isAuth = await client.isAuthenticated();
expect(isAuth).toBe(false);
expect(mockCreateAuthenticatedClient).toHaveBeenCalledTimes(1);
});
it('should return false for any error type in isAuthenticated', async () => {
mockCreateAuthenticatedClient.mockRejectedValueOnce(new AzureDevOpsError('Auth Error'));
const isAuth = await client.isAuthenticated();
expect(isAuth).toBe(false);
});
});
});