github-issue-tower-defence-management 1.174.2 → 1.175.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 (82) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/bin/adapter/entry-points/console/consoleDoneStore.js +0 -2
  3. package/bin/adapter/entry-points/console/consoleDoneStore.js.map +1 -1
  4. package/bin/adapter/entry-points/console/consoleTabNames.js +0 -1
  5. package/bin/adapter/entry-points/console/consoleTabNames.js.map +1 -1
  6. package/bin/adapter/entry-points/console/dashboardComposeService.js +2 -4
  7. package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
  8. package/bin/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  9. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  10. package/bin/domain/entities/WorkflowStatus.js +1 -6
  11. package/bin/domain/entities/WorkflowStatus.js.map +1 -1
  12. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +4 -5
  13. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
  14. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +0 -39
  15. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  16. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +4 -5
  17. package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
  18. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +15 -1
  19. package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
  20. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +0 -2
  21. package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -1
  22. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +2 -3
  23. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -1
  24. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js +0 -1
  25. package/bin/domain/usecases/dashboard/GenerateDashboardRowUseCase.js.map +1 -1
  26. package/bin/domain/usecases/findLastAgentDeclaringReport.js +12 -0
  27. package/bin/domain/usecases/findLastAgentDeclaringReport.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/adapter/entry-points/console/consoleDoneStore.ts +0 -2
  30. package/src/adapter/entry-points/console/consoleTabNames.ts +0 -1
  31. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +4 -8
  32. package/src/adapter/entry-points/console/dashboardComposeService.ts +1 -3
  33. package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +12 -17
  34. package/src/adapter/entry-points/console/ui/e2e/consoleScenario.spec.ts +4 -32
  35. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -18
  36. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +3 -7
  37. package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +2 -4
  38. package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleOperationMenu.test.tsx +1 -1
  39. package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleNavigation.test.ts +6 -8
  40. package/src/adapter/entry-points/console/ui/src/features/console/logic/tabAdvance.test.ts +8 -17
  41. package/src/adapter/entry-points/console/ui/src/features/console/logic/types.ts +0 -2
  42. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +3 -3
  43. package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +5 -6
  44. package/src/adapter/entry-points/console/ui-dist/assets/{index-g4SqIzCh.js → index-BhDothvr.js} +1 -1
  45. package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
  46. package/src/adapter/entry-points/console/webServer.test.ts +4 -6
  47. package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +30 -26
  48. package/src/adapter/entry-points/handlers/dashboardRowWriter.test.ts +5 -6
  49. package/src/domain/entities/WorkflowStatus.ts +0 -5
  50. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +32 -0
  51. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -8
  52. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +198 -863
  53. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +0 -68
  54. package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +8 -11
  55. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +89 -51
  56. package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +30 -2
  57. package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +62 -72
  58. package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +0 -8
  59. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +19 -28
  60. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +2 -3
  61. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.test.ts +11 -12
  62. package/src/domain/usecases/dashboard/GenerateDashboardRowUseCase.ts +0 -3
  63. package/src/domain/usecases/findLastAgentDeclaringReport.test.ts +71 -0
  64. package/src/domain/usecases/findLastAgentDeclaringReport.ts +17 -0
  65. package/src/domain/usecases/intmux/GenerateInTmuxByHumanDataUseCase.test.ts +2 -2
  66. package/types/adapter/entry-points/console/consoleDoneStore.d.ts.map +1 -1
  67. package/types/adapter/entry-points/console/consoleTabNames.d.ts.map +1 -1
  68. package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
  69. package/types/domain/entities/WorkflowStatus.d.ts +0 -1
  70. package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
  71. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
  72. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  73. package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
  74. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +1 -0
  75. package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
  76. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +1 -2
  77. package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -1
  78. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -1
  79. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts +0 -1
  80. package/types/domain/usecases/dashboard/GenerateDashboardRowUseCase.d.ts.map +1 -1
  81. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts +5 -0
  82. package/types/domain/usecases/findLastAgentDeclaringReport.d.ts.map +1 -0
@@ -24,12 +24,6 @@ const createMockProject = (overrides: Partial<Project> = {}): Project => ({
24
24
  color: 'BLUE',
25
25
  description: '',
26
26
  },
27
- {
28
- id: 'unread-id',
29
- name: 'Unread',
30
- color: 'ORANGE',
31
- description: '',
32
- },
33
27
  ],
34
28
  },
35
29
  nextActionDate: null,
@@ -90,7 +84,7 @@ const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
90
84
  const createMockPullRequest = (overrides: Partial<Issue> = {}): Issue =>
91
85
  createMockIssue({
92
86
  title: 'Test PR',
93
- status: 'Unread',
87
+ status: 'Awaiting Workspace',
94
88
  url: 'https://github.com/user/repo/pull/1',
95
89
  isPr: true,
96
90
  ...overrides,
@@ -1142,39 +1136,43 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1142
1136
  });
1143
1137
  });
1144
1138
 
