495 lines
15 KiB
TypeScript
495 lines
15 KiB
TypeScript
/**
|
|
* Unit tests for BitbucketRouter
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { BitbucketRouter, ToolResult } from '../../src/router.js';
|
|
|
|
// Mock domain clients - use simple object mocks instead of nested vi.fn
|
|
vi.mock('../../src/clients/pull-request-client.js', () => {
|
|
const mockPRClient = {
|
|
listPullRequests: async () => [
|
|
{ id: 1, title: 'Test PR 1', state: 'OPEN' },
|
|
{ id: 2, title: 'Test PR 2', state: 'MERGED' }
|
|
],
|
|
getPullRequest: async () => ({
|
|
id: 1,
|
|
title: 'Test PR',
|
|
state: 'OPEN',
|
|
source: { branch: { name: 'feature' } },
|
|
destination: { branch: { name: 'main' } }
|
|
}),
|
|
getPullRequestStatus: async () => ({
|
|
id: 1,
|
|
title: 'Test PR',
|
|
state: 'OPEN',
|
|
status: 'NORMAL'
|
|
}),
|
|
getPullRequestActivities: async () => ({
|
|
values: [{ action: 'OPEN' }]
|
|
}),
|
|
getPullRequestChanges: async () => ({
|
|
values: [{ type: 'modified', path: 'src/test.ts' }]
|
|
}),
|
|
getPullRequestCommits: async () => ({
|
|
values: [{ hash: 'abc123' }]
|
|
}),
|
|
getPullRequestDiff: async () => ({
|
|
diff: '--- test.ts\n+++ test.ts\n@@ -1 +1 @@'
|
|
}),
|
|
getPullRequestPatch: async () => ({
|
|
patch: '--- original\n+++ modified'
|
|
}),
|
|
getPullRequestParticipants: async () => ({
|
|
values: [{ user: { display_name: 'Test User' } }]
|
|
}),
|
|
getPullRequestReviewers: async () => [
|
|
{ user: { display_name: 'Reviewer 1' } }
|
|
],
|
|
getPullRequestTasks: async () => ({
|
|
values: []
|
|
}),
|
|
getPullRequestTaskCount: async () => ({ count: 0 }),
|
|
getFullPullRequest: async () => ({
|
|
id: 1,
|
|
title: 'Test PR',
|
|
description: 'Test description'
|
|
}),
|
|
createPullRequest: async () => ({ id: 1 }),
|
|
updatePullRequest: async () => ({ id: 1 }),
|
|
mergePullRequest: async () => ({ id: 1 }),
|
|
declinePullRequest: async () => ({ id: 1 }),
|
|
approvePullRequest: async () => ({ id: 1 }),
|
|
unapprovePullRequest: async () => ({ id: 1 }),
|
|
requestChangesPullRequest: async () => ({ id: 1 }),
|
|
removeRequestChangesPullRequest: async () => ({ id: 1 })
|
|
};
|
|
|
|
return {
|
|
PullRequestClient: class {
|
|
constructor() {
|
|
Object.assign(this, mockPRClient);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/clients/repository-client.js', () => {
|
|
const mockRepoClient = {
|
|
listWorkspaces: async () => ({ values: [] }),
|
|
listRepositories: async () => ({ values: [] }),
|
|
getRepository: async () => ({ slug: 'test' }),
|
|
listBranches: async () => ({ values: [] })
|
|
};
|
|
|
|
return {
|
|
RepositoryClient: class {
|
|
constructor() {
|
|
Object.assign(this, mockRepoClient);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
vi.mock('../../src/clients/comment-client.js', () => {
|
|
const mockCommentClient = {
|
|
getPullRequestComments: async () => ({
|
|
values: [{ id: 1, content: { raw: 'Test comment' } }]
|
|
}),
|
|
getPullRequestComment: async () => ({
|
|
id: 1,
|
|
content: { raw: 'Test comment' }
|
|
}),
|
|
addPullRequestComment: async () => ({ id: 1 }),
|
|
updatePullRequestComment: async () => ({ id: 1 }),
|
|
deletePullRequestComment: async () => ({ success: true }),
|
|
createPullRequestTask: async () => ({ id: 1 }),
|
|
updatePullRequestTask: async () => ({ id: 1 }),
|
|
deletePullRequestTask: async () => ({ success: true })
|
|
};
|
|
|
|
return {
|
|
CommentClient: class {
|
|
constructor() {
|
|
Object.assign(this, mockCommentClient);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
describe('BitbucketRouter', () => {
|
|
let router: BitbucketRouter;
|
|
const originalEnv = { ...process.env };
|
|
|
|
beforeEach(() => {
|
|
process.env = { ...originalEnv };
|
|
delete process.env.DEFAULT_WORKSPACE;
|
|
delete process.env.DEFAULT_REPO;
|
|
router = new BitbucketRouter();
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv;
|
|
});
|
|
|
|
describe('list_pull_requests', () => {
|
|
it('should return pull requests for valid parameters', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('pull_requests');
|
|
expect(result.data.pull_requests).toHaveLength(2);
|
|
});
|
|
|
|
it('should fail without workspace', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
repository: 'test-repo'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Missing required parameters');
|
|
});
|
|
|
|
it('should fail without repository', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Missing required parameters');
|
|
});
|
|
|
|
it('should accept "repo" as alias for "repository"', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace',
|
|
repo: 'test-repo'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should use DEFAULT_WORKSPACE when workspace is not provided', async () => {
|
|
process.env.DEFAULT_WORKSPACE = 'default-workspace';
|
|
process.env.DEFAULT_REPO = 'default-repo';
|
|
|
|
const result = await router.executeTool('list_pull_requests', {}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should use DEFAULT_REPO when repository is not provided', async () => {
|
|
process.env.DEFAULT_REPO = 'default-repo';
|
|
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should use both DEFAULT_WORKSPACE and DEFAULT_REPO when neither is provided', async () => {
|
|
process.env.DEFAULT_WORKSPACE = 'default-workspace';
|
|
process.env.DEFAULT_REPO = 'default-repo';
|
|
|
|
const result = await router.executeTool('list_pull_requests', {}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should prefer explicit params over defaults', async () => {
|
|
process.env.DEFAULT_WORKSPACE = 'default-workspace';
|
|
process.env.DEFAULT_REPO = 'default-repo';
|
|
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'explicit-workspace',
|
|
repository: 'explicit-repo'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should still fail when no workspace and no default available', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
repository: 'test-repo'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Missing required parameters');
|
|
});
|
|
|
|
it('should still fail when no repository and no default available', async () => {
|
|
const result = await router.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Missing required parameters');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request', () => {
|
|
it('should return pull request for valid parameters', async () => {
|
|
const result = await router.executeTool('get_pull_request', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('id', 1);
|
|
});
|
|
|
|
it('should accept pullRequestId as pr_id', async () => {
|
|
const result = await router.executeTool('get_pull_request', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pr_id: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('should fail with invalid pullRequestId', async () => {
|
|
const result = await router.executeTool('get_pull_request', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 'invalid'
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_activities', () => {
|
|
it('should return activities for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_activities', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('values');
|
|
});
|
|
|
|
it('should accept pagination parameters', async () => {
|
|
const result = await router.executeTool('get_pull_request_activities', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1,
|
|
limit: 10,
|
|
start: 0
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_changes', () => {
|
|
it('should return changes for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_changes', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('values');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_comments', () => {
|
|
it('should return comments for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_comments', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('values');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_comment', () => {
|
|
it('should return specific comment', async () => {
|
|
const result = await router.executeTool('get_pull_request_comment', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1,
|
|
commentId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('id', 1);
|
|
});
|
|
|
|
it('should fail without commentId', async () => {
|
|
const result = await router.executeTool('get_pull_request_comment', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_commits', () => {
|
|
it('should return commits for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_commits', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('values');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_diff', () => {
|
|
it('should return diff for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_diff', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('diff');
|
|
});
|
|
|
|
it('should accept path and context parameters', async () => {
|
|
const result = await router.executeTool('get_pull_request_diff', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1,
|
|
path: 'src/test.ts',
|
|
context: 3
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_patch', () => {
|
|
it('should return patch for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_patch', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('patch');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_participants', () => {
|
|
it('should return participants for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_participants', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('values');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_reviewers', () => {
|
|
it('should return reviewers for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_reviewers', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_status', () => {
|
|
it('should return status for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_status', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('state');
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_tasks', () => {
|
|
it('should return tasks for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_tasks', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('get_pull_request_task_count', () => {
|
|
it('should return task count for valid PR', async () => {
|
|
const result = await router.executeTool('get_pull_request_task_count', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('get_full_pull_request', () => {
|
|
it('should return full PR details', async () => {
|
|
const result = await router.executeTool('get_full_pull_request', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo',
|
|
pullRequestId: 1
|
|
}) as ToolResult;
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.data).toHaveProperty('id');
|
|
expect(result.data).toHaveProperty('description');
|
|
});
|
|
});
|
|
|
|
describe('unknown tool', () => {
|
|
it('should return error for unknown tool', async () => {
|
|
const result = await router.executeTool('unknown_tool', {}) as ToolResult;
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toContain('Unknown tool');
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('should handle API errors gracefully', async () => {
|
|
// Create router with a mock that throws
|
|
const errorRouter = new BitbucketRouter();
|
|
|
|
const result = await errorRouter.executeTool('list_pull_requests', {
|
|
workspace: 'test-workspace',
|
|
repository: 'test-repo'
|
|
}) as ToolResult;
|
|
|
|
// With our mock, this should succeed, but error cases should return proper format
|
|
if (!result.success) {
|
|
expect(result.error).toBeDefined();
|
|
expect(typeof result.error).toBe('string');
|
|
}
|
|
});
|
|
});
|
|
});
|