Skip to main content
Glama
get-status-and-results.test.ts12.4 kB
// Mock config to avoid reading env jest.mock('@/config', () => ({ getTeamCityUrl: () => 'https://example.test', getTeamCityToken: () => 'token', getMCPMode: () => 'dev', })); describe('tools: enhanced status and results', () => { afterEach(() => { jest.resetModules(); jest.clearAllMocks(); }); it('get_build_status with useEnhanced returns JSON payload', async () => { const fakeStatus = { buildId: '123', state: 'running', status: 'SUCCESS', percentageComplete: 42, }; // Mock BuildStatusManager to return fakeStatus const getBuildStatus = jest.fn().mockResolvedValue(fakeStatus); const BuildStatusManager = jest.fn().mockImplementation(() => ({ getBuildStatus })); jest.doMock('@/teamcity/build-status-manager', () => ({ BuildStatusManager })); // Mock TeamCityAPI.getInstance to avoid env dependency jest.doMock('@/api-client', () => ({ TeamCityAPI: { getInstance: () => ({ builds: {}, listBuildArtifacts: jest.fn(), downloadBuildArtifact: jest.fn(), getBuildStatistics: jest.fn(), listChangesForBuild: jest.fn(), listSnapshotDependencies: jest.fn(), getBaseUrl: () => 'https://example.test', }), }, })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_status').handler({ buildId: '123', useEnhanced: true, includeTests: true, }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(typeof payload).toBe('object'); expect(payload.buildId).toBe('123'); }); it('get_build_status resolves builds using buildTypeId and buildNumber', async () => { const fakeStatus = { buildId: 'b166', buildNumber: '166', state: 'finished', status: 'SUCCESS', percentageComplete: 100, }; const getBuildStatus = jest.fn().mockResolvedValue(fakeStatus); const BuildStatusManager = jest.fn().mockImplementation(() => ({ getBuildStatus })); jest.doMock('@/teamcity/build-status-manager', () => ({ BuildStatusManager })); jest.doMock('@/api-client', () => ({ TeamCityAPI: { getInstance: () => ({ builds: {}, buildQueue: {}, listBuildArtifacts: jest.fn(), downloadBuildArtifact: jest.fn(), getBuildStatistics: jest.fn(), listChangesForBuild: jest.fn(), listSnapshotDependencies: jest.fn(), getBaseUrl: () => 'https://example.test', }), }, })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_status').handler({ buildTypeId: 'Infrastructure_DeployCrossroads', buildNumber: '166', includeProblems: true, }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.buildId).toBe('b166'); expect(payload.buildNumber).toBe('166'); expect(getBuildStatus).toHaveBeenCalledWith( expect.objectContaining({ buildTypeId: 'Infrastructure_DeployCrossroads', buildNumber: '166', includeProblems: true, }) ); }); it('get_build_status rejects when buildNumber provided without buildTypeId', async () => { jest.doMock('@/teamcity/build-status-manager', () => ({ BuildStatusManager: jest.fn().mockImplementation(() => ({ getBuildStatus: jest.fn(), })), })); jest.doMock('@/api-client', () => ({ TeamCityAPI: { getInstance: () => ({ builds: {}, buildQueue: {}, listBuildArtifacts: jest.fn(), downloadBuildArtifact: jest.fn(), getBuildStatistics: jest.fn(), listChangesForBuild: jest.fn(), listSnapshotDependencies: jest.fn(), getBaseUrl: () => 'https://example.test', }), }, })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_status').handler({ buildNumber: '123', }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.code).toBe('VALIDATION_ERROR'); const issues = (payload.error?.data ?? []) as Array<{ message?: string }>; expect(issues.some((issue) => issue?.message?.includes('buildTypeId is required'))).toBe(true); }); it('get_build_status rejects when neither buildId nor buildNumber is provided', async () => { jest.doMock('@/teamcity/build-status-manager', () => ({ BuildStatusManager: jest.fn().mockImplementation(() => ({ getBuildStatus: jest.fn(), })), })); jest.doMock('@/api-client', () => ({ TeamCityAPI: { getInstance: () => ({ builds: {}, buildQueue: {}, listBuildArtifacts: jest.fn(), downloadBuildArtifact: jest.fn(), getBuildStatistics: jest.fn(), listChangesForBuild: jest.fn(), listSnapshotDependencies: jest.fn(), getBaseUrl: () => 'https://example.test', }), }, })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_status').handler({ includeTests: true }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.code).toBe('VALIDATION_ERROR'); const issues = (payload.error?.data ?? []) as Array<{ message?: string }>; expect(issues.some((issue) => issue?.message?.includes('buildId or buildNumber'))).toBe(true); }); it('get_build_results returns enriched payload via manager', async () => { const fakeResults = { build: { id: 99, status: 'SUCCESS', number: '1' }, artifacts: [{ name: 'a.txt', size: 10 }], statistics: { testCount: 5 }, }; const getBuildResults = jest.fn().mockResolvedValue(fakeResults); const BuildResultsManager = jest.fn().mockImplementation(() => ({ getBuildResults })); jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_results').handler({ buildId: '99', includeArtifacts: true, includeStatistics: true, }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(typeof payload).toBe('object'); expect(payload.build?.id).toBe(99); expect(getBuildResults).toHaveBeenCalledWith( '99', expect.objectContaining({ includeArtifacts: true, includeStatistics: true, artifactEncoding: 'base64', }) ); }); it('get_build_results allows streaming artifact encoding', async () => { const fakeResults = { build: { id: 77, status: 'SUCCESS', number: '10' }, }; const getBuildResults = jest.fn().mockResolvedValue(fakeResults); const BuildResultsManager = jest.fn().mockImplementation(() => ({ getBuildResults })); jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); await getRequiredTool('get_build_results').handler({ buildId: '77', includeArtifacts: true, artifactEncoding: 'stream', }); expect(getBuildResults).toHaveBeenCalledWith( '77', expect.objectContaining({ artifactEncoding: 'stream' }) ); }); it('get_build_results resolves builds using buildTypeId and buildNumber', async () => { const fakeResults = { build: { id: 6001, status: 'SUCCESS', number: '60' }, }; const getBuildResults = jest.fn().mockResolvedValue(fakeResults); const BuildResultsManager = jest.fn().mockImplementation(() => ({ getBuildResults })); jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); await getRequiredTool('get_build_results').handler({ buildTypeId: 'Infrastructure_DeployCrossroads', buildNumber: '60', includeStatistics: true, }); expect(getBuildResults).toHaveBeenCalledWith( 'buildType:(id:Infrastructure_DeployCrossroads),number:60', expect.objectContaining({ includeStatistics: true }) ); }); it('get_build_results surfaces friendly not-found message with build context', async () => { const { TeamCityNotFoundError } = await import('@/teamcity/errors'); const notFound = new TeamCityNotFoundError('Build', 'id:12345'); const getBuildResults = jest.fn().mockRejectedValue(notFound); const BuildResultsManager = jest.fn().mockImplementation(() => ({ getBuildResults })); jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_results').handler({ buildTypeId: 'Infrastructure_DeployCrossroads', buildNumber: '60', }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.message).toMatch(/Infrastructure_DeployCrossroads[^]*number\s*60/i); expect(payload.error?.code).toBe('TEAMCITY_ERROR'); }); it('get_build_results rejects when only buildTypeId provided without buildNumber', async () => { jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager: jest.fn().mockImplementation(() => ({ getBuildResults: jest.fn(), })), })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_results').handler({ buildTypeId: 'SomeBuildType', }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.code).toBe('VALIDATION_ERROR'); const issues = (payload.error?.data ?? []) as Array<{ message?: string }>; expect(issues.some((issue) => issue?.message?.includes('together'))).toBe(true); }); it('get_build_results rejects when only buildNumber provided without buildTypeId', async () => { jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager: jest.fn().mockImplementation(() => ({ getBuildResults: jest.fn(), })), })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_results').handler({ buildNumber: '123', }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.code).toBe('VALIDATION_ERROR'); const issues = (payload.error?.data ?? []) as Array<{ message?: string }>; expect(issues.some((issue) => issue?.message?.includes('together'))).toBe(true); }); it('get_build_results rejects when neither buildId nor buildTypeId+buildNumber provided', async () => { jest.doMock('@/teamcity/build-results-manager', () => ({ BuildResultsManager: jest.fn().mockImplementation(() => ({ getBuildResults: jest.fn(), })), })); // eslint-disable-next-line @typescript-eslint/no-var-requires const { getRequiredTool } = require('@/tools'); const res = await getRequiredTool('get_build_results').handler({ includeArtifacts: true, }); const payload = JSON.parse((res.content?.[0]?.text as string) ?? '{}'); expect(payload.success).toBe(false); expect(payload.error?.code).toBe('VALIDATION_ERROR'); const issues = (payload.error?.data ?? []) as Array<{ message?: string }>; expect(issues.some((issue) => issue?.message?.includes('buildId or buildTypeId'))).toBe(true); }); });

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/Daghis/teamcity-mcp'

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