1145
- describe('Unread pull request processing', () => {
1146
- it('should do nothing when there are no Unread pull requests', async () => {
1147
- mockIssueRepository.getAllIssues.mockResolvedValue({
1148
- project: mockProject,
1149
- issues: [
1150
- createMockPullRequest({ status: 'Awaiting Workspace' }),
1151
- createMockPullRequest({ status: 'Preparation' }),
1152
- ],
1153
- cacheUsed: false,
1154
- });
1139
+ describe('archived project item containment', () => {
1140
+ let warnSpy: jest.SpyInstance;
1155
1141
 
1156
- await useCase.run({
1157
- manager: 'manager-user',
1158
- projectUrl: 'https://github.com/users/user/projects/1',
1159
- allowedIssueAuthors: ['owner'],
1160
- });
1142
+ beforeEach(() => {
1143
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
1144
+ });
1161
1145
 
1162
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1163
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1164
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1146
+ afterEach(() => {
1147
+ warnSpy.mockRestore();
1165
1148
  });
1166
1149
 
1167
- it('should skip Unread issue that is not a pull request', async () => {
1168
- const issue = createMockIssue({
1169
- status: 'Unread',
1170
- isPr: false,
1150
+ it('should skip an archived issue on updateStatus failure and continue with remaining issues', async () => {
1151
+ const archivedIssue = createMockIssue({
1152
+ number: 1,
1171
1153
  url: 'https://github.com/user/repo/issues/1',
1154
+ itemId: 'archived-item',
1155
+ status: 'Awaiting Quality Check',
1156
+ });
1157
+ const normalIssue = createMockIssue({
1158
+ number: 2,
1159
+ url: 'https://github.com/user/repo/issues/2',
1160
+ itemId: 'normal-item',
1161
+ status: 'Awaiting Quality Check',
1172
1162
  });
1173
1163
  mockIssueRepository.getAllIssues.mockResolvedValue({
1174
1164
  project: mockProject,
1175
- issues: [issue],
1165
+ issues: [archivedIssue, normalIssue],
1176
1166
  cacheUsed: false,
1177
1167
  });
1168
+ mockIssueRepository.updateStatus.mockImplementation(
1169
+ (_project: Project, issue: Issue) =>
1170
+ issue.url === archivedIssue.url
1171
+ ? Promise.reject(
1172
+ new Error('The item is archived and cannot be updated'),
1173
+ )
1174
+ : Promise.resolve(undefined),
1175
+ );
1178
1176
 
1179
1177
  await useCase.run({
1180
1178
  manager: 'manager-user',
@@ -1182,49 +1180,86 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1182
1180
  allowedIssueAuthors: ['owner'],
1183
1181
  });
1184
1182
 
1185
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1186
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1187
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1183
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1184
+ mockProject,
1185
+ archivedIssue,
1186
+ 'awaiting-workspace-id',
1187
+ );
1188
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1189
+ mockProject,
1190
+ normalIssue,
1191
+ 'awaiting-workspace-id',
1192
+ );
1193
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1194
+ archivedIssue,
1195
+ expect.anything(),
1196
+ );
1197
+ expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1198
+ normalIssue,
1199
+ expect.stringContaining('Auto Status Check: REJECTED'),
1200
+ );
1201
+ expect(warnSpy).toHaveBeenCalledWith(
1202
+ expect.stringContaining(archivedIssue.url),
1203
+ );
1188
1204
  });
1189
1205
 
1190
- it('should skip Unread pull request with non-developer agent field', async () => {
1191
- const pullRequest = createMockPullRequest({
1192
- status: 'Unread',
1193
- labels: [],
1194
- agent: 'chore',
1206
+ it('should propagate a non-archived updateStatus error for issues unchanged', async () => {
1207
+ const issue = createMockIssue({
1208
+ status: 'Awaiting Quality Check',
1195
1209
  });
1196
1210
  mockIssueRepository.getAllIssues.mockResolvedValue({
1197
1211
  project: mockProject,
1198
- issues: [pullRequest],
1212
+ issues: [issue],
1199
1213
  cacheUsed: false,
1200
1214
  });
1215
+ mockIssueRepository.updateStatus.mockRejectedValue(
1216
+ new Error('Something went wrong'),
1217
+ );
1201
1218
 
1202
- await useCase.run({
1203
- manager: 'manager-user',
1204
- projectUrl: 'https://github.com/users/user/projects/1',
1205
- allowedIssueAuthors: ['owner'],
1206
- });
1219
+ await expect(
1220
+ useCase.run({
1221
+ manager: 'manager-user',
1222
+ projectUrl: 'https://github.com/users/user/projects/1',
1223
+ allowedIssueAuthors: ['owner'],
1224
+ }),
1225
+ ).rejects.toThrow('Something went wrong');
1207
1226
 
1208
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1209
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1210
1227
  expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1211
1228
  });
1212
1229
 
1213
- it('should revert Unread pull request with developer agent field when it is in draft state', async () => {
1214
- const pullRequest = createMockPullRequest({
1215
- status: 'Unread',
1216
- labels: ['llm-agent:developer'],
1217
- agent: 'developer',
1230
+ it('should skip an archived Awaiting Quality Check pull request on updateStatus failure and continue with remaining pull requests', async () => {
1231
+ const archivedPullRequest = createMockPullRequest({
1232
+ number: 1,
1233
+ url: 'https://github.com/user/repo/pull/1',
1234
+ itemId: 'archived-pr-item',
1235
+ status: 'Awaiting Quality Check',
1236
+ });
1237
+ const normalPullRequest = createMockPullRequest({
1238
+ number: 2,
1239
+ url: 'https://github.com/user/repo/pull/2',
1240
+ itemId: 'normal-pr-item',
1241
+ status: 'Awaiting Quality Check',
1218
1242
  });
1219
1243
  mockIssueRepository.getAllIssues.mockResolvedValue({
1220
1244
  project: mockProject,
1221
- issues: [pullRequest],
1245
+ issues: [archivedPullRequest, normalPullRequest],
1222
1246
  cacheUsed: false,
1223
1247
  });
1224
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1225
- ...createReadyPr(),
1226
- isDraft: true,
1227
- });
1248
+ mockIssueRepository.getOpenPullRequest.mockImplementation(
1249
+ (prUrl: string) =>
1250
+ Promise.resolve({
1251
+ ...createReadyPr(prUrl),
1252
+ isConflicted: true,
1253
+ }),
1254
+ );
1255
+ mockIssueRepository.updateStatus.mockImplementation(
1256
+ (_project: Project, issue: Issue) =>
1257
+ issue.url === archivedPullRequest.url
1258
+ ? Promise.reject(
1259
+ new Error('The item is archived and cannot be updated'),
1260
+ )
1261
+ : Promise.resolve(undefined),
1262
+ );
1228
1263
 
1229
1264
  await useCase.run({
1230
1265
  manager: 'manager-user',
@@ -1234,50 +1269,105 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1234
1269
 
1235
1270
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1236
1271
  mockProject,
1237
- pullRequest,
1272
+ archivedPullRequest,
1273
+ 'awaiting-workspace-id',
1274
+ );
1275
+ expect(mockIssueRepository.updateStory).not.toHaveBeenCalledWith(
1276
+ expect.anything(),
1277
+ archivedPullRequest,
1278
+ expect.anything(),
1279
+ );
1280
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1281
+ archivedPullRequest,
1282
+ expect.anything(),
1283
+ );
1284
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1285
+ mockProject,
1286
+ normalPullRequest,
1238
1287
  'awaiting-workspace-id',
1239
1288
  );
1240
1289
  expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1241
- pullRequest,
1242
- expect.stringContaining('PULL_REQUEST_IS_DRAFT'),
1290
+ normalPullRequest,
1291
+ expect.stringContaining('Auto Status Check: REJECTED'),
1292
+ );
1293
+ expect(warnSpy).toHaveBeenCalledWith(
1294
+ expect.stringContaining(archivedPullRequest.url),
1243
1295
  );
1244
1296
  });
1245
1297
 
1246
- it('should not move Unread pull request when it is review-ready', async () => {
1298
+ it('should propagate a non-archived updateStatus error for Awaiting Quality Check pull requests unchanged', async () => {
1247
1299
  const pullRequest = createMockPullRequest({
1248
- status: 'Unread',
1300
+ status: 'Awaiting Quality Check',
1249
1301
  });
1250
1302
  mockIssueRepository.getAllIssues.mockResolvedValue({
1251
1303
  project: mockProject,
1252
1304
  issues: [pullRequest],
1253
1305
  cacheUsed: false,
1254
1306
  });
1255
- mockIssueRepository.getOpenPullRequest.mockResolvedValue(createReadyPr());
1256
-
1257
- await useCase.run({
1258
- manager: 'manager-user',
1259
- projectUrl: 'https://github.com/users/user/projects/1',
1260
- allowedIssueAuthors: ['owner'],
1307
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1308
+ ...createReadyPr(),
1309
+ isConflicted: true,
1261
1310
  });
1311
+ mockIssueRepository.updateStatus.mockRejectedValue(
1312
+ new Error('Something went wrong'),
1313
+ );
1314
+
1315
+ await expect(
1316
+ useCase.run({
1317
+ manager: 'manager-user',
1318
+ projectUrl: 'https://github.com/users/user/projects/1',
1319
+ allowedIssueAuthors: ['owner'],
1320
+ }),
1321
+ ).rejects.toThrow('Something went wrong');
1262
1322
 
1263
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1264
1323
  expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1265
1324
  expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1266
1325
  });
1326
+ });
1267
1327
 
1268
- it('should move Unread pull request to Awaiting Workspace and set Story to workflow management when PR is conflicted', async () => {
1269
- const pullRequest = createMockPullRequest({
1270
- status: 'Unread',
1328
+ describe('ky TimeoutError containment', () => {
1329
+ let warnSpy: jest.SpyInstance;
1330
+
1331
+ const createKyTimeoutError = (): Error => {
1332
+ const error = new Error(
1333
+ 'Request timed out: POST https://api.github.com/graphql',
1334
+ );
1335
+ error.name = 'TimeoutError';
1336
+ return error;
1337
+ };
1338
+
1339
+ beforeEach(() => {
1340
+ warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
1341
+ });
1342
+
1343
+ afterEach(() => {
1344
+ warnSpy.mockRestore();
1345
+ });
1346
+
1347
+ it('should skip an issue whose updateStatus times out and continue with remaining issues', async () => {
1348
+ const timedOutIssue = createMockIssue({
1349
+ number: 1,
1350
+ url: 'https://github.com/user/repo/issues/1',
1351
+ itemId: 'timed-out-item',
1352
+ status: 'Awaiting Quality Check',
1353
+ });
1354
+ const normalIssue = createMockIssue({
1355
+ number: 2,
1356
+ url: 'https://github.com/user/repo/issues/2',
1357
+ itemId: 'normal-item',
1358
+ status: 'Awaiting Quality Check',
1271
1359
  });
1272
1360
  mockIssueRepository.getAllIssues.mockResolvedValue({
1273
1361
  project: mockProject,
1274
- issues: [pullRequest],
1362
+ issues: [timedOutIssue, normalIssue],
1275
1363
  cacheUsed: false,
1276
1364
  });
1277
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1278
- ...createReadyPr(),
1279
- isConflicted: true,
1280
- });
1365
+ mockIssueRepository.updateStatus.mockImplementation(
1366
+ (_project: Project, issue: Issue) =>
1367
+ issue.url === timedOutIssue.url
1368
+ ? Promise.reject(createKyTimeoutError())
1369
+ : Promise.resolve(undefined),
1370
+ );
1281
1371
 
1282
1372
  await useCase.run({
1283
1373
  manager: 'manager-user',
@@ -1287,762 +1377,43 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1287
1377
 
1288
1378
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1289
1379
  mockProject,
1290
- pullRequest,
1380
+ timedOutIssue,
1291
1381
  'awaiting-workspace-id',
1292
1382
  );
1293
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1294
- expect.objectContaining({ id: 'project-1' }),
1295
- pullRequest,
1296
- 'workflow-management-story-id',
1383
+ expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1384
+ mockProject,
1385
+ normalIssue,
1386
+ 'awaiting-workspace-id',
1387
+ );
1388
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1389
+ timedOutIssue,
1390
+ expect.anything(),
1297
1391
  );
1298
1392
  expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1299
- pullRequest,
1393
+ normalIssue,
1300
1394
  expect.stringContaining('Auto Status Check: REJECTED'),
1301
1395
  );
1302
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1303
- pullRequest,
1304
- expect.stringContaining('PULL_REQUEST_CONFLICTED'),
1396
+ expect(warnSpy).toHaveBeenCalledWith(
1397
+ expect.stringContaining(timedOutIssue.url),
1305
1398
  );
1306
1399
  });
1307
1400
 
1308
- it('should move Unread pull request when CI is failing', async () => {
1309
- const pullRequest = createMockPullRequest({
1310
- status: 'Unread',
1401
+ it('should skip an issue whose createComment times out and continue with remaining issues', async () => {
1402
+ const timedOutIssue = createMockIssue({
1403
+ number: 1,
1404
+ url: 'https://github.com/user/repo/issues/1',
1405
+ itemId: 'timed-out-item',
1406
+ status: 'Awaiting Quality Check',
1407
+ });
1408
+ const normalIssue = createMockIssue({
1409
+ number: 2,
1410
+ url: 'https://github.com/user/repo/issues/2',
1411
+ itemId: 'normal-item',
1412
+ status: 'Awaiting Quality Check',
1311
1413
  });
1312
1414
  mockIssueRepository.getAllIssues.mockResolvedValue({
1313
1415
  project: mockProject,
1314
- issues: [pullRequest],
1315
- cacheUsed: false,
1316
- });
1317
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1318
- ...createReadyPr(),
1319
- isPassedAllCiJob: false,
1320
- isCiStateSuccess: false,
1321
- });
1322
-
1323
- await useCase.run({
1324
- manager: 'manager-user',
1325
- projectUrl: 'https://github.com/users/user/projects/1',
1326
- allowedIssueAuthors: ['owner'],
1327
- });
1328
-
1329
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1330
- mockProject,
1331
- pullRequest,
1332
- 'awaiting-workspace-id',
1333
- );
1334
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1335
- expect.objectContaining({ id: 'project-1' }),
1336
- pullRequest,
1337
- 'workflow-management-story-id',
1338
- );
1339
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1340
- pullRequest,
1341
- expect.stringContaining('ANY_CI_JOB_FAILED_OR_IN_PROGRESS'),
1342
- );
1343
- });
1344
-
1345
- it('should move Unread pull request when it is in draft state', async () => {
1346
- const pullRequest = createMockPullRequest({
1347
- status: 'Unread',
1348
- });
1349
- mockIssueRepository.getAllIssues.mockResolvedValue({
1350
- project: mockProject,
1351
- issues: [pullRequest],
1352
- cacheUsed: false,
1353
- });
1354
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1355
- ...createReadyPr(),
1356
- isDraft: true,
1357
- });
1358
-
1359
- await useCase.run({
1360
- manager: 'manager-user',
1361
- projectUrl: 'https://github.com/users/user/projects/1',
1362
- allowedIssueAuthors: ['owner'],
1363
- });
1364
-
1365
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1366
- mockProject,
1367
- pullRequest,
1368
- 'awaiting-workspace-id',
1369
- );
1370
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1371
- expect.objectContaining({ id: 'project-1' }),
1372
- pullRequest,
1373
- 'workflow-management-story-id',
1374
- );
1375
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1376
- pullRequest,
1377
- expect.stringContaining('PULL_REQUEST_IS_DRAFT'),
1378
- );
1379
- });
1380
-
1381
- it('should move Unread pull request when CI is SUCCESS but required check never started', async () => {
1382
- const pullRequest = createMockPullRequest({
1383
- status: 'Unread',
1384
- });
1385
- mockIssueRepository.getAllIssues.mockResolvedValue({
1386
- project: mockProject,
1387
- issues: [pullRequest],
1388
- cacheUsed: false,
1389
- });
1390
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1391
- ...createReadyPr(),
1392
- isPassedAllCiJob: false,
1393
- isCiStateSuccess: true,
1394
- missingRequiredCheckNames: ['E2E Tests'],
1395
- });
1396
-
1397
- await useCase.run({
1398
- manager: 'manager-user',
1399
- projectUrl: 'https://github.com/users/user/projects/1',
1400
- allowedIssueAuthors: ['owner'],
1401
- });
1402
-
1403
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1404
- mockProject,
1405
- pullRequest,
1406
- 'awaiting-workspace-id',
1407
- );
1408
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1409
- expect.objectContaining({ id: 'project-1' }),
1410
- pullRequest,
1411
- 'workflow-management-story-id',
1412
- );
1413
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1414
- pullRequest,
1415
- expect.stringContaining('REQUIRED_CI_JOB_NEVER_STARTED'),
1416
- );
1417
- });
1418
-
1419
- it('should move Unread pull request when review comments are not resolved', async () => {
1420
- const pullRequest = createMockPullRequest({
1421
- status: 'Unread',
1422
- });
1423
- mockIssueRepository.getAllIssues.mockResolvedValue({
1424
- project: mockProject,
1425
- issues: [pullRequest],
1426
- cacheUsed: false,
1427
- });
1428
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1429
- ...createReadyPr(),
1430
- isResolvedAllReviewComments: false,
1431
- });
1432
-
1433
- await useCase.run({
1434
- manager: 'manager-user',
1435
- projectUrl: 'https://github.com/users/user/projects/1',
1436
- allowedIssueAuthors: ['owner'],
1437
- });
1438
-
1439
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1440
- mockProject,
1441
- pullRequest,
1442
- 'awaiting-workspace-id',
1443
- );
1444
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1445
- expect.objectContaining({ id: 'project-1' }),
1446
- pullRequest,
1447
- 'workflow-management-story-id',
1448
- );
1449
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1450
- pullRequest,
1451
- expect.stringContaining('ANY_REVIEW_COMMENT_NOT_RESOLVED'),
1452
- );
1453
- });
1454
-
1455
- it('should move Unread pull request when no open PR is found at the PR URL', async () => {
1456
- const pullRequest = createMockPullRequest({
1457
- status: 'Unread',
1458
- });
1459
- mockIssueRepository.getAllIssues.mockResolvedValue({
1460
- project: mockProject,
1461
- issues: [pullRequest],
1462
- cacheUsed: false,
1463
- });
1464
- mockIssueRepository.getOpenPullRequest.mockResolvedValue(null);
1465
-
1466
- await useCase.run({
1467
- manager: 'manager-user',
1468
- projectUrl: 'https://github.com/users/user/projects/1',
1469
- allowedIssueAuthors: ['owner'],
1470
- });
1471
-
1472
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1473
- mockProject,
1474
- pullRequest,
1475
- 'awaiting-workspace-id',
1476
- );
1477
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1478
- expect.objectContaining({ id: 'project-1' }),
1479
- pullRequest,
1480
- 'workflow-management-story-id',
1481
- );
1482
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1483
- pullRequest,
1484
- expect.stringContaining('PULL_REQUEST_NOT_FOUND'),
1485
- );
1486
- });
1487
-
1488
- it('should skip updateStory when project has no story field configured', async () => {
1489
- const projectWithoutStory = createMockProject({ story: null });
1490
- mockProjectRepository.getProject.mockResolvedValue(projectWithoutStory);
1491
-
1492
- const pullRequest = createMockPullRequest({
1493
- status: 'Unread',
1494
- });
1495
- mockIssueRepository.getAllIssues.mockResolvedValue({
1496
- project: mockProject,
1497
- issues: [pullRequest],
1498
- cacheUsed: false,
1499
- });
1500
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1501
- ...createReadyPr(),
1502
- isConflicted: true,
1503
- });
1504
-
1505
- await useCase.run({
1506
- manager: 'manager-user',
1507
- projectUrl: 'https://github.com/users/user/projects/1',
1508
- allowedIssueAuthors: ['owner'],
1509
- });
1510
-
1511
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1512
- projectWithoutStory,
1513
- pullRequest,
1514
- 'awaiting-workspace-id',
1515
- );
1516
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1517
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1518
- pullRequest,
1519
- expect.stringContaining('Auto Status Check: REJECTED'),
1520
- );
1521
- });
1522
-
1523
- it('should return early when Awaiting Workspace status option does not exist', async () => {
1524
- const projectWithoutAwaitingWorkspace = createMockProject({
1525
- status: {
1526
- name: 'Status',
1527
- fieldId: 'field-1',
1528
- statuses: [
1529
- {
1530
- id: 'unread-id',
1531
- name: 'Unread',
1532
- color: 'ORANGE',
1533
- description: '',
1534
- },
1535
- ],
1536
- },
1537
- });
1538
- mockProjectRepository.getProject.mockResolvedValue(
1539
- projectWithoutAwaitingWorkspace,
1540
- );
1541
-
1542
- const pullRequest = createMockPullRequest({
1543
- status: 'Unread',
1544
- });
1545
- mockIssueRepository.getAllIssues.mockResolvedValue({
1546
- project: mockProject,
1547
- issues: [pullRequest],
1548
- cacheUsed: false,
1549
- });
1550
-
1551
- await useCase.run({
1552
- manager: 'manager-user',
1553
- projectUrl: 'https://github.com/users/user/projects/1',
1554
- allowedIssueAuthors: ['owner'],
1555
- });
1556
-
1557
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1558
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1559
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1560
- });
1561
-
1562
- describe('author authorization guard', () => {
1563
- it('should skip an Unread pull request authored by a non-owner human not in allowedIssueAuthors', async () => {
1564
- const pullRequest = createMockPullRequest({
1565
- status: 'Unread',
1566
- author: 'outside-contributor',
1567
- });
1568
- mockIssueRepository.getAllIssues.mockResolvedValue({
1569
- project: mockProject,
1570
- issues: [pullRequest],
1571
- cacheUsed: false,
1572
- });
1573
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1574
- ...createReadyPr(),
1575
- isConflicted: true,
1576
- });
1577
-
1578
- await useCase.run({
1579
- manager: 'manager-user',
1580
- projectUrl: 'https://github.com/users/user/projects/1',
1581
- allowedIssueAuthors: ['owner'],
1582
- });
1583
-
1584
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1585
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1586
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1587
- });
1588
-
1589
- it('should act on an Unread pull request authored by an allowed owner', async () => {
1590
- const pullRequest = createMockPullRequest({
1591
- status: 'Unread',
1592
- author: 'owner',
1593
- });
1594
- mockIssueRepository.getAllIssues.mockResolvedValue({
1595
- project: mockProject,
1596
- issues: [pullRequest],
1597
- cacheUsed: false,
1598
- });
1599
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1600
- ...createReadyPr(),
1601
- isConflicted: true,
1602
- });
1603
-
1604
- await useCase.run({
1605
- manager: 'manager-user',
1606
- projectUrl: 'https://github.com/users/user/projects/1',
1607
- allowedIssueAuthors: ['owner'],
1608
- });
1609
-
1610
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1611
- mockProject,
1612
- pullRequest,
1613
- 'awaiting-workspace-id',
1614
- );
1615
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1616
- expect.objectContaining({ id: 'project-1' }),
1617
- pullRequest,
1618
- 'workflow-management-story-id',
1619
- );
1620
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1621
- pullRequest,
1622
- expect.stringContaining('Auto Status Check: REJECTED'),
1623
- );
1624
- });
1625
-
1626
- it('should skip an Unread pull request authored by a dependency-update bot not in allowedIssueAuthors', async () => {
1627
- const pullRequest = createMockPullRequest({
1628
- status: 'Unread',
1629
- author: 'dependabot[bot]',
1630
- });
1631
- mockIssueRepository.getAllIssues.mockResolvedValue({
1632
- project: mockProject,
1633
- issues: [pullRequest],
1634
- cacheUsed: false,
1635
- });
1636
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1637
- ...createReadyPr(),
1638
- isConflicted: true,
1639
- });
1640
-
1641
- await useCase.run({
1642
- manager: 'manager-user',
1643
- projectUrl: 'https://github.com/users/user/projects/1',
1644
- allowedIssueAuthors: ['owner'],
1645
- });
1646
-
1647
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1648
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1649
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1650
- });
1651
-
1652
- it('should act on a dependency-update bot only when it is explicitly listed in allowedIssueAuthors', async () => {
1653
- const pullRequest = createMockPullRequest({
1654
- status: 'Unread',
1655
- author: 'dependabot[bot]',
1656
- });
1657
- mockIssueRepository.getAllIssues.mockResolvedValue({
1658
- project: mockProject,
1659
- issues: [pullRequest],
1660
- cacheUsed: false,
1661
- });
1662
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1663
- ...createReadyPr(),
1664
- isConflicted: true,
1665
- });
1666
-
1667
- await useCase.run({
1668
- manager: 'manager-user',
1669
- projectUrl: 'https://github.com/users/user/projects/1',
1670
- allowedIssueAuthors: ['owner', 'dependabot[bot]'],
1671
- });
1672
-
1673
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1674
- mockProject,
1675
- pullRequest,
1676
- 'awaiting-workspace-id',
1677
- );
1678
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1679
- pullRequest,
1680
- expect.stringContaining('Auto Status Check: REJECTED'),
1681
- );
1682
- });
1683
-
1684
- it('should process no author when allowedIssueAuthors is null', async () => {
1685
- const pullRequest = createMockPullRequest({
1686
- status: 'Unread',
1687
- author: 'outside-contributor',
1688
- });
1689
- mockIssueRepository.getAllIssues.mockResolvedValue({
1690
- project: mockProject,
1691
- issues: [pullRequest],
1692
- cacheUsed: false,
1693
- });
1694
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1695
- ...createReadyPr(),
1696
- isConflicted: true,
1697
- });
1698
-
1699
- await useCase.run({
1700
- manager: 'manager-user',
1701
- projectUrl: 'https://github.com/users/user/projects/1',
1702
- allowedIssueAuthors: null,
1703
- });
1704
-
1705
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1706
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1707
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1708
- });
1709
-
1710
- it('should process no author when allowedIssueAuthors is undefined', async () => {
1711
- const pullRequest = createMockPullRequest({
1712
- status: 'Unread',
1713
- author: 'owner',
1714
- });
1715
- mockIssueRepository.getAllIssues.mockResolvedValue({
1716
- project: mockProject,
1717
- issues: [pullRequest],
1718
- cacheUsed: false,
1719
- });
1720
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1721
- ...createReadyPr(),
1722
- isConflicted: true,
1723
- });
1724
-
1725
- await useCase.run({
1726
- manager: 'manager-user',
1727
- projectUrl: 'https://github.com/users/user/projects/1',
1728
- });
1729
-
1730
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1731
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1732
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1733
- });
1734
-
1735
- it('should process no author when allowedIssueAuthors is an empty list', async () => {
1736
- const pullRequest = createMockPullRequest({
1737
- status: 'Unread',
1738
- author: 'owner',
1739
- });
1740
- mockIssueRepository.getAllIssues.mockResolvedValue({
1741
- project: mockProject,
1742
- issues: [pullRequest],
1743
- cacheUsed: false,
1744
- });
1745
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1746
- ...createReadyPr(),
1747
- isConflicted: true,
1748
- });
1749
-
1750
- await useCase.run({
1751
- manager: 'manager-user',
1752
- projectUrl: 'https://github.com/users/user/projects/1',
1753
- allowedIssueAuthors: [],
1754
- });
1755
-
1756
- expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1757
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1758
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1759
- });
1760
- });
1761
- });
1762
-
1763
- describe('archived project item containment', () => {
1764
- let warnSpy: jest.SpyInstance;
1765
-
1766
- beforeEach(() => {
1767
- warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
1768
- });
1769
-
1770
- afterEach(() => {
1771
- warnSpy.mockRestore();
1772
- });
1773
-
1774
- it('should skip an archived issue on updateStatus failure and continue with remaining issues', async () => {
1775
- const archivedIssue = createMockIssue({
1776
- number: 1,
1777
- url: 'https://github.com/user/repo/issues/1',
1778
- itemId: 'archived-item',
1779
- status: 'Awaiting Quality Check',
1780
- });
1781
- const normalIssue = createMockIssue({
1782
- number: 2,
1783
- url: 'https://github.com/user/repo/issues/2',
1784
- itemId: 'normal-item',
1785
- status: 'Awaiting Quality Check',
1786
- });
1787
- mockIssueRepository.getAllIssues.mockResolvedValue({
1788
- project: mockProject,
1789
- issues: [archivedIssue, normalIssue],
1790
- cacheUsed: false,
1791
- });
1792
- mockIssueRepository.updateStatus.mockImplementation(
1793
- (_project: Project, issue: Issue) =>
1794
- issue.url === archivedIssue.url
1795
- ? Promise.reject(
1796
- new Error('The item is archived and cannot be updated'),
1797
- )
1798
- : Promise.resolve(undefined),
1799
- );
1800
-
1801
- await useCase.run({
1802
- manager: 'manager-user',
1803
- projectUrl: 'https://github.com/users/user/projects/1',
1804
- allowedIssueAuthors: ['owner'],
1805
- });
1806
-
1807
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1808
- mockProject,
1809
- archivedIssue,
1810
- 'awaiting-workspace-id',
1811
- );
1812
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1813
- mockProject,
1814
- normalIssue,
1815
- 'awaiting-workspace-id',
1816
- );
1817
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1818
- archivedIssue,
1819
- expect.anything(),
1820
- );
1821
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1822
- normalIssue,
1823
- expect.stringContaining('Auto Status Check: REJECTED'),
1824
- );
1825
- expect(warnSpy).toHaveBeenCalledWith(
1826
- expect.stringContaining(archivedIssue.url),
1827
- );
1828
- });
1829
-
1830
- it('should propagate a non-archived updateStatus error for issues unchanged', async () => {
1831
- const issue = createMockIssue({
1832
- status: 'Awaiting Quality Check',
1833
- });
1834
- mockIssueRepository.getAllIssues.mockResolvedValue({
1835
- project: mockProject,
1836
- issues: [issue],
1837
- cacheUsed: false,
1838
- });
1839
- mockIssueRepository.updateStatus.mockRejectedValue(
1840
- new Error('Something went wrong'),
1841
- );
1842
-
1843
- await expect(
1844
- useCase.run({
1845
- manager: 'manager-user',
1846
- projectUrl: 'https://github.com/users/user/projects/1',
1847
- allowedIssueAuthors: ['owner'],
1848
- }),
1849
- ).rejects.toThrow('Something went wrong');
1850
-
1851
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1852
- });
1853
-
1854
- it('should skip an archived Unread pull request on updateStatus failure and continue with remaining pull requests', async () => {
1855
- const archivedPullRequest = createMockPullRequest({
1856
- number: 1,
1857
- url: 'https://github.com/user/repo/pull/1',
1858
- itemId: 'archived-pr-item',
1859
- status: 'Unread',
1860
- });
1861
- const normalPullRequest = createMockPullRequest({
1862
- number: 2,
1863
- url: 'https://github.com/user/repo/pull/2',
1864
- itemId: 'normal-pr-item',
1865
- status: 'Unread',
1866
- });
1867
- mockIssueRepository.getAllIssues.mockResolvedValue({
1868
- project: mockProject,
1869
- issues: [archivedPullRequest, normalPullRequest],
1870
- cacheUsed: false,
1871
- });
1872
- mockIssueRepository.getOpenPullRequest.mockImplementation(
1873
- (prUrl: string) =>
1874
- Promise.resolve({
1875
- ...createReadyPr(prUrl),
1876
- isConflicted: true,
1877
- }),
1878
- );
1879
- mockIssueRepository.updateStatus.mockImplementation(
1880
- (_project: Project, issue: Issue) =>
1881
- issue.url === archivedPullRequest.url
1882
- ? Promise.reject(
1883
- new Error('The item is archived and cannot be updated'),
1884
- )
1885
- : Promise.resolve(undefined),
1886
- );
1887
-
1888
- await useCase.run({
1889
- manager: 'manager-user',
1890
- projectUrl: 'https://github.com/users/user/projects/1',
1891
- allowedIssueAuthors: ['owner'],
1892
- });
1893
-
1894
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1895
- mockProject,
1896
- archivedPullRequest,
1897
- 'awaiting-workspace-id',
1898
- );
1899
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalledWith(
1900
- expect.anything(),
1901
- archivedPullRequest,
1902
- expect.anything(),
1903
- );
1904
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
1905
- archivedPullRequest,
1906
- expect.anything(),
1907
- );
1908
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1909
- mockProject,
1910
- normalPullRequest,
1911
- 'awaiting-workspace-id',
1912
- );
1913
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
1914
- expect.objectContaining({ id: 'project-1' }),
1915
- normalPullRequest,
1916
- 'workflow-management-story-id',
1917
- );
1918
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1919
- normalPullRequest,
1920
- expect.stringContaining('Auto Status Check: REJECTED'),
1921
- );
1922
- expect(warnSpy).toHaveBeenCalledWith(
1923
- expect.stringContaining(archivedPullRequest.url),
1924
- );
1925
- });
1926
-
1927
- it('should propagate a non-archived updateStatus error for Unread pull requests unchanged', async () => {
1928
- const pullRequest = createMockPullRequest({
1929
- status: 'Unread',
1930
- });
1931
- mockIssueRepository.getAllIssues.mockResolvedValue({
1932
- project: mockProject,
1933
- issues: [pullRequest],
1934
- cacheUsed: false,
1935
- });
1936
- mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1937
- ...createReadyPr(),
1938
- isConflicted: true,
1939
- });
1940
- mockIssueRepository.updateStatus.mockRejectedValue(
1941
- new Error('Something went wrong'),
1942
- );
1943
-
1944
- await expect(
1945
- useCase.run({
1946
- manager: 'manager-user',
1947
- projectUrl: 'https://github.com/users/user/projects/1',
1948
- allowedIssueAuthors: ['owner'],
1949
- }),
1950
- ).rejects.toThrow('Something went wrong');
1951
-
1952
- expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1953
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1954
- });
1955
- });
1956
-
1957
- describe('ky TimeoutError containment', () => {
1958
- let warnSpy: jest.SpyInstance;
1959
-
1960
- const createKyTimeoutError = (): Error => {
1961
- const error = new Error(
1962
- 'Request timed out: POST https://api.github.com/graphql',
1963
- );
1964
- error.name = 'TimeoutError';
1965
- return error;
1966
- };
1967
-
1968
- beforeEach(() => {
1969
- warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
1970
- });
1971
-
1972
- afterEach(() => {
1973
- warnSpy.mockRestore();
1974
- });
1975
-
1976
- it('should skip an issue whose updateStatus times out and continue with remaining issues', async () => {
1977
- const timedOutIssue = createMockIssue({
1978
- number: 1,
1979
- url: 'https://github.com/user/repo/issues/1',
1980
- itemId: 'timed-out-item',
1981
- status: 'Awaiting Quality Check',
1982
- });
1983
- const normalIssue = createMockIssue({
1984
- number: 2,
1985
- url: 'https://github.com/user/repo/issues/2',
1986
- itemId: 'normal-item',
1987
- status: 'Awaiting Quality Check',
1988
- });
1989
- mockIssueRepository.getAllIssues.mockResolvedValue({
1990
- project: mockProject,
1991
- issues: [timedOutIssue, normalIssue],
1992
- cacheUsed: false,
1993
- });
1994
- mockIssueRepository.updateStatus.mockImplementation(
1995
- (_project: Project, issue: Issue) =>
1996
- issue.url === timedOutIssue.url
1997
- ? Promise.reject(createKyTimeoutError())
1998
- : Promise.resolve(undefined),
1999
- );
2000
-
2001
- await useCase.run({
2002
- manager: 'manager-user',
2003
- projectUrl: 'https://github.com/users/user/projects/1',
2004
- allowedIssueAuthors: ['owner'],
2005
- });
2006
-
2007
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
2008
- mockProject,
2009
- timedOutIssue,
2010
- 'awaiting-workspace-id',
2011
- );
2012
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
2013
- mockProject,
2014
- normalIssue,
2015
- 'awaiting-workspace-id',
2016
- );
2017
- expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalledWith(
2018
- timedOutIssue,
2019
- expect.anything(),
2020
- );
2021
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
2022
- normalIssue,
2023
- expect.stringContaining('Auto Status Check: REJECTED'),
2024
- );
2025
- expect(warnSpy).toHaveBeenCalledWith(
2026
- expect.stringContaining(timedOutIssue.url),
2027
- );
2028
- });
2029
-
2030
- it('should skip an issue whose createComment times out and continue with remaining issues', async () => {
2031
- const timedOutIssue = createMockIssue({
2032
- number: 1,
2033
- url: 'https://github.com/user/repo/issues/1',
2034
- itemId: 'timed-out-item',
2035
- status: 'Awaiting Quality Check',
2036
- });
2037
- const normalIssue = createMockIssue({
2038
- number: 2,
2039
- url: 'https://github.com/user/repo/issues/2',
2040
- itemId: 'normal-item',
2041
- status: 'Awaiting Quality Check',
2042
- });
2043
- mockIssueRepository.getAllIssues.mockResolvedValue({
2044
- project: mockProject,
2045
- issues: [timedOutIssue, normalIssue],
1416
+ issues: [timedOutIssue, normalIssue],
2046
1417
  cacheUsed: false,
2047
1418
  });
2048
1419
  mockIssueCommentRepository.createComment.mockImplementation(
@@ -2067,18 +1438,18 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
2067
1438
  );
2068
1439
  });
