Skip to main content
Glama

Atlassian Bitbucket MCP Server

by aashari
atlassian.pullrequests.controller.test.ts19.4 kB
import { McpError, ErrorType } from '../utils/error.util.js'; import atlassianPullRequestsController from './atlassian.pullrequests.controller.js'; import { getAtlassianCredentials } from '../utils/transport.util.js'; import atlassianPullRequestsService from '../services/vendor.atlassian.pullrequests.service.js'; import atlassianWorkspacesService from '../services/vendor.atlassian.workspaces.service.js'; import atlassianRepositoriesService from '../services/vendor.atlassian.repositories.service.js'; describe('Atlassian Pull Requests Controller', () => { const skipIfNoCredentials = () => !getAtlassianCredentials(); async function getRepositoryInfo(): Promise<{ workspaceSlug: string; repoSlug: string; } | null> { // Skip if no credentials if (skipIfNoCredentials()) return null; try { // Try to get workspaces const workspacesResponse = await atlassianWorkspacesService.list({ pagelen: 1, }); if ( !workspacesResponse.values || workspacesResponse.values.length === 0 ) { console.warn('No workspaces found for test account'); return null; } const workspace = workspacesResponse.values[0]; // The workspace slug might be in different locations depending on the response structure // Try to access it safely from possible locations const workspaceSlug = workspace.workspace?.slug || (workspace as any).slug || (workspace as any).name || null; if (!workspaceSlug) { console.warn( 'Could not determine workspace slug from response', ); return null; } // Get repositories for this workspace const reposResponse = await atlassianRepositoriesService.list({ workspace: workspaceSlug, pagelen: 1, }); if (!reposResponse.values || reposResponse.values.length === 0) { console.warn( `No repositories found in workspace ${workspaceSlug}`, ); return null; } const repo = reposResponse.values[0]; // The repo slug might be in different locations or named differently const repoSlug = (repo as any).slug || (repo as any).name || (repo.full_name?.split('/').pop() as string) || null; if (!repoSlug) { console.warn( 'Could not determine repository slug from response', ); return null; } return { workspaceSlug, repoSlug, }; } catch (error) { console.warn('Failed to get repository info for tests:', error); return null; } } async function getFirstPullRequestId(): Promise<{ workspaceSlug: string; repoSlug: string; prId: string; } | null> { const repoInfo = await getRepositoryInfo(); if (!repoInfo) return null; try { // List PRs const prsResponse = await atlassianPullRequestsService.list({ workspace: repoInfo.workspaceSlug, repo_slug: repoInfo.repoSlug, pagelen: 1, }); if (!prsResponse.values || prsResponse.values.length === 0) { console.warn( `No pull requests found in ${repoInfo.workspaceSlug}/${repoInfo.repoSlug}`, ); return null; } // Return the first PR's info return { workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId: prsResponse.values[0].id.toString(), }; } catch (error) { console.warn('Failed to get pull request ID for tests:', error); return null; } } async function getPullRequestInfo(): Promise<{ workspaceSlug: string; repoSlug: string; prId: string; } | null> { // Attempt to get a first PR ID return await getFirstPullRequestId(); } // List tests describe('list', () => { it('should list pull requests for a repository', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } const result = await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, }); // Verify the response structure expect(result).toHaveProperty('content'); expect(typeof result.content).toBe('string'); // Check for expected format based on the formatter output expect(result.content).toMatch(/^# Pull Requests/); }, 30000); it('should handle query parameters correctly', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // A query term that should not match any PRs in any normal repo const uniqueQuery = 'z9x8c7vb6nm5'; const result = await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, query: uniqueQuery, }); // Even with no matches, we expect a formatted empty list expect(result).toHaveProperty('content'); expect(result.content).toMatch(/^# Pull Requests/); expect(result.content).toContain('No pull requests found'); }, 30000); it('should handle pagination options (limit)', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // Fetch with a small limit const result = await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, limit: 1, }); // Verify the response structure expect(result).toHaveProperty('content'); // Check if the result contains only one PR or none // First check if there are no PRs if (result.content.includes('No pull requests found')) { console.warn('No pull requests found for pagination test.'); return; } // Extract count const countMatch = result.content.match(/\*Showing (\d+) item/); if (countMatch && countMatch[1]) { const count = parseInt(countMatch[1], 10); expect(count).toBeLessThanOrEqual(1); } }, 30000); it('should handle authorization errors gracefully', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // Create mocked credentials error by using intentionally invalid credentials jest.spyOn( atlassianPullRequestsService, 'list', ).mockRejectedValueOnce( new McpError('Unauthorized', ErrorType.AUTH_INVALID, 401), ); // Expect to throw a standardized error await expect( atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, }), ).rejects.toThrow(McpError); // Verify it was called expect(atlassianPullRequestsService.list).toHaveBeenCalled(); // Cleanup mock jest.restoreAllMocks(); }, 30000); it('should require workspace and repository slugs', async () => { if (skipIfNoCredentials()) return; // Missing both slugs await expect( atlassianPullRequestsController.list({} as any), ).rejects.toThrow( /workspace slug and repository slug are required/i, ); // Missing repo slug await expect( atlassianPullRequestsController.list({ workspaceSlug: 'some-workspace', } as any), ).rejects.toThrow( /workspace slug and repository slug are required/i, ); // Missing workspace slug but should try to use default // This will fail with a different error if no default workspace can be found try { await atlassianPullRequestsController.list({ repoSlug: 'some-repo', } as any); } catch (error) { expect(error).toBeInstanceOf(Error); // Either it will fail with "could not determine default" or // it may succeed with a default workspace but fail with a different error } }, 10000); it('should apply default parameters correctly', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } const serviceSpy = jest.spyOn(atlassianPullRequestsService, 'list'); try { await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, // No limit provided, should use default }); // Check that the service was called with the default limit const calls = serviceSpy.mock.calls; expect(calls.length).toBeGreaterThan(0); const lastCall = calls[calls.length - 1]; expect(lastCall[0]).toHaveProperty('pagelen'); expect(lastCall[0].pagelen).toBeGreaterThan(0); // Should have some positive default value } finally { serviceSpy.mockRestore(); } }, 30000); it('should format pagination information correctly', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // Mock a response with pagination const mockResponse = { values: [ { id: 1, title: 'Test PR', state: 'OPEN', created_on: new Date().toISOString(), updated_on: new Date().toISOString(), author: { display_name: 'Test User', }, source: { branch: { name: 'feature-branch' }, }, destination: { branch: { name: 'main' }, }, links: {}, }, ], page: 1, size: 1, pagelen: 1, next: 'https://api.bitbucket.org/2.0/repositories/workspace/repo/pullrequests?page=2', }; jest.spyOn( atlassianPullRequestsService, 'list', ).mockResolvedValueOnce(mockResponse as any); const result = await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, limit: 1, }); // Verify pagination info is included expect(result.content).toContain('*Showing 1 item'); expect(result.content).toContain('More results are available'); expect(result.content).toContain('Next cursor: `2`'); // Cleanup mock jest.restoreAllMocks(); }, 10000); }); // Get PR tests describe('get', () => { it('should retrieve detailed pull request information', async () => { if (skipIfNoCredentials()) return; const prInfo = await getPullRequestInfo(); if (!prInfo) { console.warn('Skipping test: No pull request info found.'); return; } const result = await atlassianPullRequestsController.get({ workspaceSlug: prInfo.workspaceSlug, repoSlug: prInfo.repoSlug, prId: prInfo.prId, includeFullDiff: false, includeComments: false, }); // Verify the response structure expect(result).toHaveProperty('content'); expect(typeof result.content).toBe('string'); // Check for expected format based on the formatter output expect(result.content).toMatch(/^# Pull Request/); expect(result.content).toContain('**ID**:'); expect(result.content).toContain('**Title**:'); expect(result.content).toContain('**State**:'); expect(result.content).toContain('**Author**:'); expect(result.content).toContain('**Created**:'); }, 30000); it('should handle errors for non-existent pull requests', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // Use a PR ID that is very unlikely to exist const nonExistentPrId = '999999999'; await expect( atlassianPullRequestsController.get({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId: nonExistentPrId, includeFullDiff: false, includeComments: false, }), ).rejects.toThrow(McpError); // Test that the error has the right status code and type try { await atlassianPullRequestsController.get({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId: nonExistentPrId, includeFullDiff: false, includeComments: false, }); } catch (error) { expect(error).toBeInstanceOf(McpError); expect((error as McpError).statusCode).toBe(404); expect((error as McpError).message).toContain('not found'); } }, 30000); it('should require all necessary parameters', async () => { if (skipIfNoCredentials()) return; // No parameters should throw await expect( atlassianPullRequestsController.get({} as any), ).rejects.toThrow(); // Missing PR ID should throw const repoInfo = await getRepositoryInfo(); if (repoInfo) { await expect( atlassianPullRequestsController.get({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, // prId: missing } as any), ).rejects.toThrow(); } }, 10000); }); // List comments tests describe('listComments', () => { it('should return formatted pull request comments in Markdown', async () => { if (skipIfNoCredentials()) return; const prInfo = await getFirstPullRequestId(); if (!prInfo) { console.warn('Skipping test: No pull request ID found.'); return; } const result = await atlassianPullRequestsController.listComments({ workspaceSlug: prInfo.workspaceSlug, repoSlug: prInfo.repoSlug, prId: prInfo.prId, }); // Verify the response structure expect(result).toHaveProperty('content'); expect(typeof result.content).toBe('string'); // Basic Markdown content checks if (result.content !== 'No comments found on this pull request.') { expect(result.content).toMatch(/^# Comments on Pull Request/m); expect(result.content).toContain('**Author**:'); expect(result.content).toContain('**Updated**:'); expect(result.content).toMatch( /---\s*\*Showing \d+ items?\.\*/, ); } }, 30000); it('should handle pagination options (limit/cursor)', async () => { if (skipIfNoCredentials()) return; const prInfo = await getFirstPullRequestId(); if (!prInfo) { console.warn('Skipping test: No pull request ID found.'); return; } // Fetch first page with limit 1 const result1 = await atlassianPullRequestsController.listComments({ workspaceSlug: prInfo.workspaceSlug, repoSlug: prInfo.repoSlug, prId: prInfo.prId, limit: 1, }); // Extract pagination info from content const countMatch1 = result1.content.match( /\*Showing (\d+) items?\.\*/, ); const count1 = countMatch1 ? parseInt(countMatch1[1], 10) : 0; expect(count1).toBeLessThanOrEqual(1); // Extract cursor from content const cursorMatch1 = result1.content.match( /\*Next cursor: `([^`]+)`\*/, ); const nextCursor = cursorMatch1 ? cursorMatch1[1] : null; // Check if pagination indicates more results const hasMoreResults = result1.content.includes( 'More results are available.', ); // If there's a next page, fetch it if (hasMoreResults && nextCursor) { const result2 = await atlassianPullRequestsController.listComments({ workspaceSlug: prInfo.workspaceSlug, repoSlug: prInfo.repoSlug, prId: prInfo.prId, limit: 1, cursor: nextCursor, }); // Extract count from result2 const countMatch2 = result2.content.match( /\*Showing (\d+) items?\.\*/, ); const count2 = countMatch2 ? parseInt(countMatch2[1], 10) : 0; expect(count2).toBeLessThanOrEqual(1); // Ensure content is different (or handle case where only 1 comment exists) if ( result1.content !== 'No comments found on this pull request.' && result2.content !== 'No comments found on this pull request.' && count1 > 0 && count2 > 0 ) { // Only compare if we actually have multiple comments expect(result1.content).not.toEqual(result2.content); } } else { console.warn( 'Skipping cursor part of pagination test: Only one page of comments found or no comments available.', ); } }, 30000); it('should handle empty result scenario', async () => { if (skipIfNoCredentials()) return; // First get a PR without comments, or use a non-existent but valid format PR ID const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } // Try to find a PR without comments try { // First check if we can access the repository const repoResult = await atlassianPullRequestsController.list({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, limit: 1, }); // Extract PR ID from the content if we have any PRs const prIdMatch = repoResult.content.match(/\*\*ID\*\*:\s+(\d+)/); if (!prIdMatch || !prIdMatch[1]) { console.warn( 'Skipping empty result test: No pull requests available.', ); return; } const prId = prIdMatch[1]; // Get comments for this PR const commentsResult = await atlassianPullRequestsController.listComments({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId, }); // Check if there are no comments if ( commentsResult.content === 'No comments found on this pull request.' ) { // Verify the expected message for empty results expect(commentsResult.content).toBe( 'No comments found on this pull request.', ); } else { console.warn( 'Skipping empty result test: All PRs have comments.', ); } } catch (error) { console.warn('Error during empty result test:', error); } }, 30000); it('should throw an McpError for a non-existent pull request ID', async () => { if (skipIfNoCredentials()) return; const repoInfo = await getRepositoryInfo(); if (!repoInfo) { console.warn('Skipping test: No repository info found.'); return; } const nonExistentId = '999999999'; // Very high number unlikely to exist // Expect the controller call to reject with an McpError await expect( atlassianPullRequestsController.listComments({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId: nonExistentId, }), ).rejects.toThrow(McpError); // Check the status code via the error handler's behavior try { await atlassianPullRequestsController.listComments({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, prId: nonExistentId, }); } catch (e) { expect(e).toBeInstanceOf(McpError); expect((e as McpError).statusCode).toBe(404); // Expecting Not Found expect((e as McpError).message).toContain('not found'); } }, 30000); it('should require all necessary parameters', async () => { if (skipIfNoCredentials()) return; // No parameters await expect( atlassianPullRequestsController.listComments({} as any), ).rejects.toThrow(); // Missing PR ID const repoInfo = await getRepositoryInfo(); if (repoInfo) { await expect( atlassianPullRequestsController.listComments({ workspaceSlug: repoInfo.workspaceSlug, repoSlug: repoInfo.repoSlug, // prId: missing } as any), ).rejects.toThrow(); } }, 10000); }); // Note: addComment test suite has been removed to avoid creating comments on real PRs during tests });

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/aashari/mcp-server-atlassian-bitbucket'

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