@atlassian-dc-mcp/bitbucket 0.12.0 → 0.12.2

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.
@@ -4,6 +4,83 @@ import {
4
4
  type BitbucketPRApiResponse,
5
5
  type SimplifiedPRResponse
6
6
  } from '../pr-comment-mapper.js';
7
+ import { formatToolResponse } from '@atlassian-dc-mcp/common';
8
+
9
+ interface TestComment {
10
+ properties: { repositoryId: number };
11
+ id: number;
12
+ version: number;
13
+ text: string;
14
+ author: ReturnType<typeof createUser>;
15
+ createdDate: number;
16
+ updatedDate: number;
17
+ comments: TestComment[];
18
+ anchor?: {
19
+ fromHash: string;
20
+ toHash: string;
21
+ line: number;
22
+ lineType: string;
23
+ fileType: string;
24
+ path: string;
25
+ diffType: string;
26
+ orphaned: boolean;
27
+ };
28
+ threadResolved: boolean;
29
+ severity: string;
30
+ state: string;
31
+ permittedOperations: {
32
+ editable: boolean;
33
+ transitionable: boolean;
34
+ deletable: boolean;
35
+ };
36
+ }
37
+
38
+ function createUser(name: string, id: number, displayName: string) {
39
+ return {
40
+ name,
41
+ emailAddress: `${name}@company.local`,
42
+ active: true,
43
+ displayName,
44
+ id,
45
+ slug: name,
46
+ type: "NORMAL",
47
+ links: {
48
+ self: [{ href: `https://bitbucket.company.local/users/${name}` }]
49
+ }
50
+ };
51
+ }
52
+
53
+ function createComment(overrides: Partial<TestComment> = {}): TestComment {
54
+ return {
55
+ properties: { repositoryId: 1001 },
56
+ id: 2001,
57
+ version: 0,
58
+ text: "This needs review",
59
+ author: createUser('testuser1', 101, 'User A'),
60
+ createdDate: 1600000000000,
61
+ updatedDate: 1600000000000,
62
+ comments: [],
63
+ anchor: {
64
+ fromHash: "abc123def456789012345678901234567890abcd",
65
+ toHash: "def456abc789012345678901234567890123cdef",
66
+ line: 6,
67
+ lineType: "ADDED",
68
+ fileType: "TO",
69
+ path: "config.yml",
70
+ diffType: "EFFECTIVE",
71
+ orphaned: false
72
+ },
73
+ threadResolved: false,
74
+ severity: "NORMAL",
75
+ state: "OPEN",
76
+ permittedOperations: {
77
+ editable: false,
78
+ transitionable: true,
79
+ deletable: false
80
+ },
81
+ ...overrides
82
+ };
83
+ }
7
84
 
