@atlassian-dc-mcp/bitbucket 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/build/__tests__/bitbucket-service.test.d.ts +2 -0
- package/build/__tests__/bitbucket-service.test.d.ts.map +1 -0
- package/build/__tests__/bitbucket-service.test.js +269 -0
- package/build/__tests__/bitbucket-service.test.js.map +1 -0
- package/build/__tests__/pr-changes-mapper.test.d.ts +2 -0
- package/build/__tests__/pr-changes-mapper.test.d.ts.map +1 -0
- package/build/__tests__/pr-changes-mapper.test.js +396 -0
- package/build/__tests__/pr-changes-mapper.test.js.map +1 -0
- package/build/__tests__/pr-comment-mapper.test.d.ts +2 -0
- package/build/__tests__/pr-comment-mapper.test.d.ts.map +1 -0
- package/build/__tests__/pr-comment-mapper.test.js +210 -0
- package/build/__tests__/pr-comment-mapper.test.js.map +1 -0
- package/build/bitbucket-service.d.ts +84 -1
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +148 -1
- package/build/bitbucket-service.js.map +1 -1
- package/build/index.js +13 -1
- package/build/index.js.map +1 -1
- package/build/pr-changes-mapper.d.ts +32 -0
- package/build/pr-changes-mapper.d.ts.map +1 -0
- package/build/pr-changes-mapper.js +111 -0
- package/build/pr-changes-mapper.js.map +1 -0
- package/build/pr-comment-mapper.d.ts +48 -0
- package/build/pr-comment-mapper.d.ts.map +1 -0
- package/build/pr-comment-mapper.js +128 -0
- package/build/pr-comment-mapper.js.map +1 -0
- package/jest.config.js +23 -0
- package/package.json +5 -3
- package/src/__tests__/bitbucket-service.test.ts +419 -0
- package/src/__tests__/pr-changes-mapper.test.ts +420 -0
- package/src/__tests__/pr-comment-mapper.test.ts +230 -0
- package/src/bitbucket-service.ts +215 -2
- package/src/index.ts +33 -2
- package/src/pr-changes-mapper.ts +216 -0
- package/src/pr-comment-mapper.ts +318 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Type guard functions to validate object structure
|
|
2
|
+
function isBitbucketUser(obj) {
|
|
3
|
+
return (typeof obj === 'object' &&
|
|
4
|
+
obj !== null &&
|
|
5
|
+
typeof obj.name === 'string' &&
|
|
6
|
+
typeof obj.emailAddress === 'string' &&
|
|
7
|
+
typeof obj.active === 'boolean' &&
|
|
8
|
+
typeof obj.displayName === 'string' &&
|
|
9
|
+
typeof obj.id === 'number' &&
|
|
10
|
+
typeof obj.slug === 'string' &&
|
|
11
|
+
typeof obj.type === 'string' &&
|
|
12
|
+
typeof obj.links === 'object');
|
|
13
|
+
}
|
|
14
|
+
function isCommentAnchor(obj) {
|
|
15
|
+
return (typeof obj === 'object' &&
|
|
16
|
+
obj !== null &&
|
|
17
|
+
typeof obj.fromHash === 'string' &&
|
|
18
|
+
typeof obj.toHash === 'string' &&
|
|
19
|
+
typeof obj.line === 'number' &&
|
|
20
|
+
typeof obj.lineType === 'string' &&
|
|
21
|
+
typeof obj.fileType === 'string' &&
|
|
22
|
+
typeof obj.path === 'string' &&
|
|
23
|
+
typeof obj.diffType === 'string' &&
|
|
24
|
+
typeof obj.orphaned === 'boolean');
|
|
25
|
+
}
|
|
26
|
+
function isComment(obj) {
|
|
27
|
+
return (typeof obj === 'object' &&
|
|
28
|
+
obj !== null &&
|
|
29
|
+
typeof obj.id === 'number' &&
|
|
30
|
+
typeof obj.version === 'number' &&
|
|
31
|
+
typeof obj.text === 'string' &&
|
|
32
|
+
isBitbucketUser(obj.author) &&
|
|
33
|
+
typeof obj.createdDate === 'number' &&
|
|
34
|
+
typeof obj.updatedDate === 'number' &&
|
|
35
|
+
Array.isArray(obj.comments) &&
|
|
36
|
+
typeof obj.threadResolved === 'boolean' &&
|
|
37
|
+
typeof obj.severity === 'string' &&
|
|
38
|
+
typeof obj.state === 'string' &&
|
|
39
|
+
typeof obj.properties === 'object' &&
|
|
40
|
+
typeof obj.permittedOperations === 'object');
|
|
41
|
+
}
|
|
42
|
+
function isPRActivity(obj) {
|
|
43
|
+
return (typeof obj === 'object' &&
|
|
44
|
+
obj !== null &&
|
|
45
|
+
typeof obj.id === 'number' &&
|
|
46
|
+
typeof obj.createdDate === 'number' &&
|
|
47
|
+
isBitbucketUser(obj.user) &&
|
|
48
|
+
typeof obj.action === 'string');
|
|
49
|
+
}
|
|
50
|
+
function simplifyUser(user) {
|
|
51
|
+
return {
|
|
52
|
+
name: user.name,
|
|
53
|
+
displayName: user.displayName
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function simplifyAnchor(anchor) {
|
|
57
|
+
return {
|
|
58
|
+
line: anchor.line,
|
|
59
|
+
path: anchor.path,
|
|
60
|
+
fileType: anchor.fileType
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function simplifyComment(comment) {
|
|
64
|
+
return {
|
|
65
|
+
id: comment.id,
|
|
66
|
+
text: comment.text,
|
|
67
|
+
author: simplifyUser(comment.author),
|
|
68
|
+
createdDate: comment.createdDate,
|
|
69
|
+
...(comment.anchor && { anchor: simplifyAnchor(comment.anchor) }),
|
|
70
|
+
threadResolved: comment.threadResolved,
|
|
71
|
+
state: comment.state
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function simplifyActivity(activity) {
|
|
75
|
+
return {
|
|
76
|
+
id: activity.id,
|
|
77
|
+
createdDate: activity.createdDate,
|
|
78
|
+
user: simplifyUser(activity.user),
|
|
79
|
+
action: activity.action,
|
|
80
|
+
...(activity.commentAction && { commentAction: activity.commentAction }),
|
|
81
|
+
...(activity.comment && { comment: simplifyComment(activity.comment) })
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function simplifyBitbucketPRComments(response) {
|
|
85
|
+
const activities = [];
|
|
86
|
+
// Process each activity with type guard validation
|
|
87
|
+
for (const activity of response.values || []) {
|
|
88
|
+
if (isPRActivity(activity)) {
|
|
89
|
+
activities.push(simplifyActivity(activity));
|
|
90
|
+
}
|
|
91
|
+
// If type guard fails, we skip the invalid activity but continue processing
|
|
92
|
+
}
|
|
93
|
+
// If no valid activities were found, return the original response
|
|
94
|
+
if (activities.length === 0 && (response.values || []).length > 0) {
|
|
95
|
+
return response;
|
|
96
|
+
}
|
|
97
|
+
// Find PR author (usually the one who OPENED the PR)
|
|
98
|
+
const prAuthor = activities.find(a => a.action === 'OPENED')?.user;
|
|
99
|
+
// Count comments and unresolved threads
|
|
100
|
+
const comments = activities.filter(a => a.action === 'COMMENTED' && a.comment);
|
|
101
|
+
const unresolvedCount = comments.filter(a => a.comment && !a.comment.threadResolved).length;
|
|
102
|
+
return {
|
|
103
|
+
isLastPage: response.isLastPage ?? true,
|
|
104
|
+
activities,
|
|
105
|
+
summary: {
|
|
106
|
+
totalActivities: activities.length,
|
|
107
|
+
...(prAuthor && { prAuthor }),
|
|
108
|
+
commentCount: comments.length,
|
|
109
|
+
unresolvedCount
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function getCommentSummary(response) {
|
|
114
|
+
const commentSummaries = [];
|
|
115
|
+
for (const activity of response.values || []) {
|
|
116
|
+
// Use type guard to validate activity structure
|
|
117
|
+
if (isPRActivity(activity) && activity.action === 'COMMENTED' && activity.comment) {
|
|
118
|
+
// Additional validation for comment structure
|
|
119
|
+
if (isComment(activity.comment)) {
|
|
120
|
+
const comment = activity.comment;
|
|
121
|
+
commentSummaries.push(`${comment.author.displayName} on ${comment.anchor?.path || 'PR'}:${comment.anchor?.line || ''}: ${comment.text}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// If type guard fails, we skip the invalid activity but continue processing
|
|
125
|
+
}
|
|
126
|
+
return commentSummaries;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=pr-comment-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-comment-mapper.js","sourceRoot":"","sources":["../src/pr-comment-mapper.ts"],"names":[],"mappings":"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"}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
preset: 'ts-jest/presets/default-esm',
|
|
3
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
4
|
+
moduleNameMapper: {
|
|
5
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
6
|
+
'^@atlassian-dc-mcp/common$': '<rootDir>/../common/src/index.ts',
|
|
7
|
+
},
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.ts$': ['ts-jest', {
|
|
10
|
+
useESM: true,
|
|
11
|
+
}],
|
|
12
|
+
},
|
|
13
|
+
transformIgnorePatterns: [
|
|
14
|
+
'node_modules/(?!(@atlassian-dc-mcp)/)',
|
|
15
|
+
],
|
|
16
|
+
testEnvironment: 'node',
|
|
17
|
+
testMatch: ['**/__tests__/**/*.test.ts'],
|
|
18
|
+
collectCoverageFrom: [
|
|
19
|
+
'src/**/*.ts',
|
|
20
|
+
'!src/**/*.d.ts',
|
|
21
|
+
'!src/**/index.ts',
|
|
22
|
+
],
|
|
23
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlassian-dc-mcp/bitbucket",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./bin/run.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@atlassian-dc-mcp/common": "^0.
|
|
17
|
+
"@atlassian-dc-mcp/common": "^0.7.0",
|
|
18
18
|
"@modelcontextprotocol/sdk": "^1.5.0",
|
|
19
19
|
"dotenv": "^16.4.7",
|
|
20
20
|
"node-fetch": "^3.3.2",
|
|
@@ -22,12 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@modelcontextprotocol/inspector": "^0.4.1",
|
|
25
|
+
"@types/jest": "^30.0.0",
|
|
25
26
|
"lerna": "^8.2.0",
|
|
26
27
|
"nodemon": "^3.1.9",
|
|
28
|
+
"ts-jest": "^29.4.0",
|
|
27
29
|
"ts-node": "^10.9.2"
|
|
28
30
|
},
|
|
29
31
|
"publishConfig": {
|
|
30
32
|
"access": "public"
|
|
31
33
|
},
|
|
32
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "25617f5fb5da84c8bef2ed56fb6464b1a2c84262"
|
|
33
35
|
}
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { BitbucketService } from '../bitbucket-service.js';
|
|
2
|
+
import { PullRequestsService } from '../bitbucket-client/index.js';
|
|
3
|
+
|
|
4
|
+
// Mock the request function
|
|
5
|
+
jest.mock('../bitbucket-client/core/request.js', () => ({
|
|
6
|
+
request: jest.fn()
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
// Mock the PullRequestsService
|
|
10
|
+
jest.mock('../bitbucket-client/index.js', () => ({
|
|
11
|
+
PullRequestsService: {
|
|
12
|
+
streamRawDiff2: jest.fn(),
|
|
13
|
+
createComment2: jest.fn(),
|
|
14
|
+
streamChanges1: jest.fn()
|
|
15
|
+
},
|
|
16
|
+
OpenAPI: {
|
|
17
|
+
BASE: '',
|
|
18
|
+
TOKEN: '',
|
|
19
|
+
VERSION: ''
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
describe('BitbucketService', () => {
|
|
24
|
+
let bitbucketService: BitbucketService;
|
|
25
|
+
const mockProjectKey = 'TEST';
|
|
26
|
+
const mockRepositorySlug = 'test-repo';
|
|
27
|
+
const mockPullRequestId = '123';
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
bitbucketService = new BitbucketService('test-host', 'test-token');
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getPullRequestChanges', () => {
|
|
35
|
+
it('should successfully get PR changes', async () => {
|
|
36
|
+
const mockChangesData = {
|
|
37
|
+
values: [
|
|
38
|
+
{ path: { toString: 'file.txt' }, type: 'MODIFY' }
|
|
39
|
+
],
|
|
40
|
+
size: 1,
|
|
41
|
+
isLastPage: true
|
|
42
|
+
};
|
|
43
|
+
(PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockChangesData);
|
|
44
|
+
|
|
45
|
+
const result = await bitbucketService.getPullRequestChanges(
|
|
46
|
+
mockProjectKey,
|
|
47
|
+
mockRepositorySlug,
|
|
48
|
+
mockPullRequestId
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
expect(result.success).toBe(true);
|
|
52
|
+
expect(result.data).toBe(mockChangesData);
|
|
53
|
+
expect(PullRequestsService.streamChanges1).toHaveBeenCalledWith(
|
|
54
|
+
mockProjectKey,
|
|
55
|
+
mockPullRequestId,
|
|
56
|
+
mockRepositorySlug,
|
|
57
|
+
undefined,
|
|
58
|
+
undefined,
|
|
59
|
+
undefined,
|
|
60
|
+
undefined,
|
|
61
|
+
undefined,
|
|
62
|
+
25
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should successfully get PR changes with all parameters', async () => {
|
|
67
|
+
const mockChangesData = {
|
|
68
|
+
values: [
|
|
69
|
+
{ path: { toString: 'file.txt' }, type: 'MODIFY' }
|
|
70
|
+
],
|
|
71
|
+
size: 1,
|
|
72
|
+
isLastPage: true
|
|
73
|
+
};
|
|
74
|
+
(PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockChangesData);
|
|
75
|
+
|
|
76
|
+
const result = await bitbucketService.getPullRequestChanges(
|
|
77
|
+
mockProjectKey,
|
|
78
|
+
mockRepositorySlug,
|
|
79
|
+
mockPullRequestId,
|
|
80
|
+
'abc123',
|
|
81
|
+
'RANGE',
|
|
82
|
+
'def456',
|
|
83
|
+
'true',
|
|
84
|
+
0,
|
|
85
|
+
50
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result.success).toBe(true);
|
|
89
|
+
expect(result.data).toBe(mockChangesData);
|
|
90
|
+
expect(PullRequestsService.streamChanges1).toHaveBeenCalledWith(
|
|
91
|
+
mockProjectKey,
|
|
92
|
+
mockPullRequestId,
|
|
93
|
+
mockRepositorySlug,
|
|
94
|
+
'abc123',
|
|
95
|
+
'RANGE',
|
|
96
|
+
'def456',
|
|
97
|
+
'true',
|
|
98
|
+
0,
|
|
99
|
+
50
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should handle API errors gracefully', async () => {
|
|
104
|
+
const mockError = new Error('API Error');
|
|
105
|
+
(PullRequestsService.streamChanges1 as jest.Mock).mockRejectedValue(mockError);
|
|
106
|
+
|
|
107
|
+
const result = await bitbucketService.getPullRequestChanges(
|
|
108
|
+
mockProjectKey,
|
|
109
|
+
mockRepositorySlug,
|
|
110
|
+
mockPullRequestId
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(result.success).toBe(false);
|
|
114
|
+
expect(result.error).toBe('API Error');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('postPullRequestComment', () => {
|
|
119
|
+
it('should successfully post a general PR comment', async () => {
|
|
120
|
+
const mockComment = {
|
|
121
|
+
id: 12345,
|
|
122
|
+
text: 'Test comment',
|
|
123
|
+
author: { displayName: 'Test User' }
|
|
124
|
+
};
|
|
125
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
126
|
+
|
|
127
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
128
|
+
mockProjectKey,
|
|
129
|
+
mockRepositorySlug,
|
|
130
|
+
mockPullRequestId,
|
|
131
|
+
'Test comment'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
expect(result.data).toBe(mockComment);
|
|
136
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
137
|
+
mockProjectKey,
|
|
138
|
+
mockPullRequestId,
|
|
139
|
+
mockRepositorySlug,
|
|
140
|
+
{ text: 'Test comment' }
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should successfully post a reply comment', async () => {
|
|
145
|
+
const mockComment = {
|
|
146
|
+
id: 12346,
|
|
147
|
+
text: 'Reply comment',
|
|
148
|
+
author: { displayName: 'Test User' }
|
|
149
|
+
};
|
|
150
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
151
|
+
|
|
152
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
153
|
+
mockProjectKey,
|
|
154
|
+
mockRepositorySlug,
|
|
155
|
+
mockPullRequestId,
|
|
156
|
+
'Reply comment',
|
|
157
|
+
123 // parentId
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(result.success).toBe(true);
|
|
161
|
+
expect(result.data).toBe(mockComment);
|
|
162
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
163
|
+
mockProjectKey,
|
|
164
|
+
mockPullRequestId,
|
|
165
|
+
mockRepositorySlug,
|
|
166
|
+
{
|
|
167
|
+
text: 'Reply comment',
|
|
168
|
+
parent: { id: 123 }
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should successfully post a file comment', async () => {
|
|
174
|
+
const mockComment = {
|
|
175
|
+
id: 12347,
|
|
176
|
+
text: 'File comment',
|
|
177
|
+
author: { displayName: 'Test User' }
|
|
178
|
+
};
|
|
179
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
180
|
+
|
|
181
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
182
|
+
mockProjectKey,
|
|
183
|
+
mockRepositorySlug,
|
|
184
|
+
mockPullRequestId,
|
|
185
|
+
'File comment',
|
|
186
|
+
undefined, // parentId
|
|
187
|
+
'src/test.js' // filePath
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(result.success).toBe(true);
|
|
191
|
+
expect(result.data).toBe(mockComment);
|
|
192
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
193
|
+
mockProjectKey,
|
|
194
|
+
mockPullRequestId,
|
|
195
|
+
mockRepositorySlug,
|
|
196
|
+
{
|
|
197
|
+
text: 'File comment',
|
|
198
|
+
anchor: {
|
|
199
|
+
path: 'src/test.js',
|
|
200
|
+
diffType: 'EFFECTIVE'
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should successfully post a line comment', async () => {
|
|
207
|
+
const mockComment = {
|
|
208
|
+
id: 12348,
|
|
209
|
+
text: 'Line comment',
|
|
210
|
+
author: { displayName: 'Test User' }
|
|
211
|
+
};
|
|
212
|
+
(PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
213
|
+
|
|
214
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
215
|
+
mockProjectKey,
|
|
216
|
+
mockRepositorySlug,
|
|
217
|
+
mockPullRequestId,
|
|
218
|
+
'Line comment',
|
|
219
|
+
undefined, // parentId
|
|
220
|
+
'src/test.js', // filePath
|
|
221
|
+
42, // line
|
|
222
|
+
'ADDED' // lineType
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(result.success).toBe(true);
|
|
226
|
+
expect(result.data).toBe(mockComment);
|
|
227
|
+
expect(PullRequestsService.createComment2).toHaveBeenCalledWith(
|
|
228
|
+
mockProjectKey,
|
|
229
|
+
mockPullRequestId,
|
|
230
|
+
mockRepositorySlug,
|
|
231
|
+
{
|
|
232
|
+
text: 'Line comment',
|
|
233
|
+
anchor: {
|
|
234
|
+
path: 'src/test.js',
|
|
235
|
+
diffType: 'EFFECTIVE',
|
|
236
|
+
line: 42,
|
|
237
|
+
lineType: 'ADDED',
|
|
238
|
+
fileType: 'TO'
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should handle API errors gracefully', async () => {
|
|
245
|
+
const mockError = new Error('API Error');
|
|
246
|
+
(PullRequestsService.createComment2 as jest.Mock).mockRejectedValue(mockError);
|
|
247
|
+
|
|
248
|
+
const result = await bitbucketService.postPullRequestComment(
|
|
249
|
+
mockProjectKey,
|
|
250
|
+
mockRepositorySlug,
|
|
251
|
+
mockPullRequestId,
|
|
252
|
+
'Test comment'
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(result.success).toBe(false);
|
|
256
|
+
expect(result.error).toBe('API Error');
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe('getPullRequestDiff', () => {
|
|
261
|
+
const { request: mockRequest } = require('../bitbucket-client/core/request.js');
|
|
262
|
+
|
|
263
|
+
it('should successfully get raw diff with minimal parameters', async () => {
|
|
264
|
+
const mockRawDiff = 'diff --git a/file.txt b/file.txt\nindex 1234567..abcdefg 100644\n--- a/file.txt\n+++ b/file.txt\n@@ -1,3 +1,4 @@\n line1\n line2\n+new line\n line3';
|
|
265
|
+
mockRequest.mockResolvedValue(mockRawDiff);
|
|
266
|
+
|
|
267
|
+
const result = await bitbucketService.getPullRequestDiff(
|
|
268
|
+
mockProjectKey,
|
|
269
|
+
mockRepositorySlug,
|
|
270
|
+
mockPullRequestId,
|
|
271
|
+
'src/file.txt'
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
expect(result.success).toBe(true);
|
|
275
|
+
expect(result.data).toBe(mockRawDiff);
|
|
276
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
277
|
+
expect.any(Object), // OpenAPI config
|
|
278
|
+
{
|
|
279
|
+
method: 'GET',
|
|
280
|
+
url: '/api/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/diff/{path}',
|
|
281
|
+
path: {
|
|
282
|
+
'path': 'src/file.txt',
|
|
283
|
+
'projectKey': mockProjectKey,
|
|
284
|
+
'pullRequestId': mockPullRequestId,
|
|
285
|
+
'repositorySlug': mockRepositorySlug,
|
|
286
|
+
},
|
|
287
|
+
query: {
|
|
288
|
+
'contextLines': undefined,
|
|
289
|
+
'sinceId': undefined,
|
|
290
|
+
'srcPath': undefined,
|
|
291
|
+
'diffType': undefined,
|
|
292
|
+
'untilId': undefined,
|
|
293
|
+
'whitespace': undefined,
|
|
294
|
+
},
|
|
295
|
+
headers: {
|
|
296
|
+
'Accept': 'text/plain'
|
|
297
|
+
},
|
|
298
|
+
errors: {
|
|
299
|
+
400: `If the request was malformed.`,
|
|
300
|
+
401: `The currently authenticated user has insufficient permissions to view the repository or pull request.`,
|
|
301
|
+
404: `The repository or pull request does not exist.`,
|
|
302
|
+
},
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should successfully get raw diff with all parameters', async () => {
|
|
308
|
+
const mockRawDiff = 'diff --git a/old/file.txt b/new/file.txt\nindex 1234567..abcdefg 100644\n--- a/old/file.txt\n+++ b/new/file.txt\n@@ -1,5 +1,6 @@\n line1\n line2\n+new line\n line3\n line4\n line5';
|
|
309
|
+
mockRequest.mockResolvedValue(mockRawDiff);
|
|
310
|
+
|
|
311
|
+
const result = await bitbucketService.getPullRequestDiff(
|
|
312
|
+
mockProjectKey,
|
|
313
|
+
mockRepositorySlug,
|
|
314
|
+
mockPullRequestId,
|
|
315
|
+
'src/file.txt',
|
|
316
|
+
'5', // contextLines
|
|
317
|
+
'abc123', // sinceId
|
|
318
|
+
'old/file.txt', // srcPath
|
|
319
|
+
'EFFECTIVE', // diffType
|
|
320
|
+
'def456', // untilId
|
|
321
|
+
'ignore-all' // whitespace
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(result.success).toBe(true);
|
|
325
|
+
expect(result.data).toBe(mockRawDiff);
|
|
326
|
+
expect(mockRequest).toHaveBeenCalledWith(
|
|
327
|
+
expect.any(Object), // OpenAPI config
|
|
328
|
+
{
|
|
329
|
+
method: 'GET',
|
|
330
|
+
url: '/api/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/diff/{path}',
|
|
331
|
+
path: {
|
|
332
|
+
'path': 'src/file.txt',
|
|
333
|
+
'projectKey': mockProjectKey,
|
|
334
|
+
'pullRequestId': mockPullRequestId,
|
|
335
|
+
'repositorySlug': mockRepositorySlug,
|
|
336
|
+
},
|
|
337
|
+
query: {
|
|
338
|
+
'contextLines': '5',
|
|
339
|
+
'sinceId': 'abc123',
|
|
340
|
+
'srcPath': 'old/file.txt',
|
|
341
|
+
'diffType': 'EFFECTIVE',
|
|
342
|
+
'untilId': 'def456',
|
|
343
|
+
'whitespace': 'ignore-all',
|
|
344
|
+
},
|
|
345
|
+
headers: {
|
|
346
|
+
'Accept': 'text/plain'
|
|
347
|
+
},
|
|
348
|
+
errors: {
|
|
349
|
+
400: `If the request was malformed.`,
|
|
350
|
+
401: `The currently authenticated user has insufficient permissions to view the repository or pull request.`,
|
|
351
|
+
404: `The repository or pull request does not exist.`,
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should handle API errors gracefully', async () => {
|
|
358
|
+
const mockError = new Error('API Error');
|
|
359
|
+
mockRequest.mockRejectedValue(mockError);
|
|
360
|
+
|
|
361
|
+
const result = await bitbucketService.getPullRequestDiff(
|
|
362
|
+
mockProjectKey,
|
|
363
|
+
mockRepositorySlug,
|
|
364
|
+
mockPullRequestId,
|
|
365
|
+
'src/file.txt'
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
expect(result.success).toBe(false);
|
|
369
|
+
expect(result.error).toBe('API Error');
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe('validateConfig', () => {
|
|
374
|
+
const originalEnv = process.env;
|
|
375
|
+
|
|
376
|
+
beforeEach(() => {
|
|
377
|
+
jest.resetModules();
|
|
378
|
+
process.env = { ...originalEnv };
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
afterAll(() => {
|
|
382
|
+
process.env = originalEnv;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should return empty array when all required env vars are present', () => {
|
|
386
|
+
process.env.BITBUCKET_API_TOKEN = 'test-token';
|
|
387
|
+
process.env.BITBUCKET_HOST = 'test-host';
|
|
388
|
+
|
|
389
|
+
const missingVars = BitbucketService.validateConfig();
|
|
390
|
+
expect(missingVars).toEqual([]);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should return missing vars when BITBUCKET_API_TOKEN is missing', () => {
|
|
394
|
+
delete process.env.BITBUCKET_API_TOKEN;
|
|
395
|
+
process.env.BITBUCKET_HOST = 'test-host';
|
|
396
|
+
|
|
397
|
+
const missingVars = BitbucketService.validateConfig();
|
|
398
|
+
expect(missingVars).toContain('BITBUCKET_API_TOKEN');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('should return missing vars when both host options are missing', () => {
|
|
402
|
+
process.env.BITBUCKET_API_TOKEN = 'test-token';
|
|
403
|
+
delete process.env.BITBUCKET_HOST;
|
|
404
|
+
delete process.env.BITBUCKET_API_BASE_PATH;
|
|
405
|
+
|
|
406
|
+
const missingVars = BitbucketService.validateConfig();
|
|
407
|
+
expect(missingVars).toContain('BITBUCKET_HOST or BITBUCKET_API_BASE_PATH');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should accept BITBUCKET_API_BASE_PATH as alternative to BITBUCKET_HOST', () => {
|
|
411
|
+
process.env.BITBUCKET_API_TOKEN = 'test-token';
|
|
412
|
+
delete process.env.BITBUCKET_HOST;
|
|
413
|
+
process.env.BITBUCKET_API_BASE_PATH = 'https://test-host/rest';
|
|
414
|
+
|
|
415
|
+
const missingVars = BitbucketService.validateConfig();
|
|
416
|
+
expect(missingVars).toEqual([]);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|