2069
1440
 
2070
- it('should skip an Unread pull request whose updateStatus times out and continue with remaining pull requests', async () => {
1441
+ it('should skip an Awaiting Quality Check pull request whose updateStatus times out and continue with remaining pull requests', async () => {
2071
1442
  const timedOutPullRequest = createMockPullRequest({
2072
1443
  number: 1,
2073
1444
  url: 'https://github.com/user/repo/pull/1',
2074
1445
  itemId: 'timed-out-pr-item',
2075
- status: 'Unread',
1446
+ status: 'Awaiting Quality Check',
2076
1447
  });
2077
1448
  const normalPullRequest = createMockPullRequest({
2078
1449
  number: 2,
2079
1450
  url: 'https://github.com/user/repo/pull/2',
2080
1451
  itemId: 'normal-pr-item',
2081
- status: 'Unread',
1452
+ status: 'Awaiting Quality Check',
2082
1453
  });
2083
1454
  mockIssueRepository.getAllIssues.mockResolvedValue({
2084
1455
  project: mockProject,
@@ -2124,11 +1495,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
2124
1495
  normalPullRequest,
2125
1496
  'awaiting-workspace-id',
2126
1497
  );
2127
- expect(mockIssueRepository.updateStory).toHaveBeenCalledWith(
2128
- expect.objectContaining({ id: 'project-1' }),
2129
- normalPullRequest,
2130
- 'workflow-management-story-id',
2131
- );
2132
1498
  expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
2133
1499
  normalPullRequest,
2134
1500
  expect.stringContaining('Auto Status Check: REJECTED'),
@@ -2211,9 +1577,9 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
2211
1577
  );
2212
1578
  });
2213
1579
 
2214
- it('should not revert a rejected Unread pull request that is not assigned to the manager', async () => {
1580
+ it('should not revert a rejected Awaiting Quality Check pull request that is not assigned to the manager', async () => {
2215
1581
  const pullRequest = createMockPullRequest({
2216
- status: 'Unread',
1582
+ status: 'Awaiting Quality Check',
2217
1583
  assignees: ['other-user'],
2218
1584
  });
2219
1585
  mockIssueRepository.getAllIssues.mockResolvedValue({
@@ -2237,9 +1603,9 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
2237
1603
  expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
2238
1604
  });
2239
1605
 
2240
- it('should revert a rejected Unread pull request that is assigned to the manager', async () => {
1606
+ it('should revert a rejected Awaiting Quality Check pull request that is assigned to the manager', async () => {
2241
1607
  const pullRequest = createMockPullRequest({
2242
- status: 'Unread',
1608
+ status: 'Awaiting Quality Check',
2243
1609
  assignees: ['manager-user'],
2244
1610
  });
2245
1611
  mockIssueRepository.getAllIssues.mockResolvedValue({
@@ -2335,37 +1701,6 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
2335
1701
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
2336
1702
  });
2337
1703
 
2338
- it('includes the unread pull requests in the same batched call', async () => {
2339
- const unreadPrUrl = 'https://github.com/user/repo/pull/400';
2340
- const { boardIssues, relatedPrs } = buildAwaitingQualityCheckBoard(1);
2341
- mockIssueRepository.getAllIssues.mockResolvedValue({
2342
- project: mockProject,
2343
- issues: [
2344
- ...boardIssues,
2345
- createMockPullRequest({
2346
- url: unreadPrUrl,
2347
- number: 400,
2348
- itemId: 'item-pr-unread',
2349
- }),
2350
- ],
2351
- cacheUsed: false,
2352
- });
2353
- resolveBatchFrom([...relatedPrs, createReadyPr(unreadPrUrl)]);
2354
-
2355
- await useCase.run({
2356
- projectUrl,
2357
- manager: 'manager-user',
2358
- allowedIssueAuthors: ['owner'],
2359
- });
2360
-
2361
- expect(mockIssueRepository.getOpenPullRequests).toHaveBeenCalledTimes(1);
2362
- expect(mockIssueRepository.getOpenPullRequests).toHaveBeenCalledWith([
2363
- relatedPrs[0].url,
2364
- unreadPrUrl,
2365
- ]);
2366
- expect(mockIssueRepository.getOpenPullRequest).not.toHaveBeenCalled();
2367
- });
2368
-
2369
1704
  it('falls back to the single pull request query only for a url the batch left unresolved', async () => {
2370
1705
  const { boardIssues, relatedPrs } = buildAwaitingQualityCheckBoard(2);
2371
1706
  const resolvedPr = relatedPrs[0];