github-issue-tower-defence-management 1.39.0 → 1.40.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/.github/workflows/create-pr.yml +12 -5
- package/CHANGELOG.md +13 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +206 -6
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +111 -16
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +324 -8
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +982 -167
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +177 -26
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +3 -4
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +3 -4
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +5 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -3
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
|
@@ -65,8 +65,10 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
65
65
|
let mockIssueRepository: {
|
|
66
66
|
get: jest.Mock;
|
|
67
67
|
update: jest.Mock;
|
|
68
|
+
updateNextActionDate: jest.Mock;
|
|
68
69
|
findRelatedOpenPRs: jest.Mock;
|
|
69
70
|
getStoryObjectMap: jest.Mock;
|
|
71
|
+
getOpenPullRequest: jest.Mock;
|
|
70
72
|
};
|
|
71
73
|
let mockIssueCommentRepository: {
|
|
72
74
|
getCommentsFromIssue: jest.Mock;
|
|
@@ -92,10 +94,12 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
92
94
|
};
|
|
93
95
|
|
|
94
96
|
mockIssueRepository = {
|
|
95
|
-
getStoryObjectMap: jest.fn(),
|
|
97
|
+
getStoryObjectMap: jest.fn().mockResolvedValue(new Map()),
|
|
96
98
|
get: jest.fn(),
|
|
97
99
|
update: jest.fn(),
|
|
100
|
+
updateNextActionDate: jest.fn(),
|
|
98
101
|
findRelatedOpenPRs: jest.fn(),
|
|
102
|
+
getOpenPullRequest: jest.fn(),
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
mockIssueCommentRepository = {
|
|
@@ -174,6 +178,9 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
174
178
|
});
|
|
175
179
|
|
|
176
180
|
it('should update issue status from Preparation to Awaiting Quality Check when last comment starts with From:', async () => {
|
|
181
|
+
jest.useFakeTimers();
|
|
182
|
+
jest.setSystemTime(new Date('2026-01-01T00:00:00Z'));
|
|
183
|
+
|
|
177
184
|
const issue = createMockIssue({
|
|
178
185
|
url: 'https://github.com/user/repo/issues/1',
|
|
179
186
|
status: 'Preparation',
|
|
@@ -214,6 +221,65 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
214
221
|
}),
|
|
215
222
|
mockProject,
|
|
216
223
|
);
|
|
224
|
+
|
|
225
|
+
const expectedNextActionDate = new Date('2026-01-01T00:00:00Z');
|
|
226
|
+
expectedNextActionDate.setMonth(expectedNextActionDate.getMonth() + 1);
|
|
227
|
+
expect(mockIssueRepository.updateNextActionDate).toHaveBeenCalledWith(
|
|
228
|
+
'https://github.com/user/repo/pull/1',
|
|
229
|
+
mockProject,
|
|
230
|
+
expectedNextActionDate,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
jest.useRealTimers();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should set PR next action date to 1 month from now when approved', async () => {
|
|
237
|
+
jest.useFakeTimers();
|
|
238
|
+
const now = new Date('2026-03-15T12:00:00Z');
|
|
239
|
+
jest.setSystemTime(now);
|
|
240
|
+
|
|
241
|
+
const issue = createMockIssue({
|
|
242
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
243
|
+
status: 'Preparation',
|
|
244
|
+
});
|
|
245
|
+
const prUrl = 'https://github.com/user/repo/pull/42';
|
|
246
|
+
|
|
247
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
248
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
249
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
250
|
+
createMockComment({ content: 'From: Agent report' }),
|
|
251
|
+
]);
|
|
252
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
253
|
+
{
|
|
254
|
+
url: prUrl,
|
|
255
|
+
isConflicted: false,
|
|
256
|
+
isPassedAllCiJob: true,
|
|
257
|
+
isCiStateSuccess: true,
|
|
258
|
+
isResolvedAllReviewComments: true,
|
|
259
|
+
isBranchOutOfDate: false,
|
|
260
|
+
missingRequiredCheckNames: [],
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
await useCase.run({
|
|
265
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
266
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
267
|
+
preparationStatus: 'Preparation',
|
|
268
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
269
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
270
|
+
thresholdForAutoReject: 3,
|
|
271
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const expectedDate = new Date(now);
|
|
275
|
+
expectedDate.setMonth(expectedDate.getMonth() + 1);
|
|
276
|
+
expect(mockIssueRepository.updateNextActionDate).toHaveBeenCalledWith(
|
|
277
|
+
prUrl,
|
|
278
|
+
mockProject,
|
|
279
|
+
expectedDate,
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
jest.useRealTimers();
|
|
217
283
|
});
|
|
218
284
|
|
|
219
285
|
it('should throw IssueNotFoundError when issue does not exist', async () => {
|
|
@@ -259,30 +325,18 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
259
325
|
);
|
|
260
326
|
});
|
|
261
327
|
|
|
262
|
-
it('should
|
|
328
|
+
it('should set status to Awaiting Workspace when issue has dependent issue URLs', async () => {
|
|
263
329
|
const issue = createMockIssue({
|
|
264
330
|
url: 'https://github.com/user/repo/issues/1',
|
|
265
331
|
status: 'Preparation',
|
|
332
|
+
dependedIssueUrls: [
|
|
333
|
+
'https://github.com/user/repo/issues/2',
|
|
334
|
+
'https://github.com/user/repo/issues/3',
|
|
335
|
+
],
|
|
266
336
|
});
|
|
267
337
|
|
|
268
338
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
269
339
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
270
|
-
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
271
|
-
createMockComment({
|
|
272
|
-
content: 'Auto Status Check: REJECTED\n["NO_REPORT"]',
|
|
273
|
-
}),
|
|
274
|
-
]);
|
|
275
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
276
|
-
{
|
|
277
|
-
url: 'https://github.com/user/repo/pull/1',
|
|
278
|
-
isConflicted: false,
|
|
279
|
-
isPassedAllCiJob: true,
|
|
280
|
-
isCiStateSuccess: true,
|
|
281
|
-
isResolvedAllReviewComments: true,
|
|
282
|
-
isBranchOutOfDate: false,
|
|
283
|
-
missingRequiredCheckNames: [],
|
|
284
|
-
},
|
|
285
|
-
]);
|
|
286
340
|
|
|
287
341
|
await useCase.run({
|
|
288
342
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -295,41 +349,44 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
295
349
|
});
|
|
296
350
|
|
|
297
351
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
298
|
-
expect.objectContaining({
|
|
299
|
-
status: 'Awaiting Workspace',
|
|
300
|
-
}),
|
|
352
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
301
353
|
mockProject,
|
|
302
354
|
);
|
|
303
355
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
304
|
-
expect.objectContaining({
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
356
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
357
|
+
expect.stringContaining(
|
|
358
|
+
'Issue has dependent issue URLs: https://github.com/user/repo/issues/2, https://github.com/user/repo/issues/3',
|
|
359
|
+
),
|
|
308
360
|
);
|
|
309
361
|
});
|
|
310
362
|
|
|
311
|
-
it('should
|
|
363
|
+
it('should enrich dependedIssueUrls from storyObjectMap when issue has none', async () => {
|
|
312
364
|
const issue = createMockIssue({
|
|
313
365
|
url: 'https://github.com/user/repo/issues/1',
|
|
314
366
|
status: 'Preparation',
|
|
367
|
+
dependedIssueUrls: [],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const storyObjectMap: StoryObjectMap = new Map();
|
|
371
|
+
storyObjectMap.set('Some Story', {
|
|
372
|
+
story: {
|
|
373
|
+
id: 'story-1',
|
|
374
|
+
name: 'Some Story',
|
|
375
|
+
color: 'GRAY',
|
|
376
|
+
description: '',
|
|
377
|
+
},
|
|
378
|
+
storyIssue: null,
|
|
379
|
+
issues: [
|
|
380
|
+
createMockIssue({
|
|
381
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
382
|
+
dependedIssueUrls: ['https://github.com/user/repo/issues/5'],
|
|
383
|
+
}),
|
|
384
|
+
],
|
|
315
385
|
});
|
|
316
386
|
|
|
317
387
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
318
388
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
319
|
-
|
|
320
|
-
createMockComment({ content: 'Some other comment' }),
|
|
321
|
-
]);
|
|
322
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
323
|
-
{
|
|
324
|
-
url: 'https://github.com/user/repo/pull/1',
|
|
325
|
-
isConflicted: false,
|
|
326
|
-
isPassedAllCiJob: true,
|
|
327
|
-
isCiStateSuccess: true,
|
|
328
|
-
isResolvedAllReviewComments: true,
|
|
329
|
-
isBranchOutOfDate: false,
|
|
330
|
-
missingRequiredCheckNames: [],
|
|
331
|
-
},
|
|
332
|
-
]);
|
|
389
|
+
mockIssueRepository.getStoryObjectMap.mockResolvedValue(storyObjectMap);
|
|
333
390
|
|
|
334
391
|
await useCase.run({
|
|
335
392
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -342,39 +399,24 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
342
399
|
});
|
|
343
400
|
|
|
344
401
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
345
|
-
expect.objectContaining({
|
|
346
|
-
status: 'Awaiting Workspace',
|
|
347
|
-
}),
|
|
402
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
348
403
|
mockProject,
|
|
349
404
|
);
|
|
350
405
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
351
|
-
expect.objectContaining({
|
|
352
|
-
|
|
353
|
-
}),
|
|
354
|
-
expect.stringContaining('NO_REPORT_FROM_AGENT_BOT'),
|
|
406
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
407
|
+
expect.stringContaining('Issue has dependent issue URLs:'),
|
|
355
408
|
);
|
|
356
409
|
});
|
|
357
410
|
|
|
358
|
-
it('should
|
|
411
|
+
it('should set status to Awaiting Workspace when issue has nextActionDate set', async () => {
|
|
359
412
|
const issue = createMockIssue({
|
|
360
413
|
url: 'https://github.com/user/repo/issues/1',
|
|
361
414
|
status: 'Preparation',
|
|
415
|
+
nextActionDate: new Date('2026-12-01'),
|
|
362
416
|
});
|
|
363
417
|
|
|
364
418
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
365
419
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
366
|
-
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([]);
|
|
367
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
368
|
-
{
|
|
369
|
-
url: 'https://github.com/user/repo/pull/1',
|
|
370
|
-
isConflicted: false,
|
|
371
|
-
isPassedAllCiJob: true,
|
|
372
|
-
isCiStateSuccess: true,
|
|
373
|
-
isResolvedAllReviewComments: true,
|
|
374
|
-
isBranchOutOfDate: false,
|
|
375
|
-
missingRequiredCheckNames: [],
|
|
376
|
-
},
|
|
377
|
-
]);
|
|
378
420
|
|
|
379
421
|
await useCase.run({
|
|
380
422
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -387,27 +429,24 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
387
429
|
});
|
|
388
430
|
|
|
389
431
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
390
|
-
expect.objectContaining({
|
|
391
|
-
status: 'Awaiting Workspace',
|
|
392
|
-
}),
|
|
432
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
393
433
|
mockProject,
|
|
394
434
|
);
|
|
395
|
-
expect(mockIssueCommentRepository.createComment).
|
|
435
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
436
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
437
|
+
expect.stringContaining('Issue has next action date or hour set:'),
|
|
438
|
+
);
|
|
396
439
|
});
|
|
397
440
|
|
|
398
|
-
it('should
|
|
441
|
+
it('should set status to Awaiting Workspace when issue has nextActionHour set', async () => {
|
|
399
442
|
const issue = createMockIssue({
|
|
400
443
|
url: 'https://github.com/user/repo/issues/1',
|
|
401
444
|
status: 'Preparation',
|
|
445
|
+
nextActionHour: 9,
|
|
402
446
|
});
|
|
403
447
|
|
|
404
448
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
405
449
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
406
|
-
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
407
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
408
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
409
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
410
|
-
]);
|
|
411
450
|
|
|
412
451
|
await useCase.run({
|
|
413
452
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -420,22 +459,16 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
420
459
|
});
|
|
421
460
|
|
|
422
461
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
423
|
-
expect.objectContaining({
|
|
424
|
-
status: 'Awaiting Quality Check',
|
|
425
|
-
}),
|
|
462
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
426
463
|
mockProject,
|
|
427
464
|
);
|
|
428
465
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
429
|
-
expect.objectContaining({
|
|
430
|
-
|
|
431
|
-
}),
|
|
432
|
-
expect.stringContaining(
|
|
433
|
-
'Failed to pass the check autimatically for 3 times',
|
|
434
|
-
),
|
|
466
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
467
|
+
expect.stringContaining('nextActionHour=9'),
|
|
435
468
|
);
|
|
436
469
|
});
|
|
437
470
|
|
|
438
|
-
it('should
|
|
471
|
+
it('should reject and set status to Awaiting Workspace when last comment starts with Auto Status Check:', async () => {
|
|
439
472
|
const issue = createMockIssue({
|
|
440
473
|
url: 'https://github.com/user/repo/issues/1',
|
|
441
474
|
status: 'Preparation',
|
|
@@ -444,8 +477,9 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
444
477
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
445
478
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
446
479
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
447
|
-
createMockComment({
|
|
448
|
-
|
|
480
|
+
createMockComment({
|
|
481
|
+
content: 'Auto Status Check: REJECTED\n["NO_REPORT"]',
|
|
482
|
+
}),
|
|
449
483
|
]);
|
|
450
484
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
451
485
|
{
|
|
@@ -475,9 +509,15 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
475
509
|
}),
|
|
476
510
|
mockProject,
|
|
477
511
|
);
|
|
512
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
513
|
+
expect.objectContaining({
|
|
514
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
515
|
+
}),
|
|
516
|
+
expect.stringContaining('Auto Status Check: REJECTED'),
|
|
517
|
+
);
|
|
478
518
|
});
|
|
479
519
|
|
|
480
|
-
it('should
|
|
520
|
+
it('should reject when last comment does not start with From:', async () => {
|
|
481
521
|
const issue = createMockIssue({
|
|
482
522
|
url: 'https://github.com/user/repo/issues/1',
|
|
483
523
|
status: 'Preparation',
|
|
@@ -486,10 +526,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
486
526
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
487
527
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
488
528
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
489
|
-
createMockComment({ content: '
|
|
490
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
491
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
492
|
-
createMockComment({ content: 'retry' }),
|
|
529
|
+
createMockComment({ content: 'Some other comment' }),
|
|
493
530
|
]);
|
|
494
531
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
495
532
|
{
|
|
@@ -513,15 +550,21 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
513
550
|
workflowBlockerResolvedWebhookUrl: null,
|
|
514
551
|
});
|
|
515
552
|
|
|
516
|
-
expect(mockIssueRepository.update).
|
|
553
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
517
554
|
expect.objectContaining({
|
|
518
|
-
status: 'Awaiting
|
|
555
|
+
status: 'Awaiting Workspace',
|
|
519
556
|
}),
|
|
520
557
|
mockProject,
|
|
521
558
|
);
|
|
559
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
560
|
+
expect.objectContaining({
|
|
561
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
562
|
+
}),
|
|
563
|
+
expect.stringContaining('NO_REPORT_FROM_AGENT_BOT'),
|
|
564
|
+
);
|
|
522
565
|
});
|
|
523
566
|
|
|
524
|
-
it('should
|
|
567
|
+
it('should reject and set status to Awaiting Workspace when no comments exist', async () => {
|
|
525
568
|
const issue = createMockIssue({
|
|
526
569
|
url: 'https://github.com/user/repo/issues/1',
|
|
527
570
|
status: 'Preparation',
|
|
@@ -529,12 +572,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
529
572
|
|
|
530
573
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
531
574
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
532
|
-
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
533
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
534
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
535
|
-
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
536
|
-
createMockComment({ content: 'Retry please' }),
|
|
537
|
-
]);
|
|
575
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([]);
|
|
538
576
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
539
577
|
{
|
|
540
578
|
url: 'https://github.com/user/repo/pull/1',
|
|
@@ -557,15 +595,16 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
557
595
|
workflowBlockerResolvedWebhookUrl: null,
|
|
558
596
|
});
|
|
559
597
|
|
|
560
|
-
expect(mockIssueRepository.update).
|
|
598
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
561
599
|
expect.objectContaining({
|
|
562
|
-
status: 'Awaiting
|
|
600
|
+
status: 'Awaiting Workspace',
|
|
563
601
|
}),
|
|
564
602
|
mockProject,
|
|
565
603
|
);
|
|
604
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalled();
|
|
566
605
|
});
|
|
567
606
|
|
|
568
|
-
it('should reject when
|
|
607
|
+
it('should reject when last comment has REPORT_HAS_NEXT_STEP', async () => {
|
|
569
608
|
const issue = createMockIssue({
|
|
570
609
|
url: 'https://github.com/user/repo/issues/1',
|
|
571
610
|
status: 'Preparation',
|
|
@@ -574,9 +613,22 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
574
613
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
575
614
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
576
615
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
577
|
-
createMockComment({
|
|
616
|
+
createMockComment({
|
|
617
|
+
content:
|
|
618
|
+
'From: Agent report\n```json\n{"nextStep": "Fix the tests"}\n```',
|
|
619
|
+
}),
|
|
620
|
+
]);
|
|
621
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
622
|
+
{
|
|
623
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
624
|
+
isConflicted: false,
|
|
625
|
+
isPassedAllCiJob: true,
|
|
626
|
+
isCiStateSuccess: true,
|
|
627
|
+
isResolvedAllReviewComments: true,
|
|
628
|
+
isBranchOutOfDate: false,
|
|
629
|
+
missingRequiredCheckNames: [],
|
|
630
|
+
},
|
|
578
631
|
]);
|
|
579
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
580
632
|
|
|
581
633
|
await useCase.run({
|
|
582
634
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -589,20 +641,16 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
589
641
|
});
|
|
590
642
|
|
|
591
643
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
592
|
-
expect.objectContaining({
|
|
593
|
-
status: 'Awaiting Workspace',
|
|
594
|
-
}),
|
|
644
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
595
645
|
mockProject,
|
|
596
646
|
);
|
|
597
647
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
598
|
-
expect.objectContaining({
|
|
599
|
-
|
|
600
|
-
}),
|
|
601
|
-
expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
|
|
648
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
649
|
+
expect.stringContaining('REPORT_HAS_NEXT_STEP'),
|
|
602
650
|
);
|
|
603
651
|
});
|
|
604
652
|
|
|
605
|
-
it('should reject when
|
|
653
|
+
it('should not reject when last comment has nextStep set to null', async () => {
|
|
606
654
|
const issue = createMockIssue({
|
|
607
655
|
url: 'https://github.com/user/repo/issues/1',
|
|
608
656
|
status: 'Preparation',
|
|
@@ -611,7 +659,9 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
611
659
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
612
660
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
613
661
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
614
|
-
createMockComment({
|
|
662
|
+
createMockComment({
|
|
663
|
+
content: 'From: Agent report\n```json\n{"nextStep": null}\n```',
|
|
664
|
+
}),
|
|
615
665
|
]);
|
|
616
666
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
617
667
|
{
|
|
@@ -623,15 +673,6 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
623
673
|
isBranchOutOfDate: false,
|
|
624
674
|
missingRequiredCheckNames: [],
|
|
625
675
|
},
|
|
626
|
-
{
|
|
627
|
-
url: 'https://github.com/user/repo/pull/2',
|
|
628
|
-
isConflicted: false,
|
|
629
|
-
isPassedAllCiJob: true,
|
|
630
|
-
isCiStateSuccess: true,
|
|
631
|
-
isResolvedAllReviewComments: true,
|
|
632
|
-
isBranchOutOfDate: false,
|
|
633
|
-
missingRequiredCheckNames: [],
|
|
634
|
-
},
|
|
635
676
|
]);
|
|
636
677
|
|
|
637
678
|
await useCase.run({
|
|
@@ -645,20 +686,12 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
645
686
|
});
|
|
646
687
|
|
|
647
688
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
648
|
-
expect.objectContaining({
|
|
649
|
-
status: 'Awaiting Workspace',
|
|
650
|
-
}),
|
|
689
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
651
690
|
mockProject,
|
|
652
691
|
);
|
|
653
|
-
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
654
|
-
expect.objectContaining({
|
|
655
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
656
|
-
}),
|
|
657
|
-
expect.stringContaining('MULTIPLE_PULL_REQUESTS_FOUND'),
|
|
658
|
-
);
|
|
659
692
|
});
|
|
660
693
|
|
|
661
|
-
it('should
|
|
694
|
+
it('should auto-escalate to Awaiting Quality Check after threshold rejections', async () => {
|
|
662
695
|
const issue = createMockIssue({
|
|
663
696
|
url: 'https://github.com/user/repo/issues/1',
|
|
664
697
|
status: 'Preparation',
|
|
@@ -667,19 +700,11 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
667
700
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
668
701
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
669
702
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
670
|
-
createMockComment({ content: '
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
{
|
|
674
|
-
url: 'https://github.com/user/repo/pull/1',
|
|
675
|
-
isConflicted: true,
|
|
676
|
-
isPassedAllCiJob: true,
|
|
677
|
-
isCiStateSuccess: true,
|
|
678
|
-
isResolvedAllReviewComments: true,
|
|
679
|
-
isBranchOutOfDate: false,
|
|
680
|
-
missingRequiredCheckNames: [],
|
|
681
|
-
},
|
|
703
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
704
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
705
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
682
706
|
]);
|
|
707
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
683
708
|
|
|
684
709
|
await useCase.run({
|
|
685
710
|
projectUrl: 'https://github.com/users/user/projects/1',
|
|
@@ -693,7 +718,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
693
718
|
|
|
694
719
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
695
720
|
expect.objectContaining({
|
|
696
|
-
status: 'Awaiting
|
|
721
|
+
status: 'Awaiting Quality Check',
|
|
697
722
|
}),
|
|
698
723
|
mockProject,
|
|
699
724
|
);
|
|
@@ -701,27 +726,43 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
701
726
|
expect.objectContaining({
|
|
702
727
|
url: 'https://github.com/user/repo/issues/1',
|
|
703
728
|
}),
|
|
704
|
-
expect.stringContaining('
|
|
729
|
+
expect.stringContaining('Auto Status Check:'),
|
|
730
|
+
);
|
|
731
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
732
|
+
expect.objectContaining({
|
|
733
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
734
|
+
}),
|
|
735
|
+
expect.stringContaining(
|
|
736
|
+
'Failed to pass the check automatically for 3 times',
|
|
737
|
+
),
|
|
705
738
|
);
|
|
706
739
|
});
|
|
707
740
|
|
|
708
|
-
it('should
|
|
741
|
+
it('should use APPROVED escalation wording and set PR next action date when current check passes but threshold is met', async () => {
|
|
742
|
+
jest.useFakeTimers();
|
|
743
|
+
const now = new Date('2026-02-01T00:00:00Z');
|
|
744
|
+
jest.setSystemTime(now);
|
|
745
|
+
|
|
709
746
|
const issue = createMockIssue({
|
|
710
747
|
url: 'https://github.com/user/repo/issues/1',
|
|
711
748
|
status: 'Preparation',
|
|
712
749
|
});
|
|
750
|
+
const prUrl = 'https://github.com/user/repo/pull/1';
|
|
713
751
|
|
|
714
752
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
715
753
|
mockIssueRepository.get.mockResolvedValue(issue);
|
|
716
754
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
717
|
-
createMockComment({ content: '
|
|
755
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
756
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
757
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
758
|
+
createMockComment({ content: 'From: Agent final report' }),
|
|
718
759
|
]);
|
|
719
760
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
720
761
|
{
|
|
721
|
-
url:
|
|
762
|
+
url: prUrl,
|
|
722
763
|
isConflicted: false,
|
|
723
|
-
isPassedAllCiJob:
|
|
724
|
-
isCiStateSuccess:
|
|
764
|
+
isPassedAllCiJob: true,
|
|
765
|
+
isCiStateSuccess: true,
|
|
725
766
|
isResolvedAllReviewComments: true,
|
|
726
767
|
isBranchOutOfDate: false,
|
|
727
768
|
missingRequiredCheckNames: [],
|
|
@@ -739,15 +780,398 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
739
780
|
});
|
|
740
781
|
|
|
741
782
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
742
|
-
expect.objectContaining({
|
|
743
|
-
status: 'Awaiting Workspace',
|
|
744
|
-
}),
|
|
783
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
745
784
|
mockProject,
|
|
746
785
|
);
|
|
747
786
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
748
|
-
expect.objectContaining({
|
|
749
|
-
|
|
750
|
-
|
|
787
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
788
|
+
expect.stringContaining(
|
|
789
|
+
'Auto Status Check: APPROVED (escalated due to prior failures)',
|
|
790
|
+
),
|
|
791
|
+
);
|
|
792
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
793
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
794
|
+
expect.stringContaining(
|
|
795
|
+
'Failed to pass the check automatically for 3 times',
|
|
796
|
+
),
|
|
797
|
+
);
|
|
798
|
+
const expectedDate = new Date(now);
|
|
799
|
+
expectedDate.setMonth(expectedDate.getMonth() + 1);
|
|
800
|
+
expect(mockIssueRepository.updateNextActionDate).toHaveBeenCalledWith(
|
|
801
|
+
prUrl,
|
|
802
|
+
mockProject,
|
|
803
|
+
expectedDate,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
jest.useRealTimers();
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
it('should not auto-escalate when rejections are below threshold', async () => {
|
|
810
|
+
const issue = createMockIssue({
|
|
811
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
812
|
+
status: 'Preparation',
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
816
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
817
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
818
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
819
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
820
|
+
]);
|
|
821
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
822
|
+
{
|
|
823
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
824
|
+
isConflicted: false,
|
|
825
|
+
isPassedAllCiJob: true,
|
|
826
|
+
isCiStateSuccess: true,
|
|
827
|
+
isResolvedAllReviewComments: true,
|
|
828
|
+
isBranchOutOfDate: false,
|
|
829
|
+
missingRequiredCheckNames: [],
|
|
830
|
+
},
|
|
831
|
+
]);
|
|
832
|
+
|
|
833
|
+
await useCase.run({
|
|
834
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
835
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
836
|
+
preparationStatus: 'Preparation',
|
|
837
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
838
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
839
|
+
thresholdForAutoReject: 3,
|
|
840
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
844
|
+
expect.objectContaining({
|
|
845
|
+
status: 'Awaiting Workspace',
|
|
846
|
+
}),
|
|
847
|
+
mockProject,
|
|
848
|
+
);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
it('should not auto-escalate when failed-to-pass-check comment exists even if threshold met', async () => {
|
|
852
|
+
const issue = createMockIssue({
|
|
853
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
854
|
+
status: 'Preparation',
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
858
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
859
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
860
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
861
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
862
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
863
|
+
createMockComment({
|
|
864
|
+
content:
|
|
865
|
+
'Auto Status Check: REJECTED\n\nFailed to pass the check automatically for 3 times',
|
|
866
|
+
}),
|
|
867
|
+
]);
|
|
868
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
869
|
+
{
|
|
870
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
871
|
+
isConflicted: false,
|
|
872
|
+
isPassedAllCiJob: true,
|
|
873
|
+
isCiStateSuccess: true,
|
|
874
|
+
isResolvedAllReviewComments: true,
|
|
875
|
+
isBranchOutOfDate: false,
|
|
876
|
+
missingRequiredCheckNames: [],
|
|
877
|
+
},
|
|
878
|
+
]);
|
|
879
|
+
|
|
880
|
+
await useCase.run({
|
|
881
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
882
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
883
|
+
preparationStatus: 'Preparation',
|
|
884
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
885
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
886
|
+
thresholdForAutoReject: 3,
|
|
887
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
expect(mockIssueRepository.update).not.toHaveBeenCalledWith(
|
|
891
|
+
expect.objectContaining({
|
|
892
|
+
status: 'Awaiting Quality Check',
|
|
893
|
+
}),
|
|
894
|
+
mockProject,
|
|
895
|
+
);
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
it('should handle case-insensitive failed-to-pass-check comment', async () => {
|
|
899
|
+
const issue = createMockIssue({
|
|
900
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
901
|
+
status: 'Preparation',
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
905
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
906
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
907
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
908
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
909
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
910
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - fourth' }),
|
|
911
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - fifth' }),
|
|
912
|
+
createMockComment({
|
|
913
|
+
content:
|
|
914
|
+
'AUTO STATUS CHECK: APPROVED\n\nFailed to pass the check automatically for 5 times',
|
|
915
|
+
}),
|
|
916
|
+
]);
|
|
917
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
918
|
+
{
|
|
919
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
920
|
+
isConflicted: false,
|
|
921
|
+
isPassedAllCiJob: true,
|
|
922
|
+
isCiStateSuccess: true,
|
|
923
|
+
isResolvedAllReviewComments: true,
|
|
924
|
+
isBranchOutOfDate: false,
|
|
925
|
+
missingRequiredCheckNames: [],
|
|
926
|
+
},
|
|
927
|
+
]);
|
|
928
|
+
|
|
929
|
+
await useCase.run({
|
|
930
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
931
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
932
|
+
preparationStatus: 'Preparation',
|
|
933
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
934
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
935
|
+
thresholdForAutoReject: 3,
|
|
936
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
expect(mockIssueRepository.update).not.toHaveBeenCalledWith(
|
|
940
|
+
expect.objectContaining({
|
|
941
|
+
status: 'Awaiting Quality Check',
|
|
942
|
+
}),
|
|
943
|
+
mockProject,
|
|
944
|
+
);
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
it('should not auto-escalate when new-format escalation comment with Auto Status Check prefix exists', async () => {
|
|
948
|
+
const issue = createMockIssue({
|
|
949
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
950
|
+
status: 'Preparation',
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
954
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
955
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
956
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - first' }),
|
|
957
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - second' }),
|
|
958
|
+
createMockComment({ content: 'Auto Status Check: REJECTED - third' }),
|
|
959
|
+
createMockComment({
|
|
960
|
+
content:
|
|
961
|
+
'Auto Status Check: APPROVED (escalated due to prior failures)\n\nFailed to pass the check automatically for 3 times',
|
|
962
|
+
}),
|
|
963
|
+
]);
|
|
964
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
965
|
+
{
|
|
966
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
967
|
+
isConflicted: false,
|
|
968
|
+
isPassedAllCiJob: true,
|
|
969
|
+
isCiStateSuccess: true,
|
|
970
|
+
isResolvedAllReviewComments: true,
|
|
971
|
+
isBranchOutOfDate: false,
|
|
972
|
+
missingRequiredCheckNames: [],
|
|
973
|
+
},
|
|
974
|
+
]);
|
|
975
|
+
|
|
976
|
+
await useCase.run({
|
|
977
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
978
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
979
|
+
preparationStatus: 'Preparation',
|
|
980
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
981
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
982
|
+
thresholdForAutoReject: 3,
|
|
983
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
expect(mockIssueRepository.update).not.toHaveBeenCalledWith(
|
|
987
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
988
|
+
mockProject,
|
|
989
|
+
);
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
it('should reject when PR is not found', async () => {
|
|
993
|
+
const issue = createMockIssue({
|
|
994
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
995
|
+
status: 'Preparation',
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
999
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1000
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1001
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1002
|
+
]);
|
|
1003
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
1004
|
+
|
|
1005
|
+
await useCase.run({
|
|
1006
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1007
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1008
|
+
preparationStatus: 'Preparation',
|
|
1009
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1010
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1011
|
+
thresholdForAutoReject: 3,
|
|
1012
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1016
|
+
expect.objectContaining({
|
|
1017
|
+
status: 'Awaiting Workspace',
|
|
1018
|
+
}),
|
|
1019
|
+
mockProject,
|
|
1020
|
+
);
|
|
1021
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1022
|
+
expect.objectContaining({
|
|
1023
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1024
|
+
}),
|
|
1025
|
+
expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
|
|
1026
|
+
);
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
it('should reject when multiple PRs are found', async () => {
|
|
1030
|
+
const issue = createMockIssue({
|
|
1031
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1032
|
+
status: 'Preparation',
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1036
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1037
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1038
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1039
|
+
]);
|
|
1040
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1041
|
+
{
|
|
1042
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
1043
|
+
isConflicted: false,
|
|
1044
|
+
isPassedAllCiJob: true,
|
|
1045
|
+
isCiStateSuccess: true,
|
|
1046
|
+
isResolvedAllReviewComments: true,
|
|
1047
|
+
isBranchOutOfDate: false,
|
|
1048
|
+
missingRequiredCheckNames: [],
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
url: 'https://github.com/user/repo/pull/2',
|
|
1052
|
+
isConflicted: false,
|
|
1053
|
+
isPassedAllCiJob: true,
|
|
1054
|
+
isCiStateSuccess: true,
|
|
1055
|
+
isResolvedAllReviewComments: true,
|
|
1056
|
+
isBranchOutOfDate: false,
|
|
1057
|
+
missingRequiredCheckNames: [],
|
|
1058
|
+
},
|
|
1059
|
+
]);
|
|
1060
|
+
|
|
1061
|
+
await useCase.run({
|
|
1062
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1063
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1064
|
+
preparationStatus: 'Preparation',
|
|
1065
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1066
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1067
|
+
thresholdForAutoReject: 3,
|
|
1068
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1072
|
+
expect.objectContaining({
|
|
1073
|
+
status: 'Awaiting Workspace',
|
|
1074
|
+
}),
|
|
1075
|
+
mockProject,
|
|
1076
|
+
);
|
|
1077
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1078
|
+
expect.objectContaining({
|
|
1079
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1080
|
+
}),
|
|
1081
|
+
expect.stringContaining('MULTIPLE_PULL_REQUESTS_FOUND'),
|
|
1082
|
+
);
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
it('should reject when PR is conflicted', async () => {
|
|
1086
|
+
const issue = createMockIssue({
|
|
1087
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1088
|
+
status: 'Preparation',
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1092
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1093
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1094
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1095
|
+
]);
|
|
1096
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1097
|
+
{
|
|
1098
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
1099
|
+
isConflicted: true,
|
|
1100
|
+
isPassedAllCiJob: true,
|
|
1101
|
+
isCiStateSuccess: true,
|
|
1102
|
+
isResolvedAllReviewComments: true,
|
|
1103
|
+
isBranchOutOfDate: false,
|
|
1104
|
+
missingRequiredCheckNames: [],
|
|
1105
|
+
},
|
|
1106
|
+
]);
|
|
1107
|
+
|
|
1108
|
+
await useCase.run({
|
|
1109
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1110
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1111
|
+
preparationStatus: 'Preparation',
|
|
1112
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1113
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1114
|
+
thresholdForAutoReject: 3,
|
|
1115
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1119
|
+
expect.objectContaining({
|
|
1120
|
+
status: 'Awaiting Workspace',
|
|
1121
|
+
}),
|
|
1122
|
+
mockProject,
|
|
1123
|
+
);
|
|
1124
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1125
|
+
expect.objectContaining({
|
|
1126
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1127
|
+
}),
|
|
1128
|
+
expect.stringContaining('PULL_REQUEST_CONFLICTED'),
|
|
1129
|
+
);
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
it('should reject when CI job failed', async () => {
|
|
1133
|
+
const issue = createMockIssue({
|
|
1134
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1135
|
+
status: 'Preparation',
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1139
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1140
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1141
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1142
|
+
]);
|
|
1143
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1144
|
+
{
|
|
1145
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
1146
|
+
isConflicted: false,
|
|
1147
|
+
isPassedAllCiJob: false,
|
|
1148
|
+
isCiStateSuccess: false,
|
|
1149
|
+
isResolvedAllReviewComments: true,
|
|
1150
|
+
isBranchOutOfDate: false,
|
|
1151
|
+
missingRequiredCheckNames: [],
|
|
1152
|
+
},
|
|
1153
|
+
]);
|
|
1154
|
+
|
|
1155
|
+
await useCase.run({
|
|
1156
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1157
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1158
|
+
preparationStatus: 'Preparation',
|
|
1159
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1160
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1161
|
+
thresholdForAutoReject: 3,
|
|
1162
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1166
|
+
expect.objectContaining({
|
|
1167
|
+
status: 'Awaiting Workspace',
|
|
1168
|
+
}),
|
|
1169
|
+
mockProject,
|
|
1170
|
+
);
|
|
1171
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1172
|
+
expect.objectContaining({
|
|
1173
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1174
|
+
}),
|
|
751
1175
|
expect.stringContaining('ANY_CI_JOB_FAILED_OR_IN_PROGRESS'),
|
|
752
1176
|
);
|
|
753
1177
|
});
|
|
@@ -1012,20 +1436,120 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1012
1436
|
workflowBlockerResolvedWebhookUrl: null,
|
|
1013
1437
|
});
|
|
1014
1438
|
|
|
1015
|
-
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
1439
|
+
expect(mockIssueRepository.findRelatedOpenPRs).toHaveBeenCalled();
|
|
1440
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1441
|
+
expect.objectContaining({
|
|
1442
|
+
status: 'Awaiting Quality Check',
|
|
1443
|
+
}),
|
|
1444
|
+
mockProject,
|
|
1445
|
+
);
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
it('should still check for report comment even when issue has category label', async () => {
|
|
1449
|
+
const issue = createMockIssue({
|
|
1450
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1451
|
+
status: 'Preparation',
|
|
1452
|
+
labels: ['category:backend'],
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1456
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1457
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1458
|
+
createMockComment({
|
|
1459
|
+
content: 'Auto Status Check: REJECTED\n["NO_REPORT"]',
|
|
1460
|
+
}),
|
|
1461
|
+
]);
|
|
1462
|
+
|
|
1463
|
+
await useCase.run({
|
|
1464
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1465
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1466
|
+
preparationStatus: 'Preparation',
|
|
1467
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1468
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1469
|
+
thresholdForAutoReject: 3,
|
|
1470
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
1474
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1475
|
+
expect.objectContaining({
|
|
1476
|
+
status: 'Awaiting Workspace',
|
|
1477
|
+
}),
|
|
1478
|
+
mockProject,
|
|
1479
|
+
);
|
|
1480
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1481
|
+
expect.objectContaining({
|
|
1482
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1483
|
+
}),
|
|
1484
|
+
expect.stringContaining('NO_REPORT_FROM_AGENT_BOT'),
|
|
1485
|
+
);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
it('should skip PR checks and update to Awaiting Quality Check when issue has llm-agent label', async () => {
|
|
1489
|
+
const issue = createMockIssue({
|
|
1490
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1491
|
+
status: 'Preparation',
|
|
1492
|
+
labels: ['llm-agent'],
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1496
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1497
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1498
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1499
|
+
]);
|
|
1500
|
+
|
|
1501
|
+
await useCase.run({
|
|
1502
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1503
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1504
|
+
preparationStatus: 'Preparation',
|
|
1505
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1506
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1507
|
+
thresholdForAutoReject: 3,
|
|
1508
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
1512
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1513
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
1514
|
+
mockProject,
|
|
1515
|
+
);
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
it('should skip PR checks when issue has llm-agent: prefixed label', async () => {
|
|
1519
|
+
const issue = createMockIssue({
|
|
1520
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1521
|
+
status: 'Preparation',
|
|
1522
|
+
labels: ['llm-agent:claude'],
|
|
1523
|
+
});
|
|
1524
|
+
|
|
1525
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1526
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1527
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1528
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1529
|
+
]);
|
|
1530
|
+
|
|
1531
|
+
await useCase.run({
|
|
1532
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1533
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1534
|
+
preparationStatus: 'Preparation',
|
|
1535
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1536
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1537
|
+
thresholdForAutoReject: 3,
|
|
1538
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
1016
1542
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1017
|
-
expect.objectContaining({
|
|
1018
|
-
status: 'Awaiting Quality Check',
|
|
1019
|
-
}),
|
|
1543
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
1020
1544
|
mockProject,
|
|
1021
1545
|
);
|
|
1022
1546
|
});
|
|
1023
1547
|
|
|
1024
|
-
it('should still check for report comment even when issue has
|
|
1548
|
+
it('should still check for report comment even when issue has llm-agent:research label', async () => {
|
|
1025
1549
|
const issue = createMockIssue({
|
|
1026
1550
|
url: 'https://github.com/user/repo/issues/1',
|
|
1027
1551
|
status: 'Preparation',
|
|
1028
|
-
labels: ['
|
|
1552
|
+
labels: ['llm-agent:research'],
|
|
1029
1553
|
});
|
|
1030
1554
|
|
|
1031
1555
|
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
@@ -1048,19 +1572,57 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1048
1572
|
|
|
1049
1573
|
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
1050
1574
|
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1051
|
-
expect.objectContaining({
|
|
1052
|
-
status: 'Awaiting Workspace',
|
|
1053
|
-
}),
|
|
1575
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
1054
1576
|
mockProject,
|
|
1055
1577
|
);
|
|
1056
1578
|
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
1057
|
-
expect.objectContaining({
|
|
1058
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
1059
|
-
}),
|
|
1579
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/issues/1' }),
|
|
1060
1580
|
expect.stringContaining('NO_REPORT_FROM_AGENT_BOT'),
|
|
1061
1581
|
);
|
|
1062
1582
|
});
|
|
1063
1583
|
|
|
1584
|
+
it('should use getOpenPullRequest when issue is a PR item', async () => {
|
|
1585
|
+
const prIssue = createMockIssue({
|
|
1586
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
1587
|
+
status: 'Preparation',
|
|
1588
|
+
isPr: true,
|
|
1589
|
+
});
|
|
1590
|
+
|
|
1591
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1592
|
+
mockIssueRepository.get.mockResolvedValue(prIssue);
|
|
1593
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1594
|
+
createMockComment({ content: 'From: Agent report' }),
|
|
1595
|
+
]);
|
|
1596
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue({
|
|
1597
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
1598
|
+
isConflicted: false,
|
|
1599
|
+
isPassedAllCiJob: true,
|
|
1600
|
+
isCiStateSuccess: true,
|
|
1601
|
+
isResolvedAllReviewComments: true,
|
|
1602
|
+
isBranchOutOfDate: false,
|
|
1603
|
+
missingRequiredCheckNames: [],
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
await useCase.run({
|
|
1607
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1608
|
+
issueUrl: 'https://github.com/user/repo/pull/10',
|
|
1609
|
+
preparationStatus: 'Preparation',
|
|
1610
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1611
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1612
|
+
thresholdForAutoReject: 3,
|
|
1613
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
1617
|
+
'https://github.com/user/repo/pull/10',
|
|
1618
|
+
);
|
|
1619
|
+
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
1620
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1621
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
1622
|
+
mockProject,
|
|
1623
|
+
);
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1064
1626
|
describe('workflow blocker webhook notification', () => {
|
|
1065
1627
|
const createWorkflowBlockerStoryObjectMap = (
|
|
1066
1628
|
issueUrl: string,
|
|
@@ -1161,6 +1723,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1161
1723
|
content: 'Auto Status Check: REJECTED - third',
|
|
1162
1724
|
}),
|
|
1163
1725
|
]);
|
|
1726
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
1164
1727
|
mockIssueRepository.getStoryObjectMap.mockResolvedValue(
|
|
1165
1728
|
createWorkflowBlockerStoryObjectMap(
|
|
1166
1729
|
'https://github.com/user/repo/issues/1',
|
|
@@ -1257,7 +1820,6 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1257
1820
|
workflowBlockerResolvedWebhookUrl: null,
|
|
1258
1821
|
});
|
|
1259
1822
|
|
|
1260
|
-
expect(mockIssueRepository.getStoryObjectMap).not.toHaveBeenCalled();
|
|
1261
1823
|
expect(mockWebhookRepository.sendGetRequest).not.toHaveBeenCalled();
|
|
1262
1824
|
});
|
|
1263
1825
|
|
|
@@ -1372,4 +1934,257 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
1372
1934
|
);
|
|
1373
1935
|
});
|
|
1374
1936
|
});
|
|
1937
|
+
|
|
1938
|
+
it('should continue and not enrich dependedIssueUrls when getStoryObjectMap throws', async () => {
|
|
1939
|
+
const issue = createMockIssue({
|
|
1940
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
1941
|
+
status: 'Preparation',
|
|
1942
|
+
dependedIssueUrls: [],
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1946
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
1947
|
+
mockIssueRepository.getStoryObjectMap.mockRejectedValue(
|
|
1948
|
+
new Error('Story map unavailable'),
|
|
1949
|
+
);
|
|
1950
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1951
|
+
createMockComment({ content: 'From: Test report' }),
|
|
1952
|
+
]);
|
|
1953
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
1954
|
+
{
|
|
1955
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
1956
|
+
isConflicted: false,
|
|
1957
|
+
isPassedAllCiJob: true,
|
|
1958
|
+
isCiStateSuccess: true,
|
|
1959
|
+
isResolvedAllReviewComments: true,
|
|
1960
|
+
isBranchOutOfDate: false,
|
|
1961
|
+
missingRequiredCheckNames: [],
|
|
1962
|
+
},
|
|
1963
|
+
]);
|
|
1964
|
+
|
|
1965
|
+
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
|
1966
|
+
|
|
1967
|
+
await useCase.run({
|
|
1968
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
1969
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
1970
|
+
preparationStatus: 'Preparation',
|
|
1971
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
1972
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
1973
|
+
thresholdForAutoReject: 3,
|
|
1974
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
1978
|
+
'Failed to enrich dependedIssueUrls from story object map:',
|
|
1979
|
+
expect.any(Error),
|
|
1980
|
+
);
|
|
1981
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
1982
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
1983
|
+
mockProject,
|
|
1984
|
+
);
|
|
1985
|
+
|
|
1986
|
+
consoleWarnSpy.mockRestore();
|
|
1987
|
+
});
|
|
1988
|
+
|
|
1989
|
+
it('should return no PRs when getOpenPullRequest returns null for a PR item', async () => {
|
|
1990
|
+
const prIssue = createMockIssue({
|
|
1991
|
+
url: 'https://github.com/user/repo/pull/10',
|
|
1992
|
+
status: 'Preparation',
|
|
1993
|
+
isPr: true,
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
1997
|
+
mockIssueRepository.get.mockResolvedValue(prIssue);
|
|
1998
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
1999
|
+
createMockComment({ content: 'From: Agent report' }),
|
|
2000
|
+
]);
|
|
2001
|
+
mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
|
|
2002
|
+
|
|
2003
|
+
await useCase.run({
|
|
2004
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
2005
|
+
issueUrl: 'https://github.com/user/repo/pull/10',
|
|
2006
|
+
preparationStatus: 'Preparation',
|
|
2007
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2008
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
2009
|
+
thresholdForAutoReject: 3,
|
|
2010
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
expect(mockIssueRepository.getOpenPullRequest).toHaveBeenCalledWith(
|
|
2014
|
+
'https://github.com/user/repo/pull/10',
|
|
2015
|
+
);
|
|
2016
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
2017
|
+
expect.objectContaining({ status: 'Awaiting Workspace' }),
|
|
2018
|
+
mockProject,
|
|
2019
|
+
);
|
|
2020
|
+
expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
|
|
2021
|
+
expect.objectContaining({ url: 'https://github.com/user/repo/pull/10' }),
|
|
2022
|
+
expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
|
|
2023
|
+
);
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
it('should not reject REPORT_HAS_NEXT_STEP when report JSON is invalid', async () => {
|
|
2027
|
+
const issue = createMockIssue({
|
|
2028
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
2029
|
+
status: 'Preparation',
|
|
2030
|
+
});
|
|
2031
|
+
|
|
2032
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2033
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2034
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2035
|
+
createMockComment({
|
|
2036
|
+
content: 'From: Agent report\n```json\n{invalid json}\n```',
|
|
2037
|
+
}),
|
|
2038
|
+
]);
|
|
2039
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
2040
|
+
{
|
|
2041
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
2042
|
+
isConflicted: false,
|
|
2043
|
+
isPassedAllCiJob: true,
|
|
2044
|
+
isCiStateSuccess: true,
|
|
2045
|
+
isResolvedAllReviewComments: true,
|
|
2046
|
+
isBranchOutOfDate: false,
|
|
2047
|
+
missingRequiredCheckNames: [],
|
|
2048
|
+
},
|
|
2049
|
+
]);
|
|
2050
|
+
|
|
2051
|
+
await useCase.run({
|
|
2052
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
2053
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
2054
|
+
preparationStatus: 'Preparation',
|
|
2055
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2056
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
2057
|
+
thresholdForAutoReject: 3,
|
|
2058
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
2059
|
+
});
|
|
2060
|
+
|
|
2061
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
2062
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
2063
|
+
mockProject,
|
|
2064
|
+
);
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
it('should not reject REPORT_HAS_NEXT_STEP when report JSON is null', async () => {
|
|
2068
|
+
const issue = createMockIssue({
|
|
2069
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
2070
|
+
status: 'Preparation',
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2074
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2075
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2076
|
+
createMockComment({
|
|
2077
|
+
content: 'From: Agent report\n```json\nnull\n```',
|
|
2078
|
+
}),
|
|
2079
|
+
]);
|
|
2080
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
2081
|
+
{
|
|
2082
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
2083
|
+
isConflicted: false,
|
|
2084
|
+
isPassedAllCiJob: true,
|
|
2085
|
+
isCiStateSuccess: true,
|
|
2086
|
+
isResolvedAllReviewComments: true,
|
|
2087
|
+
isBranchOutOfDate: false,
|
|
2088
|
+
missingRequiredCheckNames: [],
|
|
2089
|
+
},
|
|
2090
|
+
]);
|
|
2091
|
+
|
|
2092
|
+
await useCase.run({
|
|
2093
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
2094
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
2095
|
+
preparationStatus: 'Preparation',
|
|
2096
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2097
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
2098
|
+
thresholdForAutoReject: 3,
|
|
2099
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
2103
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
2104
|
+
mockProject,
|
|
2105
|
+
);
|
|
2106
|
+
});
|
|
2107
|
+
|
|
2108
|
+
it('should not reject REPORT_HAS_NEXT_STEP when report JSON has no nextStep property', async () => {
|
|
2109
|
+
const issue = createMockIssue({
|
|
2110
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
2111
|
+
status: 'Preparation',
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2115
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2116
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2117
|
+
createMockComment({
|
|
2118
|
+
content:
|
|
2119
|
+
'From: Agent report\n```json\n{"status": "done", "result": "success"}\n```',
|
|
2120
|
+
}),
|
|
2121
|
+
]);
|
|
2122
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
2123
|
+
{
|
|
2124
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
2125
|
+
isConflicted: false,
|
|
2126
|
+
isPassedAllCiJob: true,
|
|
2127
|
+
isCiStateSuccess: true,
|
|
2128
|
+
isResolvedAllReviewComments: true,
|
|
2129
|
+
isBranchOutOfDate: false,
|
|
2130
|
+
missingRequiredCheckNames: [],
|
|
2131
|
+
},
|
|
2132
|
+
]);
|
|
2133
|
+
|
|
2134
|
+
await useCase.run({
|
|
2135
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
2136
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
2137
|
+
preparationStatus: 'Preparation',
|
|
2138
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2139
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
2140
|
+
thresholdForAutoReject: 3,
|
|
2141
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
2145
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
2146
|
+
mockProject,
|
|
2147
|
+
);
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
it('should not reject REPORT_HAS_NEXT_STEP when report JSON is a non-object value', async () => {
|
|
2151
|
+
const issue = createMockIssue({
|
|
2152
|
+
url: 'https://github.com/user/repo/issues/1',
|
|
2153
|
+
status: 'Preparation',
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
2157
|
+
mockIssueRepository.get.mockResolvedValue(issue);
|
|
2158
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
2159
|
+
createMockComment({
|
|
2160
|
+
content: 'From: Agent report\n```json\n"just a string"\n```',
|
|
2161
|
+
}),
|
|
2162
|
+
]);
|
|
2163
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
2164
|
+
{
|
|
2165
|
+
url: 'https://github.com/user/repo/pull/1',
|
|
2166
|
+
isConflicted: false,
|
|
2167
|
+
isPassedAllCiJob: true,
|
|
2168
|
+
isCiStateSuccess: true,
|
|
2169
|
+
isResolvedAllReviewComments: true,
|
|
2170
|
+
isBranchOutOfDate: false,
|
|
2171
|
+
missingRequiredCheckNames: [],
|
|
2172
|
+
},
|
|
2173
|
+
]);
|
|
2174
|
+
|
|
2175
|
+
await useCase.run({
|
|
2176
|
+
projectUrl: 'https://github.com/users/user/projects/1',
|
|
2177
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
2178
|
+
preparationStatus: 'Preparation',
|
|
2179
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
2180
|
+
awaitingQualityCheckStatus: 'Awaiting Quality Check',
|
|
2181
|
+
thresholdForAutoReject: 3,
|
|
2182
|
+
workflowBlockerResolvedWebhookUrl: null,
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
expect(mockIssueRepository.update).toHaveBeenCalledWith(
|
|
2186
|
+
expect.objectContaining({ status: 'Awaiting Quality Check' }),
|
|
2187
|
+
mockProject,
|
|
2188
|
+
);
|
|
2189
|
+
});
|
|
1375
2190
|
});
|