@atlassian-dc-mcp/bitbucket 0.12.2 → 0.14.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +45 -2
  3. package/build/__tests__/bitbucket-service.test.js +106 -17
  4. package/build/__tests__/bitbucket-service.test.js.map +1 -1
  5. package/build/__tests__/bitbucket-token-optimization.test.d.ts +2 -0
  6. package/build/__tests__/bitbucket-token-optimization.test.d.ts.map +1 -0
  7. package/build/__tests__/bitbucket-token-optimization.test.js +375 -0
  8. package/build/__tests__/bitbucket-token-optimization.test.js.map +1 -0
  9. package/build/__tests__/config.test.d.ts +2 -0
  10. package/build/__tests__/config.test.d.ts.map +1 -0
  11. package/build/__tests__/config.test.js +49 -0
  12. package/build/__tests__/config.test.js.map +1 -0
  13. package/build/__tests__/pr-comment-mapper.test.js +152 -1
  14. package/build/__tests__/pr-comment-mapper.test.js.map +1 -1
  15. package/build/bitbucket-response-mapper.d.ts +8 -0
  16. package/build/bitbucket-response-mapper.d.ts.map +1 -0
  17. package/build/bitbucket-response-mapper.js +96 -0
  18. package/build/bitbucket-response-mapper.js.map +1 -0
  19. package/build/bitbucket-service.d.ts +25 -22
  20. package/build/bitbucket-service.d.ts.map +1 -1
  21. package/build/bitbucket-service.js +89 -54
  22. package/build/bitbucket-service.js.map +1 -1
  23. package/build/config.d.ts +4 -0
  24. package/build/config.d.ts.map +1 -0
  25. package/build/config.js +11 -0
  26. package/build/config.js.map +1 -0
  27. package/build/index.js +15 -15
  28. package/build/index.js.map +1 -1
  29. package/build/pr-comment-mapper.d.ts +6 -2
  30. package/build/pr-comment-mapper.d.ts.map +1 -1
  31. package/build/pr-comment-mapper.js +47 -7
  32. package/build/pr-comment-mapper.js.map +1 -1
  33. package/package.json +3 -3
  34. package/server.json +11 -4
  35. package/src/__tests__/bitbucket-service.test.ts +109 -17
  36. package/src/__tests__/bitbucket-token-optimization.test.ts +412 -0
  37. package/src/__tests__/config.test.ts +64 -0
  38. package/src/__tests__/pr-comment-mapper.test.ts +160 -0
  39. package/src/bitbucket-response-mapper.ts +121 -0
  40. package/src/bitbucket-service.ts +119 -57
  41. package/src/config.ts +13 -0
  42. package/src/index.ts +26 -17
  43. package/src/pr-comment-mapper.ts +66 -7
  44. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,412 @@