8
85
  describe('PR Comment Mapper', () => {
9
86
  // Test data from the original ad-hoc test
@@ -15,75 +92,15 @@ describe('PR Comment Mapper', () => {
15
92
  {
16
93
  id: 1001,
17
94
  createdDate: 1600000000000,
18
- user: {
19
- name: "testuser1",
20
- emailAddress: "testuser1@company.local",
21
- active: true,
22
- displayName: "User A",
23
- id: 101,
24
- slug: "testuser1",
25
- type: "NORMAL",
26
- links: {
27
- self: [{ href: "https://bitbucket.company.local/users/testuser1" }]
28
- }
29
- },
95
+ user: createUser('testuser1', 101, 'User A'),
30
96
  action: "COMMENTED",
31
97
  commentAction: "ADDED",
32
- comment: {
33
- properties: { repositoryId: 1001 },
34
- id: 2001,
35
- version: 0,
36
- text: "This needs review",
37
- author: {
38
- name: "testuser1",
39
- emailAddress: "testuser1@company.local",
40
- active: true,
41
- displayName: "User A",
42
- id: 101,
43
- slug: "testuser1",
44
- type: "NORMAL",
45
- links: {
46
- self: [{ href: "https://bitbucket.company.local/users/testuser1" }]
47
- }
48
- },
49
- createdDate: 1600000000000,
50
- updatedDate: 1600000000000,
51
- comments: [],
52
- anchor: {
53
- fromHash: "abc123def456789012345678901234567890abcd",
54
- toHash: "def456abc789012345678901234567890123cdef",
55
- line: 6,
56
- lineType: "ADDED",
57
- fileType: "TO",
58
- path: "config.yml",
59
- diffType: "EFFECTIVE",
60
- orphaned: false
61
- },
62
- threadResolved: false,
63
- severity: "NORMAL",
64
- state: "OPEN",
65
- permittedOperations: {
66
- editable: false,
67
- transitionable: true,
68
- deletable: false
69
- }
70
- }
98
+ comment: createComment()
71
99
  },
72
100
  {
73
101
  id: 1002,
74
102
  createdDate: 1600000001000,
75
- user: {
76
- name: "testuser2",
77
- emailAddress: "testuser2@company.local",
78
- active: true,
79
- displayName: "User B",
80
- id: 102,
81
- slug: "testuser2",
82
- type: "NORMAL",
83
- links: {
84
- self: [{ href: "https://bitbucket.company.local/users/testuser2" }]
85
- }
86
- },
103
+ user: createUser('testuser2', 102, 'User B'),
87
104
  action: "OPENED"
88
105
  }
89
106
  ],
@@ -119,6 +136,7 @@ describe('PR Comment Mapper', () => {
119
136
  path: "config.yml",
120
137
  fileType: "TO"
121
138
  },
139
+ comments: [],
122
140
  threadResolved: false,
123
141
  state: "OPEN"
124
142
  }
@@ -147,6 +165,157 @@ describe('PR Comment Mapper', () => {
147
165
  expect(result).toEqual(expectedResult);
148
166
  });
149
167
 
168
+ it('should preserve nested replies recursively', () => {
169
+ const threadedResponse: BitbucketPRApiResponse = {
170
+ ...validPRResponse,
171
+ values: [
172
+ {
173
+ id: 1001,
174
+ createdDate: 1600000000000,
175
+ user: createUser('testuser1', 101, 'User A'),
176
+ action: "COMMENTED",
177
+ commentAction: "ADDED",
178
+ comment: createComment({
179
+ comments: [
180
+ createComment({
181
+ id: 2002,
182
+ text: "Author reply",
183
+ author: createUser('author1', 103, 'Author One'),
184
+ anchor: undefined,
185
+ comments: [
186
+ createComment({
187
+ id: 2003,
188
+ text: "Reviewer follow-up",
189
+ author: createUser('reviewer2', 104, 'Reviewer Two'),
190
+ anchor: undefined
191
+ })
192
+ ]
193
+ })
194
+ ]
195
+ })
196
+ }
197
+ ]
198
+ };
199
+
200
+ const result = simplifyBitbucketPRComments(threadedResponse) as SimplifiedPRResponse;
201
+ expect(result.activities[0].comment?.comments).toEqual([
202
+ {
203
+ id: 2002,
204
+ text: "Author reply",
205
+ author: {
206
+ name: "author1",
207
+ displayName: "Author One"
208
+ },
209
+ createdDate: 1600000000000,
210
+ comments: [
211
+ {
212
+ id: 2003,
213
+ text: "Reviewer follow-up",
214
+ author: {
215
+ name: "reviewer2",
216
+ displayName: "Reviewer Two"
217
+ },
218
+ createdDate: 1600000000000,
219
+ comments: [],
220
+ threadResolved: false,
221
+ state: "OPEN"
222
+ }
223
+ ],
224
+ threadResolved: false,
225
+ state: "OPEN"
226
+ }
227
+ ]);
228
+ });
229
+
230
+ it('should skip malformed nested replies', () => {
231
+ const malformedNestedResponse: BitbucketPRApiResponse = {
232
+ ...validPRResponse,
233
+ values: [
234
+ {
235
+ id: 1001,
236
+ createdDate: 1600000000000,
237
+ user: createUser('testuser1', 101, 'User A'),
238
+ action: "COMMENTED",
239
+ commentAction: "ADDED",
240
+ comment: createComment({
241
+ comments: [
242
+ { id: 'bad-comment-id' } as unknown as TestComment,
243
+ createComment({
244
+ id: 2002,
245
+ text: "Valid reply",
246
+ anchor: undefined
247
+ })
248
+ ]
249
+ })
250
+ }
251
+ ]
252
+ };
253
+
254
+ const result = simplifyBitbucketPRComments(malformedNestedResponse) as SimplifiedPRResponse;
255
+ expect(result.activities[0].comment?.comments).toEqual([
256
+ {
257
+ id: 2002,
258
+ text: "Valid reply",
259
+ author: {
260
+ name: "testuser1",
261
+ displayName: "User A"
262
+ },
263
+ createdDate: 1600000000000,
264
+ comments: [],
265
+ threadResolved: false,
266
+ state: "OPEN"
267
+ }
268
+ ]);
269
+ });
270
+
271
+ it('should drop cyclic reply branches and remain JSON serializable', () => {
272
+ const rootComment = createComment();
273
+ const childComment = createComment({
274
+ id: 2002,
275
+ text: "Child reply",
276
+ anchor: undefined
277
+ });
278
+
279
+ rootComment.comments = [childComment];
280
+ childComment.comments = [rootComment];
281
+
282
+ const cyclicResponse: BitbucketPRApiResponse = {
283
+ size: 1,
284
+ limit: 100,
285
+ isLastPage: true,
286
+ values: [
287
+ {
288
+ id: 1001,
289
+ createdDate: 1600000000000,
290
+ user: createUser('testuser1', 101, 'User A'),
291
+ action: "COMMENTED",
292
+ commentAction: "ADDED",
293
+ comment: rootComment
294
+ }
295
+ ],
296
+ start: 0
297
+ };
298
+
299
+ const result = simplifyBitbucketPRComments(cyclicResponse) as SimplifiedPRResponse;
300
+ expect(result.activities[0].comment?.comments).toEqual([
301
+ {
302
+ id: 2002,
303
+ text: "Child reply",
304
+ author: {
305
+ name: "testuser1",
306
+ displayName: "User A"
307
+ },
308
+ createdDate: 1600000000000,
309
+ comments: [],
310
+ threadResolved: false,
311
+ state: "OPEN"
312
+ }
313
+ ]);
314
+
315
+ expect(() => formatToolResponse(result)).not.toThrow();
316
+ expect(JSON.parse(formatToolResponse(result).content[0].text)).toBeTruthy();
317
+ });
318
+
150
319
  it('should handle malformed input gracefully', () => {
151
320
  const malformedInput = { invalid: 'data' } as any;
152
321
  const result = simplifyBitbucketPRComments(malformedInput) as SimplifiedPRResponse;
@@ -139,6 +139,7 @@ interface SimplifiedComment {
139
139
  author: SimplifiedUser;
140
140
  createdDate: number;
141
141
  anchor?: SimplifiedAnchor;
142
+ comments: SimplifiedComment[];
142
143
  threadResolved: boolean;
143
144
  state: string;
144
145
  }
@@ -239,13 +240,20 @@ function simplifyAnchor(anchor: CommentAnchor): SimplifiedAnchor {
239
240
  };
240
241
  }
241
242
 
242
- function simplifyComment(comment: Comment): SimplifiedComment {
243
+ function simplifyComment(comment: Comment, ancestorIds: Set<number> = new Set()): SimplifiedComment {
244
+ const nextAncestorIds = new Set(ancestorIds);
245
+ nextAncestorIds.add(comment.id);
246
+
243
247
  return {
244
248
  id: comment.id,
245
249
  text: comment.text,
246
250
  author: simplifyUser(comment.author),
247
251
  createdDate: comment.createdDate,
248
252
  ...(comment.anchor && { anchor: simplifyAnchor(comment.anchor) }),
253
+ comments: comment.comments
254
+ .filter(isComment)
255
+ .filter(childComment => !nextAncestorIds.has(childComment.id))
256
+ .map(childComment => simplifyComment(childComment, nextAncestorIds)),
249
257
  threadResolved: comment.threadResolved,
250
258
  state: comment.state
251
259
  };
@@ -258,7 +266,9 @@ function simplifyActivity(activity: PRActivity): SimplifiedActivity {
258
266
  user: simplifyUser(activity.user),
259
267
  action: activity.action,
260
268
  ...(activity.commentAction && { commentAction: activity.commentAction }),
261
- ...(activity.comment && { comment: simplifyComment(activity.comment) })
269
+ ...(activity.comment && isComment(activity.comment) && {
270
+ comment: simplifyComment(activity.comment)
271
+ })
262
272
  };
263
273
  }
264
274