@atlassian-dc-mcp/bitbucket 0.29.0 → 0.31.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 +25 -0
- package/README.md +56 -2
- package/build/__tests__/bitbucket-service.test.js +60 -2
- package/build/__tests__/bitbucket-service.test.js.map +1 -1
- package/build/__tests__/bitbucket-token-optimization.test.js +11 -0
- package/build/__tests__/bitbucket-token-optimization.test.js.map +1 -1
- package/build/__tests__/merge-gateway.test.d.ts +2 -0
- package/build/__tests__/merge-gateway.test.d.ts.map +1 -0
- package/build/__tests__/merge-gateway.test.js +98 -0
- package/build/__tests__/merge-gateway.test.js.map +1 -0
- package/build/__tests__/pr-comment-mapper.test.js +91 -11
- package/build/__tests__/pr-comment-mapper.test.js.map +1 -1
- package/build/__tests__/pr-merge.test.d.ts +2 -0
- package/build/__tests__/pr-merge.test.d.ts.map +1 -0
- package/build/__tests__/pr-merge.test.js +166 -0
- package/build/__tests__/pr-merge.test.js.map +1 -0
- package/build/bitbucket-response-mapper.d.ts +10 -0
- package/build/bitbucket-response-mapper.d.ts.map +1 -1
- package/build/bitbucket-response-mapper.js +16 -0
- package/build/bitbucket-response-mapper.js.map +1 -1
- package/build/bitbucket-service.d.ts +68 -9
- package/build/bitbucket-service.d.ts.map +1 -1
- package/build/bitbucket-service.js +91 -15
- package/build/bitbucket-service.js.map +1 -1
- package/build/index.js +34 -4
- package/build/index.js.map +1 -1
- package/build/merge-gateway.d.ts +34 -0
- package/build/merge-gateway.d.ts.map +1 -0
- package/build/merge-gateway.js +86 -0
- package/build/merge-gateway.js.map +1 -0
- package/build/pr-comment-mapper.d.ts +3 -0
- package/build/pr-comment-mapper.d.ts.map +1 -1
- package/build/pr-comment-mapper.js +8 -3
- package/build/pr-comment-mapper.js.map +1 -1
- package/build/pr-merge.d.ts +23 -0
- package/build/pr-merge.d.ts.map +1 -0
- package/build/pr-merge.js +59 -0
- package/build/pr-merge.js.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/bitbucket-service.test.ts +154 -2
- package/src/__tests__/bitbucket-token-optimization.test.ts +11 -0
- package/src/__tests__/merge-gateway.test.ts +119 -0
- package/src/__tests__/pr-comment-mapper.test.ts +95 -11
- package/src/__tests__/pr-merge.test.ts +206 -0
- package/src/bitbucket-response-mapper.ts +27 -0
- package/src/bitbucket-service.ts +100 -14
- package/src/index.ts +55 -6
- package/src/merge-gateway.ts +132 -0
- package/src/pr-comment-mapper.ts +11 -3
- package/src/pr-merge.ts +94 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { initializeRuntimeConfig } from '@atlassian-dc-mcp/common';
|
|
5
5
|
import { BitbucketService } from '../bitbucket-service.js';
|
|
6
|
-
import { PullRequestsService } from '../bitbucket-client/index.js';
|
|
6
|
+
import { PullRequestsService, RepositoryService } from '../bitbucket-client/index.js';
|
|
7
7
|
import { request as mockRequest } from '../bitbucket-client/core/request.js';
|
|
8
8
|
|
|
9
9
|
// Mock the request function
|
|
@@ -25,6 +25,9 @@ jest.mock('../bitbucket-client/index.js', () => ({
|
|
|
25
25
|
getReviewers: jest.fn(),
|
|
26
26
|
get3: jest.fn()
|
|
27
27
|
},
|
|
28
|
+
RepositoryService: {
|
|
29
|
+
streamRaw: jest.fn()
|
|
30
|
+
},
|
|
28
31
|
OpenAPI: {
|
|
29
32
|
BASE: '',
|
|
30
33
|
TOKEN: '',
|
|
@@ -1209,6 +1212,78 @@ describe('BitbucketService', () => {
|
|
|
1209
1212
|
});
|
|
1210
1213
|
});
|
|
1211
1214
|
|
|
1215
|
+
describe('getFileContent', () => {
|
|
1216
|
+
const mockFileContent = 'FROM node:20-alpine\n\nWORKDIR /app\n';
|
|
1217
|
+
|
|
1218
|
+
it('should fetch a file at the repository default branch when at is omitted', async () => {
|
|
1219
|
+
(RepositoryService.streamRaw as jest.Mock).mockResolvedValue(mockFileContent);
|
|
1220
|
+
|
|
1221
|
+
const result = await bitbucketService.getFileContent(mockProjectKey, mockRepositorySlug, 'Dockerfile');
|
|
1222
|
+
|
|
1223
|
+
expect(result.success).toBe(true);
|
|
1224
|
+
expect(result.data).toBe(mockFileContent);
|
|
1225
|
+
expect(RepositoryService.streamRaw).toHaveBeenCalledWith(
|
|
1226
|
+
'Dockerfile',
|
|
1227
|
+
mockProjectKey,
|
|
1228
|
+
mockRepositorySlug,
|
|
1229
|
+
undefined
|
|
1230
|
+
);
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
it('should pass the ref through and keep nested paths intact', async () => {
|
|
1234
|
+
(RepositoryService.streamRaw as jest.Mock).mockResolvedValue(mockFileContent);
|
|
1235
|
+
|
|
1236
|
+
await bitbucketService.getFileContent(
|
|
1237
|
+
mockProjectKey,
|
|
1238
|
+
mockRepositorySlug,
|
|
1239
|
+
'deploy/base/configmap.yaml',
|
|
1240
|
+
'refs/heads/main'
|
|
1241
|
+
);
|
|
1242
|
+
|
|
1243
|
+
expect(RepositoryService.streamRaw).toHaveBeenCalledWith(
|
|
1244
|
+
'deploy/base/configmap.yaml',
|
|
1245
|
+
mockProjectKey,
|
|
1246
|
+
mockRepositorySlug,
|
|
1247
|
+
'refs/heads/main'
|
|
1248
|
+
);
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
it('should strip a leading slash from the path', async () => {
|
|
1252
|
+
(RepositoryService.streamRaw as jest.Mock).mockResolvedValue(mockFileContent);
|
|
1253
|
+
|
|
1254
|
+
await bitbucketService.getFileContent(mockProjectKey, mockRepositorySlug, '/src/index.ts');
|
|
1255
|
+
|
|
1256
|
+
expect(RepositoryService.streamRaw).toHaveBeenCalledWith(
|
|
1257
|
+
'src/index.ts',
|
|
1258
|
+
mockProjectKey,
|
|
1259
|
+
mockRepositorySlug,
|
|
1260
|
+
undefined
|
|
1261
|
+
);
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
it('should normalize project key and repository slug casing', async () => {
|
|
1265
|
+
(RepositoryService.streamRaw as jest.Mock).mockResolvedValue(mockFileContent);
|
|
1266
|
+
|
|
1267
|
+
await bitbucketService.getFileContent('test', 'TEST-REPO', 'Dockerfile');
|
|
1268
|
+
|
|
1269
|
+
expect(RepositoryService.streamRaw).toHaveBeenCalledWith(
|
|
1270
|
+
'Dockerfile',
|
|
1271
|
+
'TEST',
|
|
1272
|
+
'test-repo',
|
|
1273
|
+
undefined
|
|
1274
|
+
);
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
it('should handle API errors gracefully', async () => {
|
|
1278
|
+
(RepositoryService.streamRaw as jest.Mock).mockRejectedValue(new Error('API Error'));
|
|
1279
|
+
|
|
1280
|
+
const result = await bitbucketService.getFileContent(mockProjectKey, mockRepositorySlug, 'missing.txt');
|
|
1281
|
+
|
|
1282
|
+
expect(result.success).toBe(false);
|
|
1283
|
+
expect(result.error).toBe('API Error');
|
|
1284
|
+
});
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1212
1287
|
describe('createPullRequest', () => {
|
|
1213
1288
|
it('should successfully create a PR with minimal parameters', async () => {
|
|
1214
1289
|
const mockPullRequest = {
|
|
@@ -2166,7 +2241,7 @@ describe('BitbucketService', () => {
|
|
|
2166
2241
|
const mockComment = { id: 502, version: 4, text: 'New body', state: 'RESOLVED', severity: 'BLOCKER' };
|
|
2167
2242
|
(PullRequestsService.updateComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
2168
2243
|
|
|
2169
|
-
await bitbucketService.updatePullRequestComment(
|
|
2244
|
+
const result = await bitbucketService.updatePullRequestComment(
|
|
2170
2245
|
mockProjectKey,
|
|
2171
2246
|
mockRepositorySlug,
|
|
2172
2247
|
mockPullRequestId,
|
|
@@ -2184,6 +2259,83 @@ describe('BitbucketService', () => {
|
|
|
2184
2259
|
mockRepositorySlug,
|
|
2185
2260
|
{ version: 3, text: 'New body', state: 'RESOLVED', severity: 'BLOCKER' }
|
|
2186
2261
|
);
|
|
2262
|
+
expect(result.data).toMatchObject({ state: 'RESOLVED', severity: 'BLOCKER' });
|
|
2263
|
+
});
|
|
2264
|
+
|
|
2265
|
+
it('should resolve a comment thread by sending threadResolved true', async () => {
|
|
2266
|
+
const mockComment = { id: 504, version: 3, state: 'OPEN', threadResolved: true };
|
|
2267
|
+
(PullRequestsService.updateComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
2268
|
+
|
|
2269
|
+
const result = await bitbucketService.updatePullRequestComment(
|
|
2270
|
+
mockProjectKey,
|
|
2271
|
+
mockRepositorySlug,
|
|
2272
|
+
mockPullRequestId,
|
|
2273
|
+
'504',
|
|
2274
|
+
2,
|
|
2275
|
+
undefined, // text
|
|
2276
|
+
undefined, // state
|
|
2277
|
+
undefined, // severity
|
|
2278
|
+
true // threadResolved
|
|
2279
|
+
);
|
|
2280
|
+
|
|
2281
|
+
expect(PullRequestsService.updateComment2).toHaveBeenCalledWith(
|
|
2282
|
+
mockProjectKey,
|
|
2283
|
+
'504',
|
|
2284
|
+
mockPullRequestId,
|
|
2285
|
+
mockRepositorySlug,
|
|
2286
|
+
{ version: 2, threadResolved: true }
|
|
2287
|
+
);
|
|
2288
|
+
expect(result.data).toMatchObject({ state: 'OPEN', threadResolved: true });
|
|
2289
|
+
});
|
|
2290
|
+
|
|
2291
|
+
it('should reopen a comment thread by sending threadResolved false', async () => {
|
|
2292
|
+
const mockComment = { id: 505, version: 4, threadResolved: false };
|
|
2293
|
+
(PullRequestsService.updateComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
2294
|
+
|
|
2295
|
+
await bitbucketService.updatePullRequestComment(
|
|
2296
|
+
mockProjectKey,
|
|
2297
|
+
mockRepositorySlug,
|
|
2298
|
+
mockPullRequestId,
|
|
2299
|
+
'505',
|
|
2300
|
+
3,
|
|
2301
|
+
undefined,
|
|
2302
|
+
undefined,
|
|
2303
|
+
undefined,
|
|
2304
|
+
false
|
|
2305
|
+
);
|
|
2306
|
+
|
|
2307
|
+
expect(PullRequestsService.updateComment2).toHaveBeenCalledWith(
|
|
2308
|
+
mockProjectKey,
|
|
2309
|
+
'505',
|
|
2310
|
+
mockPullRequestId,
|
|
2311
|
+
mockRepositorySlug,
|
|
2312
|
+
{ version: 3, threadResolved: false }
|
|
2313
|
+
);
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
it('should set task state and thread resolution independently in one update', async () => {
|
|
2317
|
+
const mockComment = { id: 506, version: 5, state: 'OPEN', threadResolved: true };
|
|
2318
|
+
(PullRequestsService.updateComment2 as jest.Mock).mockResolvedValue(mockComment);
|
|
2319
|
+
|
|
2320
|
+
await bitbucketService.updatePullRequestComment(
|
|
2321
|
+
mockProjectKey,
|
|
2322
|
+
mockRepositorySlug,
|
|
2323
|
+
mockPullRequestId,
|
|
2324
|
+
'506',
|
|
2325
|
+
4,
|
|
2326
|
+
undefined,
|
|
2327
|
+
'OPEN',
|
|
2328
|
+
undefined,
|
|
2329
|
+
true
|
|
2330
|
+
);
|
|
2331
|
+
|
|
2332
|
+
expect(PullRequestsService.updateComment2).toHaveBeenCalledWith(
|
|
2333
|
+
mockProjectKey,
|
|
2334
|
+
'506',
|
|
2335
|
+
mockPullRequestId,
|
|
2336
|
+
mockRepositorySlug,
|
|
2337
|
+
{ version: 4, state: 'OPEN', threadResolved: true }
|
|
2338
|
+
);
|
|
2187
2339
|
});
|
|
2188
2340
|
|
|
2189
2341
|
it('should propagate API errors', async () => {
|
|
@@ -143,6 +143,7 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
143
143
|
comments: [],
|
|
144
144
|
threadResolved: false,
|
|
145
145
|
state: 'OPEN',
|
|
146
|
+
severity: 'NORMAL',
|
|
146
147
|
},
|
|
147
148
|
},
|
|
148
149
|
],
|
|
@@ -151,6 +152,8 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
151
152
|
prAuthor: { name: 'author', displayName: 'Author' },
|
|
152
153
|
commentCount: 1,
|
|
153
154
|
unresolvedCount: 1,
|
|
155
|
+
blockerCount: 0,
|
|
156
|
+
unresolvedBlockerCount: 0,
|
|
154
157
|
},
|
|
155
158
|
});
|
|
156
159
|
expect(PullRequestsService.getActivities).toHaveBeenCalledWith('TEST', '123', 'repo', undefined, undefined, undefined, 25);
|
|
@@ -202,6 +205,8 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
202
205
|
prAuthor: { name: 'author', displayName: 'Author' },
|
|
203
206
|
commentCount: 0,
|
|
204
207
|
unresolvedCount: 0,
|
|
208
|
+
blockerCount: 0,
|
|
209
|
+
unresolvedBlockerCount: 0,
|
|
205
210
|
},
|
|
206
211
|
});
|
|
207
212
|
expect(includeResolvedResult.success).toBe(true);
|
|
@@ -238,10 +243,12 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
238
243
|
comments: [],
|
|
239
244
|
threadResolved: true,
|
|
240
245
|
state: 'OPEN',
|
|
246
|
+
severity: 'NORMAL',
|
|
241
247
|
},
|
|
242
248
|
],
|
|
243
249
|
threadResolved: true,
|
|
244
250
|
state: 'OPEN',
|
|
251
|
+
severity: 'NORMAL',
|
|
245
252
|
},
|
|
246
253
|
},
|
|
247
254
|
],
|
|
@@ -250,6 +257,8 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
250
257
|
prAuthor: { name: 'author', displayName: 'Author' },
|
|
251
258
|
commentCount: 1,
|
|
252
259
|
unresolvedCount: 0,
|
|
260
|
+
blockerCount: 0,
|
|
261
|
+
unresolvedBlockerCount: 0,
|
|
253
262
|
},
|
|
254
263
|
});
|
|
255
264
|
});
|
|
@@ -267,6 +276,8 @@ describe('BitbucketService token optimization paths', () => {
|
|
|
267
276
|
prAuthor: { name: 'author', displayName: 'Author' },
|
|
268
277
|
commentCount: 1,
|
|
269
278
|
unresolvedCount: 1,
|
|
279
|
+
blockerCount: 0,
|
|
280
|
+
unresolvedBlockerCount: 0,
|
|
270
281
|
},
|
|
271
282
|
items: ['Reviewer on src/app.ts:10: Looks good'],
|
|
272
283
|
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertRepoMergeAllowed,
|
|
3
|
+
assertTargetRefMergeAllowed,
|
|
4
|
+
resolveMergeGateway,
|
|
5
|
+
} from '../merge-gateway.js';
|
|
6
|
+
|
|
7
|
+
const silentWarn = () => undefined;
|
|
8
|
+
|
|
9
|
+
describe('resolveMergeGateway', () => {
|
|
10
|
+
it('is disabled by default', () => {
|
|
11
|
+
const gateway = resolveMergeGateway({ env: {}, warn: silentWarn });
|
|
12
|
+
expect(gateway.enabled).toBe(false);
|
|
13
|
+
expect(gateway.repos).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('stays disabled when enabled without any repository', () => {
|
|
17
|
+
const gateway = resolveMergeGateway({ env: { BITBUCKET_MERGE_ENABLED: 'true' }, warn: silentWarn });
|
|
18
|
+
expect(gateway.enabled).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('stays disabled when every configured repository entry is malformed', () => {
|
|
22
|
+
const gateway = resolveMergeGateway({
|
|
23
|
+
env: { BITBUCKET_MERGE_ENABLED: 'true', BITBUCKET_MERGE_ALLOWED_REPOS: 'demo, PROJ/a/b, */*' },
|
|
24
|
+
warn: silentWarn,
|
|
25
|
+
});
|
|
26
|
+
expect(gateway.enabled).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('normalizes repository entries and de-duplicates them', () => {
|
|
30
|
+
const gateway = resolveMergeGateway({
|
|
31
|
+
env: {
|
|
32
|
+
BITBUCKET_MERGE_ENABLED: 'yes',
|
|
33
|
+
BITBUCKET_MERGE_ALLOWED_REPOS: 'proj/Demo, PROJ/demo; OTHER/*',
|
|
34
|
+
},
|
|
35
|
+
warn: silentWarn,
|
|
36
|
+
});
|
|
37
|
+
expect(gateway.enabled).toBe(true);
|
|
38
|
+
expect(gateway.repos).toEqual(['PROJ/demo', 'OTHER/*']);
|
|
39
|
+
expect(gateway.targetRefs).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('expands bare branch names into fully-qualified target refs', () => {
|
|
43
|
+
const gateway = resolveMergeGateway({
|
|
44
|
+
env: {
|
|
45
|
+
BITBUCKET_MERGE_ENABLED: '1',
|
|
46
|
+
BITBUCKET_MERGE_ALLOWED_REPOS: 'PROJ/demo',
|
|
47
|
+
BITBUCKET_MERGE_ALLOWED_TARGET_REFS: 'develop, refs/heads/release/*',
|
|
48
|
+
},
|
|
49
|
+
warn: silentWarn,
|
|
50
|
+
});
|
|
51
|
+
expect(gateway.targetRefs).toEqual(['refs/heads/develop', 'refs/heads/release/*']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('warns about ignored entries and about being enabled with no valid repository', () => {
|
|
55
|
+
const warnings: string[] = [];
|
|
56
|
+
resolveMergeGateway({
|
|
57
|
+
env: { BITBUCKET_MERGE_ENABLED: 'true', BITBUCKET_MERGE_ALLOWED_REPOS: 'demo' },
|
|
58
|
+
warn: message => warnings.push(message),
|
|
59
|
+
});
|
|
60
|
+
expect(warnings).toHaveLength(2);
|
|
61
|
+
expect(warnings[0]).toContain('"demo"');
|
|
62
|
+
expect(warnings[1]).toContain('stay disabled');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('assertRepoMergeAllowed', () => {
|
|
67
|
+
const gateway = resolveMergeGateway({
|
|
68
|
+
env: { BITBUCKET_MERGE_ENABLED: 'true', BITBUCKET_MERGE_ALLOWED_REPOS: 'PROJ/demo, OTHER/*' },
|
|
69
|
+
warn: silentWarn,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('refuses everything when merging is disabled', () => {
|
|
73
|
+
expect(() => assertRepoMergeAllowed({ enabled: false, repos: [], targetRefs: [] }, 'PROJ', 'demo'))
|
|
74
|
+
.toThrow(/disabled on this server/);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('allows an exact repository regardless of the casing used by the caller', () => {
|
|
78
|
+
expect(() => assertRepoMergeAllowed(gateway, 'proj', 'DEMO')).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('allows any repository in a wildcard project', () => {
|
|
82
|
+
expect(() => assertRepoMergeAllowed(gateway, 'OTHER', 'anything')).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('refuses a repository outside the allowed list', () => {
|
|
86
|
+
expect(() => assertRepoMergeAllowed(gateway, 'PROJ', 'other-repo')).toThrow(/not allowed in PROJ\/other-repo/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('does not treat a wildcard project as a prefix of another project', () => {
|
|
90
|
+
expect(() => assertRepoMergeAllowed(gateway, 'OTHERS', 'demo')).toThrow(/not allowed/);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('assertTargetRefMergeAllowed', () => {
|
|
95
|
+
const unrestricted = { enabled: true, repos: ['PROJ/demo'], targetRefs: [] };
|
|
96
|
+
const restricted = {
|
|
97
|
+
enabled: true,
|
|
98
|
+
repos: ['PROJ/demo'],
|
|
99
|
+
targetRefs: ['refs/heads/develop', 'refs/heads/release/*'],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
it('allows any ref when no target-ref restriction is configured', () => {
|
|
103
|
+
expect(() => assertTargetRefMergeAllowed(unrestricted, 'refs/heads/master')).not.toThrow();
|
|
104
|
+
expect(() => assertTargetRefMergeAllowed(unrestricted, undefined)).not.toThrow();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('allows an exact and a wildcard target ref', () => {
|
|
108
|
+
expect(() => assertTargetRefMergeAllowed(restricted, 'refs/heads/develop')).not.toThrow();
|
|
109
|
+
expect(() => assertTargetRefMergeAllowed(restricted, 'refs/heads/release/1.2')).not.toThrow();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('refuses a target ref outside the allowed list', () => {
|
|
113
|
+
expect(() => assertTargetRefMergeAllowed(restricted, 'refs/heads/master')).toThrow(/refs\/heads\/master is not allowed/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('refuses when the target ref is unknown but restrictions apply', () => {
|
|
117
|
+
expect(() => assertTargetRefMergeAllowed(restricted, undefined)).toThrow(/Could not determine the target branch/);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -140,7 +140,8 @@ describe('PR Comment Mapper', () => {
|
|
|
140
140
|
},
|
|
141
141
|
comments: [],
|
|
142
142
|
threadResolved: false,
|
|
143
|
-
state: "OPEN"
|
|
143
|
+
state: "OPEN",
|
|
144
|
+
severity: "NORMAL"
|
|
144
145
|
}
|
|
145
146
|
},
|
|
146
147
|
{
|
|
@@ -160,13 +161,85 @@ describe('PR Comment Mapper', () => {
|
|
|
160
161
|
displayName: "User B"
|
|
161
162
|
},
|
|
162
163
|
commentCount: 1,
|
|
163
|
-
unresolvedCount: 1
|
|
164
|
+
unresolvedCount: 1,
|
|
165
|
+
blockerCount: 0,
|
|
166
|
+
unresolvedBlockerCount: 0
|
|
164
167
|
}
|
|
165
168
|
};
|
|
166
169
|
|
|
167
170
|
expect(result).toEqual(expectedResult);
|
|
168
171
|
});
|
|
169
172
|
|
|
173
|
+
it('should expose BLOCKER severity so tasks are distinguishable from normal comments', () => {
|
|
174
|
+
const blockerResponse: BitbucketPRApiResponse = {
|
|
175
|
+
...validPRResponse,
|
|
176
|
+
values: [
|
|
177
|
+
{
|
|
178
|
+
id: 1001,
|
|
179
|
+
createdDate: 1600000000000,
|
|
180
|
+
user: createUser('reviewer1', 106, 'Reviewer One'),
|
|
181
|
+
action: 'COMMENTED',
|
|
182
|
+
commentAction: 'ADDED',
|
|
183
|
+
comment: createComment({
|
|
184
|
+
id: 2010,
|
|
185
|
+
text: 'Blocking: fix this',
|
|
186
|
+
severity: 'BLOCKER',
|
|
187
|
+
comments: [
|
|
188
|
+
createComment({
|
|
189
|
+
id: 2011,
|
|
190
|
+
text: 'Reply keeps its own severity',
|
|
191
|
+
anchor: undefined,
|
|
192
|
+
severity: 'NORMAL'
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const result = simplifyBitbucketPRComments(blockerResponse) as SimplifiedPRResponse;
|
|
201
|
+
expect(result.activities[0].comment?.severity).toBe('BLOCKER');
|
|
202
|
+
expect(result.activities[0].comment?.comments[0].severity).toBe('NORMAL');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should count blockers and unresolved blockers by task state in the summary', () => {
|
|
206
|
+
const blockerResponse: BitbucketPRApiResponse = {
|
|
207
|
+
...validPRResponse,
|
|
208
|
+
values: [
|
|
209
|
+
openedActivity!,
|
|
210
|
+
{
|
|
211
|
+
id: 1010,
|
|
212
|
+
createdDate: 1600000003000,
|
|
213
|
+
user: createUser('reviewer1', 106, 'Reviewer One'),
|
|
214
|
+
action: 'COMMENTED',
|
|
215
|
+
commentAction: 'ADDED',
|
|
216
|
+
comment: createComment({ id: 2020, text: 'Unresolved task', severity: 'BLOCKER', state: 'OPEN' })
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 1011,
|
|
220
|
+
createdDate: 1600000004000,
|
|
221
|
+
user: createUser('reviewer1', 106, 'Reviewer One'),
|
|
222
|
+
action: 'COMMENTED',
|
|
223
|
+
commentAction: 'ADDED',
|
|
224
|
+
comment: createComment({ id: 2021, text: 'Ticked task', severity: 'BLOCKER', state: 'RESOLVED' })
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 1012,
|
|
228
|
+
createdDate: 1600000005000,
|
|
229
|
+
user: createUser('reviewer1', 106, 'Reviewer One'),
|
|
230
|
+
action: 'COMMENTED',
|
|
231
|
+
commentAction: 'ADDED',
|
|
232
|
+
comment: createComment({ id: 2022, text: 'Just a note', severity: 'NORMAL', state: 'OPEN' })
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const result = simplifyBitbucketPRComments(blockerResponse) as SimplifiedPRResponse;
|
|
238
|
+
expect(result.summary.commentCount).toBe(3);
|
|
239
|
+
expect(result.summary.blockerCount).toBe(2);
|
|
240
|
+
expect(result.summary.unresolvedBlockerCount).toBe(1);
|
|
241
|
+
});
|
|
242
|
+
|
|
170
243
|
it('should preserve nested replies recursively', () => {
|
|
171
244
|
const threadedResponse: BitbucketPRApiResponse = {
|
|
172
245
|
...validPRResponse,
|
|
@@ -220,11 +293,13 @@ describe('PR Comment Mapper', () => {
|
|
|
220
293
|
createdDate: 1600000000000,
|
|
221
294
|
comments: [],
|
|
222
295
|
threadResolved: false,
|
|
223
|
-
state: "OPEN"
|
|
296
|
+
state: "OPEN",
|
|
297
|
+
severity: "NORMAL"
|
|
224
298
|
}
|
|
225
299
|
],
|
|
226
300
|
threadResolved: false,
|
|
227
|
-
state: "OPEN"
|
|
301
|
+
state: "OPEN",
|
|
302
|
+
severity: "NORMAL"
|
|
228
303
|
}
|
|
229
304
|
]);
|
|
230
305
|
});
|
|
@@ -294,7 +369,8 @@ describe('PR Comment Mapper', () => {
|
|
|
294
369
|
},
|
|
295
370
|
comments: [],
|
|
296
371
|
threadResolved: false,
|
|
297
|
-
state: 'OPEN'
|
|
372
|
+
state: 'OPEN',
|
|
373
|
+
severity: 'NORMAL'
|
|
298
374
|
}
|
|
299
375
|
}
|
|
300
376
|
]);
|
|
@@ -355,11 +431,13 @@ describe('PR Comment Mapper', () => {
|
|
|
355
431
|
createdDate: 1600000000000,
|
|
356
432
|
comments: [],
|
|
357
433
|
threadResolved: true,
|
|
358
|
-
state: 'OPEN'
|
|
434
|
+
state: 'OPEN',
|
|
435
|
+
severity: 'NORMAL'
|
|
359
436
|
}
|
|
360
437
|
],
|
|
361
438
|
threadResolved: true,
|
|
362
|
-
state: 'OPEN'
|
|
439
|
+
state: 'OPEN',
|
|
440
|
+
severity: 'NORMAL'
|
|
363
441
|
});
|
|
364
442
|
expect(getCommentSummary(resolvedThreadResponse, { includeResolved: true })).toEqual([
|
|
365
443
|
'User A on config.yml:6: Resolved thread root'
|
|
@@ -402,7 +480,8 @@ describe('PR Comment Mapper', () => {
|
|
|
402
480
|
createdDate: 1600000000000,
|
|
403
481
|
comments: [],
|
|
404
482
|
threadResolved: false,
|
|
405
|
-
state: "OPEN"
|
|
483
|
+
state: "OPEN",
|
|
484
|
+
severity: "NORMAL"
|
|
406
485
|
}
|
|
407
486
|
]);
|
|
408
487
|
});
|
|
@@ -447,7 +526,8 @@ describe('PR Comment Mapper', () => {
|
|
|
447
526
|
createdDate: 1600000000000,
|
|
448
527
|
comments: [],
|
|
449
528
|
threadResolved: false,
|
|
450
|
-
state: "OPEN"
|
|
529
|
+
state: "OPEN",
|
|
530
|
+
severity: "NORMAL"
|
|
451
531
|
}
|
|
452
532
|
]);
|
|
453
533
|
|
|
@@ -465,7 +545,9 @@ describe('PR Comment Mapper', () => {
|
|
|
465
545
|
summary: {
|
|
466
546
|
totalActivities: 0,
|
|
467
547
|
commentCount: 0,
|
|
468
|
-
unresolvedCount: 0
|
|
548
|
+
unresolvedCount: 0,
|
|
549
|
+
blockerCount: 0,
|
|
550
|
+
unresolvedBlockerCount: 0
|
|
469
551
|
}
|
|
470
552
|
};
|
|
471
553
|
|
|
@@ -489,7 +571,9 @@ describe('PR Comment Mapper', () => {
|
|
|
489
571
|
summary: {
|
|
490
572
|
totalActivities: 0,
|
|
491
573
|
commentCount: 0,
|
|
492
|
-
unresolvedCount: 0
|
|
574
|
+
unresolvedCount: 0,
|
|
575
|
+
blockerCount: 0,
|
|
576
|
+
unresolvedBlockerCount: 0
|
|
493
577
|
}
|
|
494
578
|
};
|
|
495
579
|
|