Skip to main content
Glama

Azure DevOps MCP Server

feature.spec.int.ts7.35 kB
import { WebApi } from 'azure-devops-node-api'; import { getWorkItem } from './feature'; import { getTestConnection, shouldSkipIntegrationTest, } from '../__test__/test-helpers'; import { AzureDevOpsResourceNotFoundError } from '../../../shared/errors'; import { createWorkItem } from '../create-work-item/feature'; import { manageWorkItemLink } from '../manage-work-item-link/feature'; import { CreateWorkItemOptions } from '../types'; describe('getWorkItem integration', () => { let connection: WebApi | null = null; let testWorkItemId: number | null = null; let linkedWorkItemId: number | null = null; let projectName: string; beforeAll(async () => { // Get a real connection using environment variables connection = await getTestConnection(); projectName = process.env.AZURE_DEVOPS_DEFAULT_PROJECT || 'DefaultProject'; // Skip setup if integration tests should be skipped if (shouldSkipIntegrationTest() || !connection) { return; } try { // Create a test work item const uniqueTitle = `Test Work Item ${new Date().toISOString()}`; const options: CreateWorkItemOptions = { title: uniqueTitle, description: 'Test work item for get-work-item integration tests', }; const testWorkItem = await createWorkItem( connection, projectName, 'Task', options, ); // Create another work item to link to the first one const linkedItemOptions: CreateWorkItemOptions = { title: `Linked Work Item ${new Date().toISOString()}`, description: 'Linked work item for get-work-item integration tests', }; const linkedWorkItem = await createWorkItem( connection, projectName, 'Task', linkedItemOptions, ); if (testWorkItem?.id && linkedWorkItem?.id) { testWorkItemId = testWorkItem.id; linkedWorkItemId = linkedWorkItem.id; // Create a link between the two work items await manageWorkItemLink(connection, projectName, { sourceWorkItemId: testWorkItemId, targetWorkItemId: linkedWorkItemId, operation: 'add', relationType: 'System.LinkTypes.Related', comment: 'Link created for get-work-item integration tests', }); } } catch (error) { console.error('Failed to create test work items:', error); } }); test('should retrieve a real work item from Azure DevOps with default expand=all', async () => { // Skip if no connection is available if (shouldSkipIntegrationTest() || !connection || !testWorkItemId) { return; } // Act - get work item by ID const result = await getWorkItem(connection, testWorkItemId); // Assert expect(result).toBeDefined(); expect(result.id).toBe(testWorkItemId); // Verify expanded fields and data are present expect(result.fields).toBeDefined(); expect(result._links).toBeDefined(); // With expand=all and a linked item, relations should be defined expect(result.relations).toBeDefined(); if (result.fields) { // Verify common fields that should be present with expand=all expect(result.fields['System.Title']).toBeDefined(); expect(result.fields['System.State']).toBeDefined(); expect(result.fields['System.CreatedDate']).toBeDefined(); expect(result.fields['System.ChangedDate']).toBeDefined(); } }); test('should retrieve work item with expanded relations', async () => { // Skip if no connection is available if (shouldSkipIntegrationTest() || !connection || !testWorkItemId) { return; } // Act - get work item with relations expansion const result = await getWorkItem(connection, testWorkItemId, 'relations'); // Assert expect(result).toBeDefined(); expect(result.id).toBe(testWorkItemId); // When using expand=relations on a work item with links, relations should be defined expect(result.relations).toBeDefined(); // Verify we can access the related work item if (result.relations && result.relations.length > 0) { const relation = result.relations[0]; expect(relation.rel).toBe('System.LinkTypes.Related'); expect(relation.url).toContain(linkedWorkItemId?.toString()); } // Verify fields exist expect(result.fields).toBeDefined(); if (result.fields) { expect(result.fields['System.Title']).toBeDefined(); } }); test('should retrieve work item with minimal fields when using expand=none', async () => { if (shouldSkipIntegrationTest() || !connection || !testWorkItemId) { return; } // Act - get work item with no expansion const result = await getWorkItem(connection, testWorkItemId, 'none'); // Assert expect(result).toBeDefined(); expect(result.id).toBe(testWorkItemId); expect(result.fields).toBeDefined(); // With expand=none, we should still get _links but no relations // The Azure DevOps API still returns _links even with expand=none expect(result.relations).toBeUndefined(); }); test('should throw AzureDevOpsResourceNotFoundError for non-existent work item', async () => { if (shouldSkipIntegrationTest() || !connection) { return; } // Use a very large ID that's unlikely to exist const nonExistentId = 999999999; // Assert that it throws the correct error await expect(getWorkItem(connection, nonExistentId)).rejects.toThrow( AzureDevOpsResourceNotFoundError, ); }); test('should include all possible fields with null values for empty fields', async () => { // Skip if no connection is available if (shouldSkipIntegrationTest() || !connection || !testWorkItemId) { return; } // Act - get work item by ID const result = await getWorkItem(connection, testWorkItemId); // Assert expect(result).toBeDefined(); expect(result.fields).toBeDefined(); if (result.fields) { // Get a direct connection to WorkItemTrackingApi to fetch field info for comparison const witApi = await connection.getWorkItemTrackingApi(); const projectName = result.fields['System.TeamProject']; const workItemType = result.fields['System.WorkItemType']; expect(projectName).toBeDefined(); expect(workItemType).toBeDefined(); if (projectName && workItemType) { // Get all possible field references for this work item type const allFields = await witApi.getWorkItemTypeFieldsWithReferences( projectName.toString(), workItemType.toString(), ); // Check that all fields from the reference are present in the result // Some might be null, but they should exist in the fields object for (const field of allFields) { if (field.referenceName) { expect(Object.keys(result.fields)).toContain(field.referenceName); } } // There should be at least one field with a null value // (This is a probabilistic test but very likely to pass since work items // typically have many optional fields that aren't filled in) const hasNullField = Object.values(result.fields).some( (value) => value === null, ); expect(hasNullField).toBe(true); } } }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tiberriver256/mcp-server-azure-devops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server