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.
- package/CHANGELOG.md +7 -0
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +83 -58
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +7 -2
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +4 -5
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +2 -1
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/resolveAllowedIssueAuthors.js +8 -0
- package/bin/domain/usecases/resolveAllowedIssueAuthors.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +36 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +23 -1
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +109 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +8 -2
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +109 -12
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +5 -7
- package/src/domain/usecases/StartPreparationUseCase.test.ts +202 -88
- package/src/domain/usecases/StartPreparationUseCase.ts +2 -1
- package/src/domain/usecases/resolveAllowedIssueAuthors.test.ts +51 -0
- package/src/domain/usecases/resolveAllowedIssueAuthors.ts +6 -0
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/resolveAllowedIssueAuthors.d.ts +5 -0
- 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
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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).
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
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 (
|
|
19
|
-
return
|
|
16
|
+
if (allowedIssueAuthors === null || allowedIssueAuthors === undefined) {
|
|
17
|
+
return false;
|
|
20
18
|
}
|
|
21
|
-
if (allowedIssueAuthors ===
|
|
22
|
-
return
|
|
19
|
+
if (allowedIssueAuthors.length === 0) {
|
|
20
|
+
return false;
|
|
23
21
|
}
|
|
24
22
|
return allowedIssueAuthors.includes(author);
|
|
25
23
|
};
|