Skip to main content
Glama
client-adapter.test.ts11.6 kB
import axios, { type AxiosInstance } from 'axios'; import type { TeamCityAPI, TeamCityAPIClientConfig } from '@/api-client'; import { createAdapterFromTeamCityAPI } from '@/teamcity/client-adapter'; import type { TeamCityFullConfig } from '@/teamcity/config'; import type { BuildApiLike, TeamCityApiSurface, TeamCityRequestContext, } from '@/teamcity/types/client'; import { warn } from '@/utils/logger'; jest.mock('@/utils/logger', () => ({ warn: jest.fn(), })); describe('createAdapterFromTeamCityAPI', () => { const baseUrl = 'https://teamcity.example.com'; let http: AxiosInstance; let modules: Readonly<TeamCityApiSurface>; let apiConfig: TeamCityAPIClientConfig; let fullConfig: TeamCityFullConfig; let apiMock: TeamCityAPI; const testConnection = jest.fn(); const listProjects = jest.fn(); const getProject = jest.fn(); const listBuilds = jest.fn(); const getBuild = jest.fn(); const triggerBuild = jest.fn(); const getBuildLog = jest.fn(); const getBuildLogChunk = jest.fn(); const listBuildArtifacts = jest.fn(); const downloadBuildArtifact = jest.fn(); const getBuildStatistics = jest.fn(); const listChangesForBuild = jest.fn(); const listSnapshotDependencies = jest.fn(); const listBuildTypes = jest.fn(); const getBuildType = jest.fn(); const listTestFailures = jest.fn(); const listVcsRoots = jest.fn(); const listAgents = jest.fn(); const listAgentPools = jest.fn(); const warnMock = warn as jest.MockedFunction<typeof warn>; beforeEach(() => { const token = 'token-123'; const timeout = 4200; http = axios.create({ baseURL: baseUrl, timeout, headers: { Authorization: `Bearer ${token}` }, }); const buildsMock: BuildApiLike = { getAllBuilds: jest.fn(), getBuild: jest.fn(), getMultipleBuilds: jest.fn(), getBuildProblems: jest.fn(), }; modules = { agents: {}, agentPools: {}, agentTypes: {}, audit: {}, avatars: {}, builds: buildsMock, buildQueue: {}, buildTypes: {}, changes: {}, cloudInstances: {}, deploymentDashboards: {}, globalServerSettings: {}, groups: {}, health: {}, investigations: {}, mutes: {}, nodes: {}, problems: {}, problemOccurrences: {}, projects: {}, roles: {}, root: {}, server: {}, serverAuthSettings: {}, tests: {}, testMetadata: {}, users: {}, vcsRoots: {}, vcsRootInstances: {}, versionedSettings: {}, } as unknown as TeamCityApiSurface; apiConfig = { baseUrl, token, timeout, }; fullConfig = { connection: { baseUrl, token: apiConfig.token, timeout: apiConfig.timeout, }, }; apiMock = { modules, http, getBaseUrl: () => baseUrl, testConnection, listProjects, getProject, listBuilds, getBuild, triggerBuild, getBuildLog, getBuildLogChunk, listBuildTypes, getBuildType, listTestFailures, listBuildArtifacts, downloadBuildArtifact, getBuildStatistics, listChangesForBuild, listSnapshotDependencies, listVcsRoots, listAgents, listAgentPools, } as unknown as TeamCityAPI; testConnection.mockReset().mockResolvedValue(true); listProjects.mockReset().mockResolvedValue({ projects: [] }); getProject.mockReset().mockResolvedValue({ project: {} }); listBuilds.mockReset().mockResolvedValue({ build: [] }); getBuild.mockReset().mockResolvedValue({ build: {} }); triggerBuild.mockReset().mockResolvedValue({ build: 'queued' }); getBuildLog.mockReset().mockResolvedValue('log'); getBuildLogChunk.mockReset().mockResolvedValue({ lines: [], startLine: 0 }); listBuildTypes.mockReset().mockResolvedValue({ buildType: [] }); getBuildType.mockReset().mockResolvedValue({ buildType: {} }); listTestFailures.mockReset().mockResolvedValue({ testOccurrence: [] }); listBuildArtifacts.mockReset(); downloadBuildArtifact.mockReset(); getBuildStatistics.mockReset(); listChangesForBuild.mockReset(); listSnapshotDependencies.mockReset(); listVcsRoots.mockReset().mockResolvedValue({ vcsRoot: [] }); listAgents.mockReset().mockResolvedValue({ agent: [] }); listAgentPools.mockReset().mockResolvedValue({ agentPool: [] }); warnMock.mockReset(); }); it('exposes unified surface and configuration helpers', async () => { const adapter = createAdapterFromTeamCityAPI(apiMock, { apiConfig, fullConfig }); expect(adapter.modules).toBe(modules); expect(adapter.http).toBe(http); expect(adapter.getAxios()).toBe(http); expect(adapter.getConfig()).toBe(fullConfig); expect(adapter.getApiConfig()).toEqual(apiConfig); expect(adapter.baseUrl).toBe(baseUrl); const buildsMock = modules.builds.getAllBuilds as jest.Mock; buildsMock.mockClear(); expect(typeof adapter.builds.getAllBuilds).toBe('function'); await adapter.builds.getAllBuilds('locator'); expect(buildsMock).toHaveBeenCalledWith('locator', undefined, undefined); }); it('delegates helper methods to the underlying API', async () => { const adapter = createAdapterFromTeamCityAPI(apiMock, { apiConfig, fullConfig }); await adapter.listBuildArtifacts('42'); expect(listBuildArtifacts).toHaveBeenCalledWith('42', undefined); await adapter.downloadArtifactContent('42', 'foo.zip'); expect(downloadBuildArtifact).toHaveBeenCalledWith('42', 'foo.zip', undefined); await adapter.downloadArtifactContent('42', 'foo.zip', { responseType: 'stream' }); expect(downloadBuildArtifact).toHaveBeenCalledWith('42', 'foo.zip', { responseType: 'stream', }); await adapter.getBuildStatistics('42', 'data'); expect(getBuildStatistics).toHaveBeenCalledWith('42', 'data'); await adapter.listChangesForBuild('42', 'changes'); expect(listChangesForBuild).toHaveBeenCalledWith('42', 'changes'); await adapter.listSnapshotDependencies('42'); expect(listSnapshotDependencies).toHaveBeenCalledWith('42'); }); it('delegates extended helper methods to the underlying API', async () => { const adapter = createAdapterFromTeamCityAPI(apiMock, { apiConfig, fullConfig }); await adapter.testConnection(); expect(testConnection).toHaveBeenCalledTimes(1); await adapter.listProjects('locator'); expect(listProjects).toHaveBeenCalledWith('locator'); await adapter.getProject('PROJECT_ID'); expect(getProject).toHaveBeenCalledWith('PROJECT_ID'); await adapter.listBuilds('buildLocator'); expect(listBuilds).toHaveBeenCalledWith('buildLocator'); await adapter.getBuild('buildId'); expect(getBuild).toHaveBeenCalledWith('buildId'); await adapter.triggerBuild('bt2', 'refs/heads/main', 'comment'); expect(triggerBuild).toHaveBeenCalledWith('bt2', 'refs/heads/main', 'comment'); await adapter.getBuildLog('99'); expect(getBuildLog).toHaveBeenCalledWith('99'); const chunkOptions = { startLine: 10, lineCount: 5 } as const; await adapter.getBuildLogChunk('100', chunkOptions); expect(getBuildLogChunk).toHaveBeenCalledWith('100', chunkOptions); await adapter.listBuildTypes('proj'); expect(listBuildTypes).toHaveBeenCalledWith('proj'); await adapter.getBuildType('bt3'); expect(getBuildType).toHaveBeenCalledWith('bt3'); await adapter.listTestFailures('101'); expect(listTestFailures).toHaveBeenCalledWith('101'); await adapter.listVcsRoots('proj'); expect(listVcsRoots).toHaveBeenCalledWith('proj'); await adapter.listAgents(); expect(listAgents).toHaveBeenCalledWith(); await adapter.listAgentPools(); expect(listAgentPools).toHaveBeenCalledWith(); }); it('provides request helper with axios context', async () => { const adapter = createAdapterFromTeamCityAPI(apiMock, { apiConfig, fullConfig }); const fn = jest.fn(async (ctx: TeamCityRequestContext) => ctx.baseUrl); const result = await adapter.request(fn); expect(result).toBe(baseUrl); expect(fn).toHaveBeenCalledWith({ axios: http, baseUrl, requestId: undefined }); }); it('falls back to minimal config when options omitted', () => { const adapter = createAdapterFromTeamCityAPI(apiMock); expect(adapter.getApiConfig()).toEqual({ baseUrl, token: apiConfig.token, timeout: apiConfig.timeout, }); expect(adapter.getConfig()).toEqual({ connection: { baseUrl, token: apiConfig.token, timeout: apiConfig.timeout, }, }); }); it('uses apiConfig baseUrl when TeamCityAPI lacks getBaseUrl', () => { const minimalApi = { modules, listProjects, getProject, listBuilds, getBuild, triggerBuild, getBuildLog, getBuildLogChunk, listBuildTypes, getBuildType, listTestFailures, listBuildArtifacts, downloadBuildArtifact, getBuildStatistics, listChangesForBuild, listSnapshotDependencies, listVcsRoots, listAgents, listAgentPools, } as unknown as TeamCityAPI; const adapter = createAdapterFromTeamCityAPI(minimalApi, { apiConfig: { baseUrl: 'https://from-options', token: 'opt-token', timeout: 1111 }, }); expect(adapter.baseUrl).toBe('https://from-options'); expect(warnMock).not.toHaveBeenCalled(); }); it('uses axios defaults when baseUrl is provided on the HTTP client', () => { const httpOnly = axios.create({ baseURL: 'https://from-http' }); const minimalApi = { modules, http: httpOnly, listProjects, getProject, listBuilds, getBuild, triggerBuild, getBuildLog, getBuildLogChunk, listBuildTypes, getBuildType, listTestFailures, listBuildArtifacts, downloadBuildArtifact, getBuildStatistics, listChangesForBuild, listSnapshotDependencies, listVcsRoots, listAgents, listAgentPools, } as unknown as TeamCityAPI; const adapter = createAdapterFromTeamCityAPI(minimalApi); expect(adapter.baseUrl).toBe('https://from-http'); expect(warnMock).not.toHaveBeenCalled(); }); it('warns and uses the placeholder baseUrl when no sources are available', async () => { const projects = { getAllProjects: jest.fn().mockResolvedValue({}) }; const builds: BuildApiLike = { getAllBuilds: jest.fn(), getBuild: jest.fn(), getMultipleBuilds: jest.fn(), getBuildProblems: jest.fn(), }; const minimalApi = { projects, builds, listProjects, getProject, listBuilds, getBuild, triggerBuild, getBuildLog, getBuildLogChunk, listBuildTypes, getBuildType, listTestFailures, listBuildArtifacts, downloadBuildArtifact, getBuildStatistics, listChangesForBuild, listSnapshotDependencies, listVcsRoots, listAgents, listAgentPools, } as unknown as TeamCityAPI; const adapter = createAdapterFromTeamCityAPI(minimalApi); expect(adapter.baseUrl).toBe('http://not-configured'); expect(warnMock).toHaveBeenCalledWith( 'TeamCity adapter using fallback baseUrl placeholder', expect.objectContaining({ reason: 'missing_base_url' }) ); await adapter.modules.projects.getAllProjects?.('foo'); expect(projects.getAllProjects).toHaveBeenCalledWith('foo'); }); });

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