1
+ import { BitbucketService } from '../bitbucket-service.js';
2
+ import { PullRequestsService } from '../bitbucket-client/index.js';
3
+
4
+ jest.mock('../bitbucket-client/index.js', () => ({
5
+ PullRequestsService: {
6
+ getActivities: jest.fn(),
7
+ streamChanges1: jest.fn(),
8
+ createComment2: jest.fn(),
9
+ create: jest.fn(),
10
+ update: jest.fn(),
11
+ },
12
+ ProjectService: {},
13
+ RepositoryService: {},
14
+ OpenAPI: {
15
+ BASE: '',
16
+ TOKEN: '',
17
+ VERSION: '',
18
+ },
19
+ }));
20
+
21
+ function createUser(name: string, displayName: string) {
22
+ return {
23
+ name,
24
+ emailAddress: `${name}@example.com`,
25
+ active: true,
26
+ displayName,
27
+ id: 1,
28
+ slug: name,
29
+ type: 'NORMAL',
30
+ links: {
31
+ self: [{ href: `https://bitbucket.example.com/users/${name}` }],
32
+ },
33
+ };
34
+ }
35
+
36
+ function createComment(overrides: Record<string, unknown> = {}) {
37
+ return {
38
+ properties: { repositoryId: 1 },
39
+ id: 101,
40
+ version: 1,
41
+ text: 'Looks good',
42
+ author: createUser('reviewer', 'Reviewer'),
43
+ createdDate: 20,
44
+ updatedDate: 21,
45
+ comments: [],
46
+ anchor: {
47
+ fromHash: 'abc',
48
+ toHash: 'def',
49
+ line: 10,
50
+ lineType: 'ADDED',
51
+ fileType: 'TO',
52
+ path: 'src/app.ts',
53
+ diffType: 'EFFECTIVE',
54
+ orphaned: false,
55
+ },
56
+ threadResolved: false,
57
+ severity: 'NORMAL',
58
+ state: 'OPEN',
59
+ permittedOperations: {
60
+ editable: true,
61
+ transitionable: true,
62
+ deletable: true,
63
+ },
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ describe('BitbucketService token optimization paths', () => {
69
+ let service: BitbucketService;
70
+
71
+ beforeEach(() => {
72
+ service = new BitbucketService('test-host', 'test-token');
73
+ jest.clearAllMocks();
74
+ });
75
+
76
+ describe('getPullRequestCommentsAndActions', () => {
77
+ const mockActivityResponse = {
78
+ isLastPage: false,
79
+ values: [
80
+ {
81
+ id: 1,
82
+ createdDate: 10,
83
+ user: createUser('author', 'Author'),
84
+ action: 'OPENED',
85
+ },
86
+ {
87
+ id: 2,
88
+ createdDate: 20,
89
+ user: createUser('reviewer', 'Reviewer'),
90
+ action: 'COMMENTED',
91
+ comment: createComment(),
92
+ },
93
+ ],
94
+ };
95
+
96
+ it('returns compact output by default', async () => {
97
+ (PullRequestsService.getActivities as jest.Mock).mockResolvedValue(mockActivityResponse);
98
+
99
+ const result = await service.getPullRequestCommentsAndActions('TEST', 'repo', '123');
100
+
101
+ expect(result.success).toBe(true);
102
+ expect(result.data).toEqual({
103
+ isLastPage: false,
104
+ activities: [
105
+ {
106
+ id: 1,
107
+ createdDate: 10,
108
+ user: { name: 'author', displayName: 'Author' },
109
+ action: 'OPENED',
110
+ },
111
+ {
112
+ id: 2,
113
+ createdDate: 20,
114
+ user: { name: 'reviewer', displayName: 'Reviewer' },
115
+ action: 'COMMENTED',
116
+ comment: {
117
+ id: 101,
118
+ text: 'Looks good',
119
+ author: { name: 'reviewer', displayName: 'Reviewer' },
120
+ createdDate: 20,
121
+ anchor: {
122
+ line: 10,
123
+ path: 'src/app.ts',
124
+ fileType: 'TO',
125
+ },
126
+ comments: [],
127
+ threadResolved: false,
128
+ state: 'OPEN',
129
+ },
130
+ },
131
+ ],
132
+ summary: {
133
+ totalActivities: 2,
134
+ prAuthor: { name: 'author', displayName: 'Author' },
135
+ commentCount: 1,
136
+ unresolvedCount: 1,
137
+ },
138
+ });
139
+ expect(PullRequestsService.getActivities).toHaveBeenCalledWith('TEST', '123', 'repo', undefined, undefined, undefined, 25);
140
+ });
141
+
142
+ it('skips resolved threads by default and includes them on request', async () => {
143
+ const resolvedActivityResponse = {
144
+ isLastPage: true,
145
+ values: [
146
+ {
147
+ id: 1,
148
+ createdDate: 10,
149
+ user: createUser('author', 'Author'),
150
+ action: 'OPENED',
151
+ },
152
+ {
153
+ id: 2,
154
+ createdDate: 20,
155
+ user: createUser('reviewer', 'Reviewer'),
156
+ action: 'COMMENTED',
157
+ comment: createComment({
158
+ id: 202,
159
+ text: 'Resolved comment',
160
+ threadResolved: true,
161
+ comments: [createComment({ id: 203, text: 'Resolved reply', anchor: undefined, threadResolved: true })],
162
+ }),
163
+ },
164
+ ],
165
+ };
166
+
167
+ (PullRequestsService.getActivities as jest.Mock).mockResolvedValue(resolvedActivityResponse);
168
+
169
+ const defaultResult = await service.getPullRequestCommentsAndActions('TEST', 'repo', '123');
170
+ const includeResolvedResult = await service.getPullRequestCommentsAndActions('TEST', 'repo', '123', undefined, undefined, 'compact', true);
171
+
172
+ expect(defaultResult.success).toBe(true);
173
+ expect(defaultResult.data).toEqual({
174
+ isLastPage: true,
175
+ activities: [
176
+ {
177
+ id: 1,
178
+ createdDate: 10,
179
+ user: { name: 'author', displayName: 'Author' },
180
+ action: 'OPENED',
181
+ },
182
+ ],
183
+ summary: {
184
+ totalActivities: 1,
185
+ prAuthor: { name: 'author', displayName: 'Author' },
186
+ commentCount: 0,
187
+ unresolvedCount: 0,
188
+ },
189
+ });
190
+ expect(includeResolvedResult.success).toBe(true);
191
+ expect(includeResolvedResult.data).toEqual({
192
+ isLastPage: true,
193
+ activities: [
194
+ {
195
+ id: 1,
196
+ createdDate: 10,
197
+ user: { name: 'author', displayName: 'Author' },
198
+ action: 'OPENED',
199
+ },
200
+ {
201
+ id: 2,
202
+ createdDate: 20,
203
+ user: { name: 'reviewer', displayName: 'Reviewer' },
204
+ action: 'COMMENTED',
205
+ comment: {
206
+ id: 202,
207
+ text: 'Resolved comment',
208
+ author: { name: 'reviewer', displayName: 'Reviewer' },
209
+ createdDate: 20,
210
+ anchor: {
211
+ line: 10,
212
+ path: 'src/app.ts',
213
+ fileType: 'TO',
214
+ },
215
+ comments: [
216
+ {
217
+ id: 203,
218
+ text: 'Resolved reply',
219
+ author: { name: 'reviewer', displayName: 'Reviewer' },
220
+ createdDate: 20,
221
+ comments: [],
222
+ threadResolved: true,
223
+ state: 'OPEN',
224
+ },
225
+ ],
226
+ threadResolved: true,
227
+ state: 'OPEN',
228
+ },
229
+ },
230
+ ],
231
+ summary: {
232
+ totalActivities: 2,
233
+ prAuthor: { name: 'author', displayName: 'Author' },
234
+ commentCount: 1,
235
+ unresolvedCount: 0,
236
+ },
237
+ });
238
+ });
239
+
240
+ it('returns summary output when requested', async () => {
241
+ (PullRequestsService.getActivities as jest.Mock).mockResolvedValue(mockActivityResponse);
242
+
243
+ const result = await service.getPullRequestCommentsAndActions('TEST', 'repo', '123', 5, 10, 'summary');
244
+
245
+ expect(result.success).toBe(true);
246
+ expect(result.data).toEqual({
247
+ isLastPage: false,
248
+ summary: {
249
+ totalActivities: 2,
250
+ prAuthor: { name: 'author', displayName: 'Author' },
251
+ commentCount: 1,
252
+ unresolvedCount: 1,
253
+ },
254
+ items: ['Reviewer on src/app.ts:10: Looks good'],
255
+ });
256
+ expect(PullRequestsService.getActivities).toHaveBeenCalledWith('TEST', '123', 'repo', undefined, undefined, 5, 10);
257
+ });
258
+
259
+ it('returns the raw payload when output is full', async () => {
260
+ (PullRequestsService.getActivities as jest.Mock).mockResolvedValue(mockActivityResponse);
261
+
262
+ const result = await service.getPullRequestCommentsAndActions('TEST', 'repo', '123', undefined, undefined, 'full');
263
+
264
+ expect(result.success).toBe(true);
265
+ expect(result.data).toStrictEqual(mockActivityResponse);
266
+ });
267
+ });
268
+
269
+ describe('getPullRequestChanges', () => {
270
+ const mockChangesResponse = {
271
+ fromHash: 'abc',
272
+ toHash: 'def',
273
+ properties: { changeScope: 'ALL' },
274
+ values: [
275
+ {
276
+ contentId: 'content-1',
277
+ fromContentId: 'from-content-1',
278
+ path: {
279
+ components: ['src', 'app.ts'],
280
+ parent: 'src',
281
+ name: 'app.ts',
282
+ extension: 'ts',
283
+ toString: 'src/app.ts',
284
+ },
285
+ type: 'MODIFY',
286
+ properties: {
287
+ gitChangeType: 'MODIFY',
288
+ activeComments: 2,
289
+ },
290
+ },
291
+ ],
292
+ size: 1,
293
+ isLastPage: true,
294
+ start: 0,
295
+ limit: 25,
296
+ nextPageStart: null,
297
+ };
298
+
299
+ it('returns compact output by default', async () => {
300
+ (PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockChangesResponse);
301
+
302
+ const result = await service.getPullRequestChanges('TEST', 'repo', '123');
303
+
304
+ expect(result.success).toBe(true);
305
+ expect(result.data).toEqual({
306
+ fromHash: 'abc',
307
+ toHash: 'def',
308
+ changeScope: 'ALL',
309
+ changes: [
310
+ {
311
+ contentId: 'content-1',
312
+ path: {
313
+ name: 'app.ts',
314
+ path: 'src/app.ts',
315
+ extension: 'ts',
316
+ },
317
+ type: 'MODIFY',
318
+ gitChangeType: 'MODIFY',
319
+ comments: 2,
320
+ },
321
+ ],
322
+ summary: {
323
+ totalChanges: 1,
324
+ additions: 0,
325
+ deletions: 0,
326
+ modifications: 1,
327
+ moves: 0,
328
+ filesWithComments: 1,
329
+ },
330
+ isLastPage: true,
331
+ });
332
+ });
333
+
334
+ it('returns summary output when requested', async () => {
335
+ (PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockChangesResponse);
336
+
337
+ const result = await service.getPullRequestChanges('TEST', 'repo', '123', undefined, undefined, undefined, undefined, undefined, undefined, 'summary');
338
+
339
+ expect(result.success).toBe(true);
340
+ expect(result.data).toEqual({
341
+ fromHash: 'abc',
342
+ toHash: 'def',
343
+ changeScope: 'ALL',
344
+ isLastPage: true,
345
+ summary: {
346
+ totalChanges: 1,
347
+ additions: 0,
348
+ deletions: 0,
349
+ modifications: 1,
350
+ moves: 0,
351
+ filesWithComments: 1,
352
+ },
353
+ items: ['Modified: src/app.ts [2 comments]'],
354
+ });
355
+ });
356
+
357
+ it('returns the raw payload when output is full', async () => {
358
+ (PullRequestsService.streamChanges1 as jest.Mock).mockResolvedValue(mockChangesResponse);
359
+
360
+ const result = await service.getPullRequestChanges('TEST', 'repo', '123', undefined, undefined, undefined, undefined, undefined, undefined, 'full');
361
+
362
+ expect(result.success).toBe(true);
363
+ expect(result.data).toBe(mockChangesResponse);
364
+ });
365
+ });
366
+
367
+ describe('full mutation output', () => {
368
+ it('returns the raw comment response when requested', async () => {
369
+ const mockComment = {
370
+ id: 222,
371
+ text: 'Raw comment',
372
+ state: 'OPEN',
373
+ };
374
+ (PullRequestsService.createComment2 as jest.Mock).mockResolvedValue(mockComment);
375
+
376
+ const result = await service.postPullRequestComment('TEST', 'repo', '123', 'Raw comment', undefined, undefined, undefined, undefined, undefined, 'full');
377
+
378
+ expect(result.success).toBe(true);
379
+ expect(result.data).toBe(mockComment);
380
+ });
381
+
382
+ it('returns the raw PR create response when requested', async () => {
383
+ const mockPullRequest = {
384
+ id: 10,
385
+ version: 1,
386
+ title: 'Raw create',
387
+ state: 'OPEN',
388
+ };
389
+ (PullRequestsService.create as jest.Mock).mockResolvedValue(mockPullRequest);
390
+
391
+ const result = await service.createPullRequest('TEST', 'repo', 'Raw create', undefined, 'refs/heads/feature', 'refs/heads/main', undefined, 'full');
392
+
393
+ expect(result.success).toBe(true);
394
+ expect(result.data).toBe(mockPullRequest);
395
+ });
396
+
397
+ it('returns the raw PR update response when requested', async () => {
398
+ const mockPullRequest = {
399
+ id: 11,
400
+ version: 2,
401
+ title: 'Raw update',
402
+ state: 'OPEN',
403
+ };
404
+ (PullRequestsService.update as jest.Mock).mockResolvedValue(mockPullRequest);
405
+
406
+ const result = await service.updatePullRequest('TEST', 'repo', '123', 1, 'Raw update', undefined, undefined, 'full');
407
+
408
+ expect(result.success).toBe(true);
409
+ expect(result.data).toBe(mockPullRequest);
410
+ });
411
+ });
412
+ });
@@ -0,0 +1,64 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ describe('Bitbucket config', () => {
6
+ const originalEnv = process.env;
7
+ const originalCwd = process.cwd();
8
+ let tempDir: string;
9
+
10
+ beforeEach(() => {
11
+ jest.resetModules();
12
+ process.env = { ...originalEnv };
13
+ delete process.env.ATLASSIAN_DC_MCP_CONFIG_FILE;
14
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bitbucket-config-'));
15
+ process.chdir(tempDir);
16
+ });
17
+
18
+ afterEach(() => {
19
+ fs.rmSync(tempDir, { recursive: true, force: true });
20
+ process.chdir(originalCwd);
21
+ });
22
+
23
+ afterAll(() => {
24
+ process.env = originalEnv;
25
+ });
26
+
27
+ it('uses the configured page size when the env var is a positive integer', async () => {
28
+ process.env.BITBUCKET_DEFAULT_PAGE_SIZE = '40';
29
+
30
+ const { getDefaultPageSize } = await import('../config.js');
31
+
32
+ expect(getDefaultPageSize()).toBe(40);
33
+ });
34
+
35
+ it('falls back to 25 when the env var is invalid', async () => {
36
+ process.env.BITBUCKET_DEFAULT_PAGE_SIZE = '-1';
37
+
38
+ const { getDefaultPageSize } = await import('../config.js');
39
+
40
+ expect(getDefaultPageSize()).toBe(25);
41
+ });
42
+
43
+ it('reads the page size from the shared config file', async () => {
44
+ const sharedConfigPath = path.join(tempDir, 'shared.env');
45
+ fs.writeFileSync(sharedConfigPath, 'BITBUCKET_HOST=file-host\nBITBUCKET_API_TOKEN=file-token\nBITBUCKET_DEFAULT_PAGE_SIZE=35\n');
46
+ process.env.ATLASSIAN_DC_MCP_CONFIG_FILE = sharedConfigPath;
47
+
48
+ const { getBitbucketRuntimeConfig, getDefaultPageSize } = await import('../config.js');
49
+
50
+ expect(getDefaultPageSize()).toBe(35);
51
+ expect(getBitbucketRuntimeConfig().token).toBe('file-token');
52
+ });
53
+
54
+ it('keeps env values higher priority than the shared config file', async () => {
55
+ const sharedConfigPath = path.join(tempDir, 'shared.env');
56
+ fs.writeFileSync(sharedConfigPath, 'BITBUCKET_HOST=file-host\nBITBUCKET_API_TOKEN=file-token\nBITBUCKET_DEFAULT_PAGE_SIZE=35\n');
57
+ process.env.ATLASSIAN_DC_MCP_CONFIG_FILE = sharedConfigPath;
58
+ process.env.BITBUCKET_DEFAULT_PAGE_SIZE = '45';
59
+
60
+ const { getDefaultPageSize } = await import('../config.js');
61
+
62
+ expect(getDefaultPageSize()).toBe(45);
63
+ });
64
+ });
@@ -1,4 +1,5 @@
1
1
  import {
2
+ filterPullRequestComments,
2
3
  simplifyBitbucketPRComments,
3
4
  getCommentSummary,
4
5
  type BitbucketPRApiResponse,
@@ -106,6 +107,7 @@ describe('PR Comment Mapper', () => {
106
107
  ],
107
108
  start: 0
108
109
  };
110
+ const [baseCommentActivity, openedActivity] = validPRResponse.values ?? [];
109
111
 
110
112
  describe('simplifyBitbucketPRComments', () => {
111
113
  it('should simplify valid PR response correctly', () => {
@@ -227,6 +229,143 @@ describe('PR Comment Mapper', () => {
227
229
  ]);
228
230
  });
229
231
 
232
+ it('should exclude resolved threads and all replies by default', () => {
233
+ const resolvedThreadResponse: BitbucketPRApiResponse = {
234
+ ...validPRResponse,
235
+ values: [
236
+ openedActivity!,
237
+ {
238
+ id: 1003,
239
+ createdDate: 1600000002000,
240
+ user: createUser('reviewer3', 105, 'Reviewer Three'),
241
+ action: 'COMMENTED',
242
+ commentAction: 'ADDED',
243
+ comment: createComment({
244
+ id: 2004,
245
+ text: 'Resolved thread root',
246
+ threadResolved: true,
247
+ comments: [
248
+ createComment({
249
+ id: 2005,
250
+ text: 'Resolved reply',
251
+ anchor: undefined,
252
+ threadResolved: true,
253
+ })
254
+ ]
255
+ })
256
+ },
257
+ baseCommentActivity!,
258
+ ],
259
+ };
260
+
261
+ const result = simplifyBitbucketPRComments(resolvedThreadResponse) as SimplifiedPRResponse;
262
+
263
+ expect(result.activities).toEqual([
264
+ {
265
+ id: 1002,
266
+ createdDate: 1600000001000,
267
+ user: {
268
+ name: 'testuser2',
269
+ displayName: 'User B'
270
+ },
271
+ action: 'OPENED'
272
+ },
273
+ {
274
+ id: 1001,
275
+ createdDate: 1600000000000,
276
+ user: {
277
+ name: 'testuser1',
278
+ displayName: 'User A'
279
+ },
280
+ action: 'COMMENTED',
281
+ commentAction: 'ADDED',
282
+ comment: {
283
+ id: 2001,
284
+ text: 'This needs review',
285
+ author: {
286
+ name: 'testuser1',
287
+ displayName: 'User A'
288
+ },
289
+ createdDate: 1600000000000,
290
+ anchor: {
291
+ line: 6,
292
+ path: 'config.yml',
293
+ fileType: 'TO'
294
+ },
295
+ comments: [],
296
+ threadResolved: false,
297
+ state: 'OPEN'
298
+ }
299
+ }
300
+ ]);
301
+ expect(result.summary.commentCount).toBe(1);
302
+ expect(getCommentSummary(resolvedThreadResponse)).toEqual(['User A on config.yml:6: This needs review']);
303
+ });
304
+
305
+ it('should include resolved threads when requested', () => {
306
+ const resolvedThreadResponse: BitbucketPRApiResponse = {
307
+ ...validPRResponse,
308
+ values: [
309
+ {
310
+ id: 1003,
311
+ createdDate: 1600000002000,
312
+ user: createUser('reviewer3', 105, 'Reviewer Three'),
313
+ action: 'COMMENTED',
314
+ commentAction: 'ADDED',
315
+ comment: createComment({
316
+ id: 2004,
317
+ text: 'Resolved thread root',
318
+ threadResolved: true,
319
+ comments: [
320
+ createComment({
321
+ id: 2005,
322
+ text: 'Resolved reply',
323
+ anchor: undefined,
324
+ threadResolved: true,
325
+ })
326
+ ]
327
+ })
328
+ }
329
+ ],
330
+ };
331
+
332
+ const result = simplifyBitbucketPRComments(resolvedThreadResponse, { includeResolved: true }) as SimplifiedPRResponse;
333
+
334
+ expect(result.activities[0].comment).toEqual({
335
+ id: 2004,
336
+ text: 'Resolved thread root',
337
+ author: {
338
+ name: 'testuser1',
339
+ displayName: 'User A'
340
+ },
341
+ createdDate: 1600000000000,
342
+ anchor: {
343
+ line: 6,
344
+ path: 'config.yml',
345
+ fileType: 'TO'
346
+ },
347
+ comments: [
348
+ {
349
+ id: 2005,
350
+ text: 'Resolved reply',
351
+ author: {
352
+ name: 'testuser1',
353
+ displayName: 'User A'
354
+ },
355
+ createdDate: 1600000000000,
356
+ comments: [],
357
+ threadResolved: true,
358
+ state: 'OPEN'
359
+ }
360
+ ],
361
+ threadResolved: true,
362
+ state: 'OPEN'
363
+ });
364
+ expect(getCommentSummary(resolvedThreadResponse, { includeResolved: true })).toEqual([
365
+ 'User A on config.yml:6: Resolved thread root'
366
+ ]);
367
+ });
368
+
230
369
  it('should skip malformed nested replies', () => {
231
370
  const malformedNestedResponse: BitbucketPRApiResponse = {
232
371
  ...validPRResponse,
@@ -396,4 +535,25 @@ describe('PR Comment Mapper', () => {
396
535
  expect(summary).toHaveLength(0);
397
536
  });
398
537
  });
538
+
539
+ describe('filterPullRequestComments', () => {
540
+ it('should keep non-comment activities while removing resolved comment activities by default', () => {
541
+ const response: BitbucketPRApiResponse = {
542
+ ...validPRResponse,
543
+ values: [
544
+ openedActivity!,
545
+ {
546
+ id: 1004,
547
+ createdDate: 1600000003000,
548
+ user: createUser('reviewer4', 106, 'Reviewer Four'),
549
+ action: 'COMMENTED',
550
+ commentAction: 'ADDED',
551
+ comment: createComment({ id: 2006, threadResolved: true })
552
+ }
553
+ ]
554
+ };
555
+
556
+ expect(filterPullRequestComments(response).values).toEqual([openedActivity!]);
557
+ });
558
+ });
399
559
  });