Skip to main content
Glama
pipelines.test.ts30.9 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { AccessToken } from "@azure/identity"; import { describe, expect, it, beforeEach } from "@jest/globals"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { WebApi } from "azure-devops-node-api"; import { StageUpdateType } from "azure-devops-node-api/interfaces/BuildInterfaces.js"; import { configurePipelineTools } from "../../../src/tools/pipelines"; import { apiVersion } from "../../../src/utils.js"; import { mockUpdateBuildStageResponse } from "../../mocks/pipelines"; // Mock fetch globally global.fetch = jest.fn() as jest.MockedFunction<typeof fetch>; type TokenProviderMock = () => Promise<string>; type ConnectionProviderMock = () => Promise<WebApi>; describe("configurePipelineTools", () => { let server: McpServer; let tokenProvider: TokenProviderMock; let connectionProvider: ConnectionProviderMock; let userAgentProvider: () => string; let mockConnection: { getBuildApi: jest.Mock; getPipelinesApi: jest.Mock; serverUrl: string }; beforeEach(() => { server = { tool: jest.fn() } as unknown as McpServer; tokenProvider = jest.fn(); userAgentProvider = () => "Jest"; mockConnection = { getBuildApi: jest.fn(), getPipelinesApi: jest.fn(), serverUrl: "https://dev.azure.com/test-org", }; connectionProvider = jest.fn().mockResolvedValue(mockConnection); (global.fetch as jest.MockedFunction<typeof fetch>).mockClear(); }); describe("tool registration", () => { it("registers build tools on the server", () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); expect(server.tool as jest.Mock).toHaveBeenCalled(); }); }); describe("update_build_stage tool", () => { it("should update build stage with correct parameters and return the expected result", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_update_build_stage"); if (!call) throw new Error("pipelines_update_build_stage tool not registered"); const [, , , handler] = call; // Mock the token provider (tokenProvider as jest.Mock).mockResolvedValue("mock-token"); // Mock successful fetch response const mockResponse = { ok: true, text: jest.fn().mockResolvedValue(JSON.stringify(mockUpdateBuildStageResponse)), }; (global.fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue(mockResponse as unknown as Response); const params = { project: "test-project", buildId: 123, stageName: "Build", status: "Retry", forceRetryAllJobs: true, }; const result = await handler(params); expect(global.fetch).toHaveBeenCalledWith(`https://dev.azure.com/test-org/test-project/_apis/build/builds/123/stages/Build?api-version=${apiVersion}`, { method: "PATCH", headers: { "Content-Type": "application/json", "Authorization": "Bearer mock-token", "User-Agent": "Jest", }, body: JSON.stringify({ forceRetryAllJobs: true, state: StageUpdateType.Retry.valueOf(), }), }); expect(result.content[0].text).toBe(JSON.stringify(JSON.stringify(mockUpdateBuildStageResponse), null, 2)); expect(result.isError).toBeUndefined(); }); it("should handle HTTP errors correctly", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_update_build_stage"); if (!call) throw new Error("pipelines_update_build_stage tool not registered"); const [, , , handler] = call; // Mock the token provider (tokenProvider as jest.Mock).mockResolvedValue("mock-token"); // Mock failed fetch response const mockResponse = { ok: false, status: 404, text: jest.fn().mockResolvedValue("Build stage not found"), }; (global.fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue(mockResponse as unknown as Response); const params = { project: "test-project", buildId: 999, stageName: "NonExistentStage", status: "Retry", forceRetryAllJobs: false, }; await expect(handler(params)).rejects.toThrow("Failed to update build stage: 404 Build stage not found"); expect(global.fetch).toHaveBeenCalledWith(`https://dev.azure.com/test-org/test-project/_apis/build/builds/999/stages/NonExistentStage?api-version=${apiVersion}`, { method: "PATCH", headers: { "Content-Type": "application/json", "Authorization": "Bearer mock-token", "User-Agent": "Jest", }, body: JSON.stringify({ forceRetryAllJobs: false, state: StageUpdateType.Retry.valueOf(), }), }); }); it("should handle network errors correctly", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_update_build_stage"); if (!call) throw new Error("pipelines_update_build_stage tool not registered"); const [, , , handler] = call; // Mock the token provider (tokenProvider as jest.Mock).mockResolvedValue("mock-token"); // Mock network error const networkError = new Error("Network connection failed"); (global.fetch as jest.MockedFunction<typeof fetch>).mockRejectedValue(networkError); const params = { project: "test-project", buildId: 123, stageName: "Build", status: "Retry", forceRetryAllJobs: false, }; await expect(handler(params)).rejects.toThrow("Network connection failed"); expect(global.fetch).toHaveBeenCalledWith(`https://dev.azure.com/test-org/test-project/_apis/build/builds/123/stages/Build?api-version=${apiVersion}`, { method: "PATCH", headers: { "Content-Type": "application/json", "Authorization": "Bearer mock-token", "User-Agent": "Jest", }, body: JSON.stringify({ forceRetryAllJobs: false, state: StageUpdateType.Retry.valueOf(), }), }); }); it("should handle token provider errors correctly", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_update_build_stage"); if (!call) throw new Error("pipelines_update_build_stage tool not registered"); const [, , , handler] = call; // Mock token provider error const tokenError = new Error("Failed to get access token"); (tokenProvider as jest.Mock).mockRejectedValue(tokenError); const params = { project: "test-project", buildId: 123, stageName: "Build", status: "Retry", forceRetryAllJobs: false, }; await expect(handler(params)).rejects.toThrow("Failed to get access token"); // Should not call fetch if token provider fails expect(global.fetch).not.toHaveBeenCalled(); }); it("should handle different StageUpdateType values correctly", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_update_build_stage"); if (!call) throw new Error("pipelines_update_build_stage tool not registered"); const [, , , handler] = call; (tokenProvider as jest.Mock).mockResolvedValue("mock-token"); const mockResponse = { ok: true, text: jest.fn().mockResolvedValue(JSON.stringify(mockUpdateBuildStageResponse)), }; (global.fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue(mockResponse as unknown as Response); const params = { project: "test-project", buildId: 123, stageName: "Deploy", status: "Cancel", forceRetryAllJobs: false, }; await handler(params); expect(global.fetch).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ body: JSON.stringify({ forceRetryAllJobs: false, state: StageUpdateType.Cancel.valueOf(), }), }) ); }); }); describe("get_definitions tool", () => { it("should call getDefinitions with correct parameters and return expected result", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_definitions"); if (!call) throw new Error("pipelines_get_build_definitions tool not registered"); const [, , , handler] = call; const mockBuildApi = { getDefinitions: jest.fn().mockResolvedValue([ { id: 1, name: "Build Definition 1" }, { id: 2, name: "Build Definition 2" }, ]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", repositoryId: "repo-123", repositoryType: "TfsGit" as const, name: "test-build", top: 10, }; const result = await handler(params); expect(mockBuildApi.getDefinitions).toHaveBeenCalledWith( "test-project", "test-build", "repo-123", "TfsGit", undefined, // queryOrder 10, // top undefined, // continuationToken undefined, // minMetricsTime undefined, // definitionIds undefined, // path undefined, // builtAfter undefined, // notBuiltAfter undefined, // includeAllProperties undefined, // includeLatestBuilds undefined, // taskIdFilter undefined, // processType undefined // yamlFilename ); expect(result.content[0].text).toBe( JSON.stringify( [ { id: 1, name: "Build Definition 1" }, { id: 2, name: "Build Definition 2" }, ], null, 2 ) ); }); it("should handle API errors for get_definitions", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_definitions"); if (!call) throw new Error("pipelines_get_build_definitions tool not registered"); const [, , , handler] = call; const mockBuildApi = { getDefinitions: jest.fn().mockRejectedValue(new Error("API Error")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project" }; await expect(handler(params)).rejects.toThrow("API Error"); }); }); describe("get_definition_revisions tool", () => { it("should call getDefinitionRevisions with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_definition_revisions"); if (!call) throw new Error("pipelines_get_build_definition_revisions tool not registered"); const [, , , handler] = call; const mockBuildApi = { getDefinitionRevisions: jest.fn().mockResolvedValue([ { revision: 1, comment: "Initial revision" }, { revision: 2, comment: "Updated build steps" }, ]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", definitionId: 123, }; const result = await handler(params); expect(mockBuildApi.getDefinitionRevisions).toHaveBeenCalledWith("test-project", 123); expect(result.content[0].text).toBe( JSON.stringify( [ { revision: 1, comment: "Initial revision" }, { revision: 2, comment: "Updated build steps" }, ], null, 2 ) ); }); it("should handle API errors for get_definition_revisions", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_definition_revisions"); if (!call) throw new Error("pipelines_get_build_definition_revisions tool not registered"); const [, , , handler] = call; const mockBuildApi = { getDefinitionRevisions: jest.fn().mockRejectedValue(new Error("Definition not found")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", definitionId: 999, }; await expect(handler(params)).rejects.toThrow("Definition not found"); }); }); describe("get_builds tool", () => { it("should call getBuilds with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_builds"); if (!call) throw new Error("pipelines_get_builds tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuilds: jest.fn().mockResolvedValue([ { id: 1, buildNumber: "20241201.1", status: "completed" }, { id: 2, buildNumber: "20241201.2", status: "inProgress" }, ]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", definitions: [1, 2], top: 5, branchName: "refs/heads/main", }; const result = await handler(params); expect(mockBuildApi.getBuilds).toHaveBeenCalledWith( "test-project", [1, 2], // definitions undefined, // queues undefined, // buildNumber undefined, // minTime undefined, // maxTime undefined, // requestedFor undefined, // reasonFilter undefined, // statusFilter undefined, // resultFilter undefined, // tagFilters undefined, // properties 5, // top undefined, // continuationToken undefined, // maxBuildsPerDefinition undefined, // deletedFilter undefined, // queryOrder (default BuildQueryOrder.QueueTimeDescending) "refs/heads/main", // branchName undefined, // buildIds undefined, // repositoryId undefined // repositoryType ); expect(result.content[0].text).toBe( JSON.stringify( [ { id: 1, buildNumber: "20241201.1", status: "completed" }, { id: 2, buildNumber: "20241201.2", status: "inProgress" }, ], null, 2 ) ); }); it("should handle API errors for get_builds", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_builds"); if (!call) throw new Error("pipelines_get_builds tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuilds: jest.fn().mockRejectedValue(new Error("Project not found")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "nonexistent-project" }; await expect(handler(params)).rejects.toThrow("Project not found"); }); }); describe("get_log tool", () => { it("should call getBuildLogs with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_log"); if (!call) throw new Error("pipelines_get_build_log tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildLogs: jest.fn().mockResolvedValue([ { id: 1, lineCount: 100 }, { id: 2, lineCount: 50 }, ]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, }; const result = await handler(params); expect(mockBuildApi.getBuildLogs).toHaveBeenCalledWith("test-project", 123); expect(result.content[0].text).toBe( JSON.stringify( [ { id: 1, lineCount: 100 }, { id: 2, lineCount: 50 }, ], null, 2 ) ); }); it("should handle API errors for get_log", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_log"); if (!call) throw new Error("pipelines_get_build_log tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildLogs: jest.fn().mockRejectedValue(new Error("Build not found")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 999, }; await expect(handler(params)).rejects.toThrow("Build not found"); }); }); describe("get_log_by_id tool", () => { it("should call getBuildLogLines with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_log_by_id"); if (!call) throw new Error("pipelines_get_build_log_by_id tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildLogLines: jest.fn().mockResolvedValue(["2024-12-01T10:00:00.000Z Starting build...", "2024-12-01T10:01:00.000Z Build completed successfully"]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, logId: 1, startLine: 10, endLine: 20, }; const result = await handler(params); expect(mockBuildApi.getBuildLogLines).toHaveBeenCalledWith("test-project", 123, 1, 10, 20); expect(result.content[0].text).toBe(JSON.stringify(["2024-12-01T10:00:00.000Z Starting build...", "2024-12-01T10:01:00.000Z Build completed successfully"], null, 2)); }); it("should handle API errors for get_log_by_id", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_log_by_id"); if (!call) throw new Error("pipelines_get_build_log_by_id tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildLogLines: jest.fn().mockRejectedValue(new Error("Log not found")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, logId: 999, }; await expect(handler(params)).rejects.toThrow("Log not found"); }); }); describe("get_changes tool", () => { it("should call getBuildChanges with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_changes"); if (!call) throw new Error("pipelines_get_build_changes tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildChanges: jest.fn().mockResolvedValue([ { id: "abc123", message: "Fixed bug in login" }, { id: "def456", message: "Added new feature" }, ]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, continuationToken: "token123", top: 50, includeSourceChange: true, }; const result = await handler(params); expect(mockBuildApi.getBuildChanges).toHaveBeenCalledWith("test-project", 123, "token123", 50, true); expect(result.content[0].text).toBe( JSON.stringify( [ { id: "abc123", message: "Fixed bug in login" }, { id: "def456", message: "Added new feature" }, ], null, 2 ) ); }); it("should use default top value when not provided", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_changes"); if (!call) throw new Error("pipelines_get_build_changes tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildChanges: jest.fn().mockResolvedValue([]), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, }; await handler(params); expect(mockBuildApi.getBuildChanges).toHaveBeenCalledWith("test-project", 123, undefined, undefined, undefined); }); it("should handle API errors for get_changes", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_build_changes"); if (!call) throw new Error("pipelines_get_build_changes tool not registered"); const [, , , handler] = call; const mockBuildApi = { getBuildChanges: jest.fn().mockRejectedValue(new Error("Changes not available")), }; mockConnection.getBuildApi.mockResolvedValue(mockBuildApi); const params = { project: "test-project", buildId: 123, }; await expect(handler(params)).rejects.toThrow("Changes not available"); }); }); describe("pipelines_get_run tool", () => { it("should call getRun with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_run"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { getRun: jest.fn().mockResolvedValue({ id: 1, name: "run-1" }), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, runId: 456, }; const result = await handler(params); expect(mockPipelinesApi.getRun).toHaveBeenCalledWith("test-project", 123, 456); expect(result.content[0].text).toBe(JSON.stringify({ id: 1, name: "run-1" }, null, 2)); }); it("should handle API errors for pipelines_get_run", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_get_run"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { getRun: jest.fn().mockRejectedValue(new Error("Run not found")), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, runId: 999, }; await expect(handler(params)).rejects.toThrow("Run not found"); }); }); describe("pipelines_list_runs tool", () => { it("should call listRuns with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_list_runs"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { listRuns: jest.fn().mockResolvedValue([{ id: 1, name: "run-1" }]), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, }; const result = await handler(params); expect(mockPipelinesApi.listRuns).toHaveBeenCalledWith("test-project", 123); expect(result.content[0].text).toBe(JSON.stringify([{ id: 1, name: "run-1" }], null, 2)); }); it("should handle API errors for pipelines_list_runs", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_list_runs"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { listRuns: jest.fn().mockRejectedValue(new Error("Pipeline not found")), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 999, }; await expect(handler(params)).rejects.toThrow("Pipeline not found"); }); }); describe("pipelines_run_pipeline tool", () => { it("should trigger pipeline with correct parameters", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_run_pipeline"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { runPipeline: jest.fn().mockResolvedValue({ id: 456 }), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, resources: { repositories: { self: { refName: "refs/heads/feature/new-feature", }, }, }, templateParameters: { key1: "value1", key2: "value2" }, }; const result = await handler(params); expect(mockPipelinesApi.runPipeline).toHaveBeenCalledWith( { previewRun: undefined, resources: { repositories: { self: { refName: "refs/heads/feature/new-feature", }, }, }, stagesToSkip: undefined, templateParameters: { key1: "value1", key2: "value2" }, variables: undefined, yamlOverride: undefined, }, "test-project", 123, undefined ); expect(result.content[0].text).toBe(JSON.stringify({ id: 456 }, null, 2)); }); it("should handle preview run", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_run_pipeline"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { runPipeline: jest.fn().mockResolvedValue({ id: 456, finalYaml: "final yaml" }), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, previewRun: true, }; await handler(params); expect(mockPipelinesApi.runPipeline).toHaveBeenCalledWith( expect.objectContaining({ previewRun: true, }), "test-project", 123, undefined ); }); it("should throw error for previewRun and yamlOverride", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_run_pipeline"); if (!call) fail("Tool not found"); const [, , , handler] = call; const params = { project: "test-project", pipelineId: 123, previewRun: false, yamlOverride: "some yaml", }; await expect(handler(params)).rejects.toThrow("Parameter 'yamlOverride' can only be specified together with parameter 'previewRun'."); }); it("should handle missing build ID from pipeline run", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_run_pipeline"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { runPipeline: jest.fn().mockResolvedValue({}), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, }; await expect(handler(params)).rejects.toThrow("Failed to get build ID from pipeline run"); }); it("should handle API errors for pipelines_run_pipeline", async () => { configurePipelineTools(server, tokenProvider, connectionProvider, userAgentProvider); const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === "pipelines_run_pipeline"); if (!call) fail("Tool not found"); const [, , , handler] = call; const mockPipelinesApi = { runPipeline: jest.fn().mockRejectedValue(new Error("API Error")), }; mockConnection.getPipelinesApi.mockResolvedValue(mockPipelinesApi); const params = { project: "test-project", pipelineId: 123, }; await expect(handler(params)).rejects.toThrow("API Error"); }); }); });

Latest Blog Posts

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/magemaclean/azure-devops-mcp'

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