github-issue-tower-defence-management 1.104.0 → 1.104.1

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 (29) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +83 -58
  3. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  4. package/bin/domain/usecases/HandleScheduledEventUseCase.js +7 -2
  5. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  6. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +4 -5
  7. package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
  8. package/bin/domain/usecases/StartPreparationUseCase.js +2 -1
  9. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
  10. package/bin/domain/usecases/resolveAllowedIssueAuthors.js +8 -0
  11. package/bin/domain/usecases/resolveAllowedIssueAuthors.js.map +1 -0
  12. package/package.json +1 -1
  13. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +36 -0
  14. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +23 -1
  15. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +109 -0
  16. package/src/domain/usecases/HandleScheduledEventUseCase.ts +8 -2
  17. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +109 -12
  18. package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +5 -7
  19. package/src/domain/usecases/StartPreparationUseCase.test.ts +202 -88
  20. package/src/domain/usecases/StartPreparationUseCase.ts +2 -1
  21. package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +51 -0
  22. package/src/domain/usecases/resolveAllowedIssueAuthors.ts +6 -0
  23. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  24. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
  25. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  26. package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
  27. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
  28. package/types/domain/usecases/resolveAllowedIssueAuthors.d.ts +5 -0
  29. package/types/domain/usecases/resolveAllowedIssueAuthors.d.ts.map +1 -0
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAllowedIssueAuthors = void 0;
4
+ const resolveAllowedIssueAuthors = (source) => {
5
+ return source.topLevel ?? source.startPreparation ?? null;
6
+ };
7
+ exports.resolveAllowedIssueAuthors = resolveAllowedIssueAuthors;
8
+ //# sourceMappingURL=resolveAllowedIssueAuthors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveAllowedIssueAuthors.js","sourceRoot":"","sources":["../../../src/domain/usecases/resolveAllowedIssueAuthors.ts"],"names":[],"mappings":";;;AAAO,MAAM,0BAA0B,GAAG,CAAC,MAG1C,EAAmB,EAAE;IACpB,OAAO,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,gBAAgB,IAAI,IAAI,CAAC;AAC5D,CAAC,CAAC;AALW,QAAA,0BAA0B,8BAKrC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.104.0",
3
+ "version": "1.104.1",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -384,6 +384,42 @@ allowedIssueAuthors: 'user1, user2, user3'
384
384
  projectUrl: validConfig.projectUrl,
385
385
  });
386
386
  });
387
+
388
+ it('should accept a top-level allowedIssueAuthors from the YAML config and pass it through', async () => {
389
+ mockFetchReturningReadme(null);
390
+ const configWithTopLevelAllowedIssueAuthors = {
391
+ ...validConfig,
392
+ allowedIssueAuthors: ['owner', 'dependabot[bot]'],
393
+ };
394
+ jest
395
+ .mocked(fs.readFileSync)
396
+ .mockReturnValue(YAML.stringify(configWithTopLevelAllowedIssueAuthors));
397
+
398
+ const handler = new HandleScheduledEventUseCaseHandler();
399
+ await handler.handle('config.yml', false);
400
+
401
+ expect(capturedRunInputs[0][0]).toMatchObject({
402
+ allowedIssueAuthors: ['owner', 'dependabot[bot]'],
403
+ });
404
+ });
405
+
406
+ it('should split a comma-separated top-level allowedIssueAuthors string from the YAML config', async () => {
407
+ mockFetchReturningReadme(null);
408
+ const configWithCommaSeparatedAuthors = {
409
+ ...validConfig,
410
+ allowedIssueAuthors: 'owner, umino-bot ,dependabot[bot]',
411
+ };
412
+ jest
413
+ .mocked(fs.readFileSync)
414
+ .mockReturnValue(YAML.stringify(configWithCommaSeparatedAuthors));
415
+
416
+ const handler = new HandleScheduledEventUseCaseHandler();
417
+ await handler.handle('config.yml', false);
418
+
419
+ expect(capturedRunInputs[0][0]).toMatchObject({
420
+ allowedIssueAuthors: ['owner', 'umino-bot', 'dependabot[bot]'],
421
+ });
422
+ });
387
423
  });
388
424
 
389
425
  describe('Claude OAuth token rotation wiring', () => {
@@ -67,7 +67,11 @@ export class HandleScheduledEventUseCaseHandler {
67
67
  } | null> => {
68
68
  const configFileContent = fs.readFileSync(configFilePath, 'utf8');
69
69
  const input: unknown = YAML.parse(configFileContent);
70
- type inputType = Parameters<HandleScheduledEventUseCase['run']>[0] & {
70
+ type inputType = Omit<
71
+ Parameters<HandleScheduledEventUseCase['run']>[0],
72
+ 'allowedIssueAuthors'
73
+ > & {
74
+ allowedIssueAuthors?: string | string[] | null;
71
75
  claudeCodeOauthTokenListJsonPath?: string;
72
76
  consoleDataOutputDir?: string;
73
77
  workflowBlockerStoryName?: string;
@@ -111,8 +115,26 @@ export class HandleScheduledEventUseCaseHandler {
111
115
  ? parseProjectReadmeConfig(readme, input.projectUrl)
112
116
  : {};
113
117
 
118
+ const normalizeAllowedIssueAuthors = (
119
+ value: string | string[] | null | undefined,
120
+ ): string[] | null => {
121
+ if (value === null || value === undefined) {
122
+ return null;
123
+ }
124
+ if (Array.isArray(value)) {
125
+ return value;
126
+ }
127
+ return value
128
+ .split(',')
129
+ .map((s) => s.trim())
130
+ .filter(Boolean);
131
+ };
132
+
114
133
  const mergedInput = {
115
134
  ...input,
135
+ allowedIssueAuthors: normalizeAllowedIssueAuthors(
136
+ input.allowedIssueAuthors,
137
+ ),
116
138
  allowIssueCacheMinutes:
117
139
  readmeConfig.allowIssueCacheMinutes ?? input.allowIssueCacheMinutes,
118
140
  claudeCodeOauthTokenListJsonPath:
@@ -379,6 +379,115 @@ describe('HandleScheduledEventUseCase', () => {
379
379
  ).toHaveBeenCalledTimes(1);
380
380
  });
381
381
 
382
+ it('should pass a top-level allowedIssueAuthors to revertNotReadyReviewQueueIssueUseCase', async () => {
383
+ const input = {
384
+ projectName: 'test-project',
385
+ org: 'test-org',
386
+ projectUrl: 'https://github.com/test-org/test-project',
387
+ manager: 'test-manager',
388
+ workingReport: {
389
+ repo: 'test-repo',
390
+ members: ['member1'],
391
+ spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
392
+ },
393
+ urlOfStoryView: 'https://github.com/test-org/test-project/issues',
394
+ disabled: false,
395
+ allowIssueCacheMinutes: 60,
396
+ allowedIssueAuthors: ['top-level-author'],
397
+ };
398
+
399
+ mockProjectRepository.getProject.mockResolvedValue(mock<Project>());
400
+ await useCase.run(input);
401
+
402
+ expect(
403
+ mockRevertNotReadyReviewQueueIssueUseCase.run,
404
+ ).toHaveBeenCalledWith(
405
+ expect.objectContaining({
406
+ allowedIssueAuthors: ['top-level-author'],
407
+ }),
408
+ );
409
+ });
410
+
411
+ it('should prefer a top-level allowedIssueAuthors over the startPreparation one', async () => {
412
+ const input = {
413
+ projectName: 'test-project',
414
+ org: 'test-org',
415
+ projectUrl: 'https://github.com/test-org/test-project',
416
+ manager: 'test-manager',
417
+ workingReport: {
418
+ repo: 'test-repo',
419
+ members: ['member1'],
420
+ spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
421
+ },
422
+ urlOfStoryView: 'https://github.com/test-org/test-project/issues',
423
+ disabled: false,
424
+ allowIssueCacheMinutes: 60,
425
+ allowedIssueAuthors: ['top-level-author'],
426
+ startPreparation: {
427
+ defaultAgentName: 'agent1',
428
+ configFilePath: '/path/to/config.yml',
429
+ maximumPreparingIssuesCount: null,
430
+ allowedIssueAuthors: ['nested-author'],
431
+ },
432
+ };
433
+
434
+ mockProjectRepository.getProject.mockResolvedValue(mock<Project>());
435
+ mockStartPreparationUseCase.run.mockResolvedValue({
436
+ rotationOrder: null,
437
+ });
438
+ await useCase.run(input);
439
+
440
+ expect(
441
+ mockRevertNotReadyReviewQueueIssueUseCase.run,
442
+ ).toHaveBeenCalledWith(
443
+ expect.objectContaining({
444
+ allowedIssueAuthors: ['top-level-author'],
445
+ }),
446
+ );
447
+ expect(mockStartPreparationUseCase.run).toHaveBeenCalledWith(
448
+ expect.objectContaining({
449
+ allowedIssueAuthors: ['top-level-author'],
450
+ }),
451
+ );
452
+ });
453
+
454
+ it('should fall back to startPreparation allowedIssueAuthors when no top-level value is set', async () => {
455
+ const input = {
456
+ projectName: 'test-project',
457
+ org: 'test-org',
458
+ projectUrl: 'https://github.com/test-org/test-project',
459
+ manager: 'test-manager',
460
+ workingReport: {
461
+ repo: 'test-repo',
462
+ members: ['member1'],
463
+ spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
464
+ },
465
+ urlOfStoryView: 'https://github.com/test-org/test-project/issues',
466
+ disabled: false,
467
+ allowIssueCacheMinutes: 60,
468
+ startPreparation: {
469
+ defaultAgentName: 'agent1',
470
+ configFilePath: '/path/to/config.yml',
471
+ maximumPreparingIssuesCount: null,
472
+ allowedIssueAuthors: ['nested-author'],
473
+ },
474
+ };
475
+
476
+ mockProjectRepository.getProject.mockResolvedValue(mock<Project>());
477
+ mockStartPreparationUseCase.run.mockResolvedValue({
478
+ rotationOrder: null,
479
+ });
480
+ await useCase.run(input);
481
+
482
+ expect(
483
+ mockRevertNotReadyReviewQueueIssueUseCase.run,
484
+ ).toHaveBeenCalledWith(
485
+ expect.objectContaining({
486
+ allowedIssueAuthors: ['nested-author'],
487
+ }),
488
+ );
489
+ });
490
+
382
491
  it('should invoke UpdateRateLimitCacheUseCase before StartPreparationUseCase when startPreparation is configured', async () => {
383
492
  const callOrder: string[] = [];
384
493
  mockUpdateRateLimitCacheUseCase.run.mockImplementation(async () => {
@@ -27,6 +27,7 @@ import {
27
27
  import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
28
28
  import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
29
29
  import { resolveLabelsAsLlmAgentName } from './resolveLabelsAsLlmAgentName';
30
+ import { resolveAllowedIssueAuthors } from './resolveAllowedIssueAuthors';
30
31
  import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
31
32
  import { UpdateRateLimitCacheUseCase } from './UpdateRateLimitCacheUseCase';
32
33
  import {
@@ -86,6 +87,7 @@ export class HandleScheduledEventUseCase {
86
87
  allowIssueCacheMinutes: number;
87
88
  labelsAsLlmAgentName?: string[] | null;
88
89
  changeTargetPathAliases?: Record<string, string> | null;
90
+ allowedIssueAuthors?: string[] | null;
89
91
  startPreparation?: {
90
92
  defaultAgentName: string;
91
93
  defaultLlmModelName?: string | null;
@@ -293,12 +295,16 @@ ${JSON.stringify(e)}
293
295
  topLevel: input.labelsAsLlmAgentName,
294
296
  startPreparation: input.startPreparation?.labelsAsLlmAgentName,
295
297
  });
298
+ const allowedIssueAuthors = resolveAllowedIssueAuthors({
299
+ topLevel: input.allowedIssueAuthors,
300
+ startPreparation: input.startPreparation?.allowedIssueAuthors,
301
+ });
296
302
  await this.revertNotReadyReviewQueueIssueUseCase.run({
297
303
  projectUrl: input.projectUrl,
298
304
  allowIssueCacheMinutes: input.allowIssueCacheMinutes,
299
305
  labelsAsLlmAgentName,
300
306
  changeTargetPathAliases: input.changeTargetPathAliases,
301
- allowedIssueAuthors: input.startPreparation?.allowedIssueAuthors ?? null,
307
+ allowedIssueAuthors,
302
308
  });
303
309
  if (this.dailySecurityScanUseCase !== null && input.dailySecurityScan) {
304
310
  await this.dailySecurityScanUseCase.run({
@@ -341,7 +347,7 @@ ${JSON.stringify(e)}
341
347
  input.startPreparation.maximumPreparingIssuesCount,
342
348
  utilizationPercentageThreshold:
343
349
  input.startPreparation.utilizationPercentageThreshold ?? 90,
344
- allowedIssueAuthors: input.startPreparation.allowedIssueAuthors ?? null,
350
+ allowedIssueAuthors,
345
351
  codexHomeCandidates: input.startPreparation.codexHomeCandidates ?? null,
346
352
  allowIssueCacheMinutes: input.allowIssueCacheMinutes,
347
353
  labelsAsLlmAgentName,
@@ -79,7 +79,7 @@ const createMockIssue = (overrides: Partial<Issue> = {}): Issue => ({
79
79
  isInProgress: false,
80
80
  isClosed: false,
81
81
  createdAt: new Date(),
82
- author: '',
82
+ author: 'owner',
83
83
  closingIssueReferenceUrls: [],
84
84
  ...overrides,
85
85
  });
@@ -171,6 +171,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
171
171
  await useCase.run({
172
172
  projectUrl: 'https://github.com/users/user/projects/1',
173
173
  allowIssueCacheMinutes: 10,
174
+ allowedIssueAuthors: ['owner'],
174
175
  });
175
176
 
176
177
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -190,6 +191,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
190
191
  await useCase.run({
191
192
  projectUrl: 'https://github.com/users/user/projects/1',
192
193
  allowIssueCacheMinutes: 10,
194
+ allowedIssueAuthors: ['owner'],
193
195
  });
194
196
 
195
197
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -209,6 +211,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
209
211
  await useCase.run({
210
212
  projectUrl: 'https://github.com/users/user/projects/1',
211
213
  allowIssueCacheMinutes: 10,
214
+ allowedIssueAuthors: ['owner'],
212
215
  });
213
216
 
214
217
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -241,6 +244,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
241
244
  projectUrl: 'https://github.com/users/user/projects/1',
242
245
  allowIssueCacheMinutes: 10,
243
246
  labelsAsLlmAgentName: ['story', 'chore', 'accounting'],
247
+ allowedIssueAuthors: ['owner'],
244
248
  });
245
249
 
246
250
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -262,6 +266,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
262
266
  projectUrl: 'https://github.com/users/user/projects/1',
263
267
  allowIssueCacheMinutes: 10,
264
268
  labelsAsLlmAgentName: ['story', 'chore'],
269
+ allowedIssueAuthors: ['owner'],
265
270
  });
266
271
 
267
272
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -282,6 +287,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
282
287
  await useCase.run({
283
288
  projectUrl: 'https://github.com/users/user/projects/1',
284
289
  allowIssueCacheMinutes: 10,
290
+ allowedIssueAuthors: ['owner'],
285
291
  });
286
292
 
287
293
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -310,6 +316,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
310
316
  projectUrl: 'https://github.com/users/user/projects/1',
311
317
  allowIssueCacheMinutes: 10,
312
318
  labelsAsLlmAgentName: null,
319
+ allowedIssueAuthors: ['owner'],
313
320
  });
314
321
 
315
322
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -334,6 +341,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
334
341
  await useCase.run({
335
342
  projectUrl: 'https://github.com/users/user/projects/1',
336
343
  allowIssueCacheMinutes: 10,
344
+ allowedIssueAuthors: ['owner'],
337
345
  });
338
346
 
339
347
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -355,6 +363,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
355
363
  await useCase.run({
356
364
  projectUrl: 'https://github.com/users/user/projects/1',
357
365
  allowIssueCacheMinutes: 10,
366
+ allowedIssueAuthors: ['owner'],
358
367
  });
359
368
 
360
369
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -391,6 +400,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
391
400
  await useCase.run({
392
401
  projectUrl: 'https://github.com/users/user/projects/1',
393
402
  allowIssueCacheMinutes: 10,
403
+ allowedIssueAuthors: ['owner'],
394
404
  });
395
405
 
396
406
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -423,6 +433,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
423
433
  await useCase.run({
424
434
  projectUrl: 'https://github.com/users/user/projects/1',
425
435
  allowIssueCacheMinutes: 10,
436
+ allowedIssueAuthors: ['owner'],
426
437
  });
427
438
 
428
439
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -455,6 +466,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
455
466
  await useCase.run({
456
467
  projectUrl: 'https://github.com/users/user/projects/1',
457
468
  allowIssueCacheMinutes: 10,
469
+ allowedIssueAuthors: ['owner'],
458
470
  });
459
471
 
460
472
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -488,6 +500,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
488
500
  await useCase.run({
489
501
  projectUrl: 'https://github.com/users/user/projects/1',
490
502
  allowIssueCacheMinutes: 10,
503
+ allowedIssueAuthors: ['owner'],
491
504
  });
492
505
 
493
506
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -525,6 +538,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
525
538
  await useCase.run({
526
539
  projectUrl: 'https://github.com/users/user/projects/1',
527
540
  allowIssueCacheMinutes: 10,
541
+ allowedIssueAuthors: ['owner'],
528
542
  });
529
543
 
530
544
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -562,6 +576,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
562
576
  useCase.run({
563
577
  projectUrl: 'https://github.com/users/user/projects/1',
564
578
  allowIssueCacheMinutes: 10,
579
+ allowedIssueAuthors: ['owner'],
565
580
  });
566
581
 
567
582
  it('should not approve PR when issue has no change-target label', async () => {
@@ -773,6 +788,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
773
788
  await useCase.run({
774
789
  projectUrl: 'https://github.com/users/user/projects/1',
775
790
  allowIssueCacheMinutes: 10,
791
+ allowedIssueAuthors: ['owner'],
776
792
  changeTargetPathAliases: {
777
793
  adapters: 'src/domain/usecases/adapter-interfaces',
778
794
  },
@@ -792,6 +808,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
792
808
  await useCase.run({
793
809
  projectUrl: 'https://github.com/users/user/projects/1',
794
810
  allowIssueCacheMinutes: 10,
811
+ allowedIssueAuthors: ['owner'],
795
812
  changeTargetPathAliases: {
796
813
  adapters: 'src/domain/usecases/adapter-interfaces',
797
814
  },
@@ -815,6 +832,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
815
832
  await useCase.run({
816
833
  projectUrl: 'https://github.com/users/user/projects/1',
817
834
  allowIssueCacheMinutes: 10,
835
+ allowedIssueAuthors: ['owner'],
818
836
  });
819
837
 
820
838
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -836,6 +854,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
836
854
  await useCase.run({
837
855
  projectUrl: 'https://github.com/users/user/projects/1',
838
856
  allowIssueCacheMinutes: 10,
857
+ allowedIssueAuthors: ['owner'],
839
858
  });
840
859
 
841
860
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -856,6 +875,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
856
875
  await useCase.run({
857
876
  projectUrl: 'https://github.com/users/user/projects/1',
858
877
  allowIssueCacheMinutes: 10,
878
+ allowedIssueAuthors: ['owner'],
859
879
  });
860
880
 
861
881
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -876,6 +896,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
876
896
  await useCase.run({
877
897
  projectUrl: 'https://github.com/users/user/projects/1',
878
898
  allowIssueCacheMinutes: 10,
899
+ allowedIssueAuthors: ['owner'],
879
900
  });
880
901
 
881
902
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -899,6 +920,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
899
920
  await useCase.run({
900
921
  projectUrl: 'https://github.com/users/user/projects/1',
901
922
  allowIssueCacheMinutes: 10,
923
+ allowedIssueAuthors: ['owner'],
902
924
  });
903
925
 
904
926
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -938,6 +960,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
938
960
  await useCase.run({
939
961
  projectUrl: 'https://github.com/users/user/projects/1',
940
962
  allowIssueCacheMinutes: 10,
963
+ allowedIssueAuthors: ['owner'],
941
964
  });
942
965
 
943
966
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -972,6 +995,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
972
995
  await useCase.run({
973
996
  projectUrl: 'https://github.com/users/user/projects/1',
974
997
  allowIssueCacheMinutes: 10,
998
+ allowedIssueAuthors: ['owner'],
975
999
  });
976
1000
 
977
1001
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -1008,6 +1032,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1008
1032
  await useCase.run({
1009
1033
  projectUrl: 'https://github.com/users/user/projects/1',
1010
1034
  allowIssueCacheMinutes: 10,
1035
+ allowedIssueAuthors: ['owner'],
1011
1036
  });
1012
1037
 
1013
1038
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -1042,6 +1067,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1042
1067
  await useCase.run({
1043
1068
  projectUrl: 'https://github.com/users/user/projects/1',
1044
1069
  allowIssueCacheMinutes: 10,
1070
+ allowedIssueAuthors: ['owner'],
1045
1071
  });
1046
1072
 
1047
1073
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -1073,6 +1099,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1073
1099
  await useCase.run({
1074
1100
  projectUrl: 'https://github.com/users/user/projects/1',
1075
1101
  allowIssueCacheMinutes: 10,
1102
+ allowedIssueAuthors: ['owner'],
1076
1103
  });
1077
1104
 
1078
1105
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -1110,6 +1137,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1110
1137
  await useCase.run({
1111
1138
  projectUrl: 'https://github.com/users/user/projects/1',
1112
1139
  allowIssueCacheMinutes: 10,
1140
+ allowedIssueAuthors: ['owner'],
1113
1141
  });
1114
1142
 
1115
1143
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
@@ -1154,6 +1182,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1154
1182
  await useCase.run({
1155
1183
  projectUrl: 'https://github.com/users/user/projects/1',
1156
1184
  allowIssueCacheMinutes: 10,
1185
+ allowedIssueAuthors: ['owner'],
1157
1186
  });
1158
1187
 
1159
1188
  expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
@@ -1223,7 +1252,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1223
1252
  );
1224
1253
  });
1225
1254
 
1226
- it('should act on an Unread pull request authored by a dependency-update bot not in allowedIssueAuthors', async () => {
1255
+ it('should skip an Unread pull request authored by a dependency-update bot not in allowedIssueAuthors', async () => {
1227
1256
  const pullRequest = createMockPullRequest({
1228
1257
  status: 'Unread',
1229
1258
  author: 'dependabot[bot]',
@@ -1243,6 +1272,31 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1243
1272
  allowedIssueAuthors: ['owner'],
1244
1273
  });
1245
1274
 
1275
+ expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1276
+ expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1277
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1278
+ });
1279
+
1280
+ it('should act on a dependency-update bot only when it is explicitly listed in allowedIssueAuthors', async () => {
1281
+ const pullRequest = createMockPullRequest({
1282
+ status: 'Unread',
1283
+ author: 'dependabot[bot]',
1284
+ });
1285
+ mockIssueRepository.getAllIssues.mockResolvedValue({
1286
+ issues: [pullRequest],
1287
+ cacheUsed: false,
1288
+ });
1289
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1290
+ ...createReadyPr(),
1291
+ isConflicted: true,
1292
+ });
1293
+
1294
+ await useCase.run({
1295
+ projectUrl: 'https://github.com/users/user/projects/1',
1296
+ allowIssueCacheMinutes: 10,
1297
+ allowedIssueAuthors: ['owner', 'dependabot[bot]'],
1298
+ });
1299
+
1246
1300
  expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1247
1301
  mockProject,
1248
1302
  pullRequest,
@@ -1254,7 +1308,7 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1254
1308
  );
1255
1309
  });
1256
1310
 
1257
- it('should act on every author when allowedIssueAuthors is null', async () => {
1311
+ it('should process no author when allowedIssueAuthors is null', async () => {
1258
1312
  const pullRequest = createMockPullRequest({
1259
1313
  status: 'Unread',
1260
1314
  author: 'outside-contributor',
@@ -1274,15 +1328,58 @@ describe('RevertNotReadyReviewQueueIssueUseCase', () => {
1274
1328
  allowedIssueAuthors: null,
1275
1329
  });
1276
1330
 
1277
- expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
1278
- mockProject,
1279
- pullRequest,
1280
- 'awaiting-workspace-id',
1281
- );
1282
- expect(mockIssueCommentRepository.createComment).toHaveBeenCalledWith(
1283
- pullRequest,
1284
- expect.stringContaining('Auto Status Check: REJECTED'),
1285
- );
1331
+ expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1332
+ expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1333
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1334
+ });
1335
+
1336
+ it('should process no author when allowedIssueAuthors is undefined', async () => {
1337
+ const pullRequest = createMockPullRequest({
1338
+ status: 'Unread',
1339
+ author: 'owner',
1340
+ });
1341
+ mockIssueRepository.getAllIssues.mockResolvedValue({
1342
+ issues: [pullRequest],
1343
+ cacheUsed: false,
1344
+ });
1345
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1346
+ ...createReadyPr(),
1347
+ isConflicted: true,
1348
+ });
1349
+
1350
+ await useCase.run({
1351
+ projectUrl: 'https://github.com/users/user/projects/1',
1352
+ allowIssueCacheMinutes: 10,
1353
+ });
1354
+
1355
+ expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1356
+ expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1357
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1358
+ });
1359
+
1360
+ it('should process no author when allowedIssueAuthors is an empty list', async () => {
1361
+ const pullRequest = createMockPullRequest({
1362
+ status: 'Unread',
1363
+ author: 'owner',
1364
+ });
1365
+ mockIssueRepository.getAllIssues.mockResolvedValue({
1366
+ issues: [pullRequest],
1367
+ cacheUsed: false,
1368
+ });
1369
+ mockIssueRepository.getOpenPullRequest.mockResolvedValue({
1370
+ ...createReadyPr(),
1371
+ isConflicted: true,
1372
+ });
1373
+
1374
+ await useCase.run({
1375
+ projectUrl: 'https://github.com/users/user/projects/1',
1376
+ allowIssueCacheMinutes: 10,
1377
+ allowedIssueAuthors: [],
1378
+ });
1379
+
1380
+ expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
1381
+ expect(mockIssueRepository.updateStory).not.toHaveBeenCalled();
1382
+ expect(mockIssueCommentRepository.createComment).not.toHaveBeenCalled();
1286
1383
  });
1287
1384
  });
1288
1385
  });
@@ -9,17 +9,15 @@ import {
9
9
  DEFAULT_STATUS_NAME,
10
10
  } from '../entities/WorkflowStatus';
11
11
 
12
- const DEPENDENCY_UPDATE_BOT_AUTHORS = ['dependabot[bot]', 'renovate[bot]'];
13
-
14
12
  const isAuthorAuthorizedForAutoStatusCheck = (
15
13
  author: string,
16
- allowedIssueAuthors: string[] | null,
14
+ allowedIssueAuthors: string[] | null | undefined,
17
15
  ): boolean => {
18
- if (DEPENDENCY_UPDATE_BOT_AUTHORS.includes(author)) {
19
- return true;
16
+ if (allowedIssueAuthors === null || allowedIssueAuthors === undefined) {
17
+ return false;
20
18
  }
21
- if (allowedIssueAuthors === null) {
22
- return true;
19
+ if (allowedIssueAuthors.length === 0) {
20
+ return false;
23
21
  }
24
22
  return allowedIssueAuthors.includes(author);
25
23
  };