github-issue-tower-defence-management 1.148.21 → 1.148.23
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 +14 -0
- package/bin/adapter/entry-points/console/consoleReadApi.js +1 -11
- package/bin/adapter/entry-points/console/consoleReadApi.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +15 -3
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/DailySecurityScanUseCase.js +46 -5
- package/bin/domain/usecases/DailySecurityScanUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleReadApi.test.ts +15 -47
- package/src/adapter/entry-points/console/consoleReadApi.ts +2 -20
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +10 -1
- package/src/adapter/entry-points/handlers/inTmuxByHumanSessionReconciler.test.ts +1 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +87 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +24 -4
- package/src/domain/usecases/DailySecurityScanUseCase.test.ts +502 -195
- package/src/domain/usecases/DailySecurityScanUseCase.ts +60 -5
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +1 -0
- package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +1 -0
- package/types/adapter/entry-points/console/consoleReadApi.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts +1 -0
- package/types/domain/usecases/DailySecurityScanUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
|
@@ -1,33 +1,5 @@
|
|
|
1
1
|
import { mock } from 'jest-mock-extended';
|
|
2
|
-
import { Issue } from '../../../domain/entities/Issue';
|
|
3
2
|
import { IssueRepository } from '../../../domain/usecases/adapter-interfaces/IssueRepository';
|
|
4
|
-
|
|
5
|
-
const buildIssueFixture = (title: string): Issue => ({
|
|
6
|
-
nameWithOwner: 'o/r',
|
|
7
|
-
number: 1,
|
|
8
|
-
title,
|
|
9
|
-
state: 'OPEN',
|
|
10
|
-
status: null,
|
|
11
|
-
story: null,
|
|
12
|
-
nextActionDate: null,
|
|
13
|
-
nextActionHour: null,
|
|
14
|
-
estimationMinutes: null,
|
|
15
|
-
dependedIssueUrls: [],
|
|
16
|
-
completionDate50PercentConfidence: null,
|
|
17
|
-
url: 'https://github.com/o/r/issues/1',
|
|
18
|
-
assignees: [],
|
|
19
|
-
labels: [],
|
|
20
|
-
org: 'o',
|
|
21
|
-
repo: 'r',
|
|
22
|
-
body: '',
|
|
23
|
-
itemId: 'itemId',
|
|
24
|
-
isPr: false,
|
|
25
|
-
isInProgress: false,
|
|
26
|
-
isClosed: false,
|
|
27
|
-
createdAt: new Date('2026-01-01T00:00:00Z'),
|
|
28
|
-
author: 'octocat',
|
|
29
|
-
closingIssueReferenceUrls: [],
|
|
30
|
-
});
|
|
31
3
|
import {
|
|
32
4
|
ISSUE_TITLE_CACHE_TTL_MS,
|
|
33
5
|
IssueTitleStateCache,
|
|
@@ -272,10 +244,8 @@ describe('consoleReadApi', () => {
|
|
|
272
244
|
state: 'OPEN',
|
|
273
245
|
merged: false,
|
|
274
246
|
isPullRequest: false,
|
|
247
|
+
title: 'Issue title from repository',
|
|
275
248
|
});
|
|
276
|
-
issueRepository.getIssueByUrl.mockResolvedValue(
|
|
277
|
-
buildIssueFixture('Issue title from repository'),
|
|
278
|
-
);
|
|
279
249
|
const cache = new IssueTitleStateCache(() => 0);
|
|
280
250
|
const url = 'https://github.com/o/r/issues/1';
|
|
281
251
|
const first = await handleIssueTitle(issueRepository, cache, url);
|
|
@@ -290,22 +260,16 @@ describe('consoleReadApi', () => {
|
|
|
290
260
|
expect(issueRepository.getIssueOrPullRequestState).toHaveBeenCalledTimes(
|
|
291
261
|
1,
|
|
292
262
|
);
|
|
293
|
-
expect(issueRepository.getIssueByUrl).
|
|
263
|
+
expect(issueRepository.getIssueByUrl).not.toHaveBeenCalled();
|
|
294
264
|
});
|
|
295
265
|
|
|
296
|
-
it('returns the pull request title
|
|
266
|
+
it('returns the pull request title for pull request urls', async () => {
|
|
297
267
|
const issueRepository = mock<IssueRepository>();
|
|
298
268
|
issueRepository.getIssueOrPullRequestState.mockResolvedValue({
|
|
299
269
|
state: 'CLOSED',
|
|
300
270
|
merged: true,
|
|
301
271
|
isPullRequest: true,
|
|
302
|
-
|
|
303
|
-
issueRepository.getPullRequestSummary.mockResolvedValue({
|
|
304
|
-
title: 'Pull request title from summary',
|
|
305
|
-
body: 'body',
|
|
306
|
-
additions: 1,
|
|
307
|
-
deletions: 0,
|
|
308
|
-
changedFiles: 1,
|
|
272
|
+
title: 'Pull request title from state',
|
|
309
273
|
});
|
|
310
274
|
const cache = new IssueTitleStateCache(() => 0);
|
|
311
275
|
const url = 'https://github.com/o/r/pull/2';
|
|
@@ -314,32 +278,34 @@ describe('consoleReadApi', () => {
|
|
|
314
278
|
state: 'CLOSED',
|
|
315
279
|
merged: true,
|
|
316
280
|
isPullRequest: true,
|
|
317
|
-
title: 'Pull request title from
|
|
281
|
+
title: 'Pull request title from state',
|
|
318
282
|
});
|
|
319
|
-
expect(issueRepository.getPullRequestSummary).
|
|
283
|
+
expect(issueRepository.getPullRequestSummary).not.toHaveBeenCalled();
|
|
320
284
|
expect(issueRepository.getIssueByUrl).not.toHaveBeenCalled();
|
|
321
285
|
});
|
|
322
286
|
|
|
323
|
-
it('
|
|
287
|
+
it('returns the title of an issue that is not an item on the project board', async () => {
|
|
324
288
|
const issueRepository = mock<IssueRepository>();
|
|
325
289
|
issueRepository.getIssueOrPullRequestState.mockResolvedValue({
|
|
326
|
-
state: '
|
|
290
|
+
state: 'CLOSED',
|
|
327
291
|
merged: false,
|
|
328
292
|
isPullRequest: false,
|
|
293
|
+
title: 'Title of an issue outside the board',
|
|
329
294
|
});
|
|
330
295
|
issueRepository.getIssueByUrl.mockResolvedValue(null);
|
|
331
296
|
const cache = new IssueTitleStateCache(() => 0);
|
|
332
297
|
const response = await handleIssueTitle(
|
|
333
298
|
issueRepository,
|
|
334
299
|
cache,
|
|
335
|
-
'https://github.com/o/
|
|
300
|
+
'https://github.com/o/off-board-repository/issues/656',
|
|
336
301
|
);
|
|
337
302
|
expect(response.body).toEqual({
|
|
338
|
-
state: '
|
|
303
|
+
state: 'CLOSED',
|
|
339
304
|
merged: false,
|
|
340
305
|
isPullRequest: false,
|
|
341
|
-
title: '',
|
|
306
|
+
title: 'Title of an issue outside the board',
|
|
342
307
|
});
|
|
308
|
+
expect(issueRepository.getIssueByUrl).not.toHaveBeenCalled();
|
|
343
309
|
});
|
|
344
310
|
|
|
345
311
|
it('re-fetches a non-merged result after the TTL elapses', async () => {
|
|
@@ -348,6 +314,7 @@ describe('consoleReadApi', () => {
|
|
|
348
314
|
state: 'OPEN',
|
|
349
315
|
merged: false,
|
|
350
316
|
isPullRequest: true,
|
|
317
|
+
title: 'Open pull request title',
|
|
351
318
|
});
|
|
352
319
|
let now = 0;
|
|
353
320
|
const cache = new IssueTitleStateCache(() => now);
|
|
@@ -371,6 +338,7 @@ describe('consoleReadApi', () => {
|
|
|
371
338
|
state: 'CLOSED',
|
|
372
339
|
merged: true,
|
|
373
340
|
isPullRequest: true,
|
|
341
|
+
title: 'Merged pull request title',
|
|
374
342
|
});
|
|
375
343
|
let now = 0;
|
|
376
344
|
const cache = new IssueTitleStateCache(() => now);
|
|
@@ -233,19 +233,6 @@ export const handleRelatedPrs = async (
|
|
|
233
233
|
return ok({ relatedPullRequests: withSummaries });
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
-
const resolveIssueOrPullRequestTitle = async (
|
|
237
|
-
issueRepository: IssueRepository,
|
|
238
|
-
url: string,
|
|
239
|
-
isPullRequest: boolean,
|
|
240
|
-
): Promise<string> => {
|
|
241
|
-
if (isPullRequest) {
|
|
242
|
-
const summary = await issueRepository.getPullRequestSummary(url);
|
|
243
|
-
return summary?.title ?? '';
|
|
244
|
-
}
|
|
245
|
-
const issue = await issueRepository.getIssueByUrl(url);
|
|
246
|
-
return issue?.title ?? '';
|
|
247
|
-
};
|
|
248
|
-
|
|
249
236
|
export const handleIssueTitle = async (
|
|
250
237
|
issueRepository: IssueRepository,
|
|
251
238
|
cache: IssueTitleStateCache,
|
|
@@ -258,13 +245,8 @@ export const handleIssueTitle = async (
|
|
|
258
245
|
if (cached !== null) {
|
|
259
246
|
return ok(cached);
|
|
260
247
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
issueRepository,
|
|
264
|
-
url,
|
|
265
|
-
baseState.isPullRequest,
|
|
266
|
-
);
|
|
267
|
-
const state: IssueOrPullRequestState = { ...baseState, title };
|
|
248
|
+
const state: IssueOrPullRequestState =
|
|
249
|
+
await issueRepository.getIssueOrPullRequestState(url);
|
|
268
250
|
cache.set(url, state);
|
|
269
251
|
return ok(state);
|
|
270
252
|
};
|
|
@@ -473,10 +473,19 @@ const createStubIssueRepository = (
|
|
|
473
473
|
getPullRequestCommits: async (): Promise<PullRequestCommit[]> => [],
|
|
474
474
|
getIssueOrPullRequestState: async (
|
|
475
475
|
url: string,
|
|
476
|
-
): Promise<{
|
|
476
|
+
): Promise<{
|
|
477
|
+
state: string;
|
|
478
|
+
merged: boolean;
|
|
479
|
+
isPullRequest: boolean;
|
|
480
|
+
title: string;
|
|
481
|
+
}> => ({
|
|
477
482
|
state: 'open',
|
|
478
483
|
merged: false,
|
|
479
484
|
isPullRequest: url.includes('/pull/'),
|
|
485
|
+
title:
|
|
486
|
+
url === CONSOLE_E2E_INLINE_COMMENT_PR_URL
|
|
487
|
+
? 'Add inline review comments on the related pull request diff'
|
|
488
|
+
: buildIssueForUrl(url).title,
|
|
480
489
|
}),
|
|
481
490
|
getPullRequestSummary: async (
|
|
482
491
|
url: string,
|
|
@@ -1103,9 +1103,16 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
1103
1103
|
),
|
|
1104
1104
|
)
|
|
1105
1105
|
.mockResolvedValueOnce(
|
|
1106
|
-
new Response(
|
|
1107
|
-
|
|
1108
|
-
|
|
1106
|
+
new Response(
|
|
1107
|
+
JSON.stringify({
|
|
1108
|
+
state: 'open',
|
|
1109
|
+
merged: false,
|
|
1110
|
+
title: 'Issue title',
|
|
1111
|
+
}),
|
|
1112
|
+
{
|
|
1113
|
+
status: 200,
|
|
1114
|
+
},
|
|
1115
|
+
),
|
|
1109
1116
|
);
|
|
1110
1117
|
|
|
1111
1118
|
const { repository, sleep } = createApiV3CheerioRestIssueRepository();
|
|
@@ -2010,6 +2017,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2010
2017
|
state: 'closed',
|
|
2011
2018
|
merged: true,
|
|
2012
2019
|
isPullRequest: true,
|
|
2020
|
+
title: 'PR title',
|
|
2013
2021
|
});
|
|
2014
2022
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
2015
2023
|
'https://api.github.com/repos/HiromiShikata/test-repository/pulls/42',
|
|
@@ -2019,7 +2027,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2019
2027
|
|
|
2020
2028
|
it('should fetch issue state with merged always false for an issue URL', async () => {
|
|
2021
2029
|
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
2022
|
-
new Response(JSON.stringify({ state: 'open' }), {
|
|
2030
|
+
new Response(JSON.stringify({ state: 'open', title: 'Issue title' }), {
|
|
2023
2031
|
status: 200,
|
|
2024
2032
|
headers: { 'Content-Type': 'application/json' },
|
|
2025
2033
|
}),
|
|
@@ -2034,6 +2042,7 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2034
2042
|
state: 'open',
|
|
2035
2043
|
merged: false,
|
|
2036
2044
|
isPullRequest: false,
|
|
2045
|
+
title: 'Issue title',
|
|
2037
2046
|
});
|
|
2038
2047
|
expect(fetchSpy).toHaveBeenCalledWith(
|
|
2039
2048
|
'https://api.github.com/repos/HiromiShikata/test-repository/issues/42',
|
|
@@ -2041,6 +2050,80 @@ describe('ApiV3CheerioRestIssueRepository', () => {
|
|
|
2041
2050
|
);
|
|
2042
2051
|
});
|
|
2043
2052
|
|
|
2053
|
+
it('should return the issue title carried by the same REST payload', async () => {
|
|
2054
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
2055
|
+
new Response(
|
|
2056
|
+
JSON.stringify({ state: 'closed', title: 'Issue title from REST' }),
|
|
2057
|
+
{
|
|
2058
|
+
status: 200,
|
|
2059
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2060
|
+
},
|
|
2061
|
+
),
|
|
2062
|
+
);
|
|
2063
|
+
|
|
2064
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
2065
|
+
const result = await repository.getIssueOrPullRequestState(
|
|
2066
|
+
'https://github.com/HiromiShikata/other-repository/issues/656',
|
|
2067
|
+
);
|
|
2068
|
+
|
|
2069
|
+
expect(result).toEqual({
|
|
2070
|
+
state: 'closed',
|
|
2071
|
+
merged: false,
|
|
2072
|
+
isPullRequest: false,
|
|
2073
|
+
title: 'Issue title from REST',
|
|
2074
|
+
});
|
|
2075
|
+
});
|
|
2076
|
+
|
|
2077
|
+
it('should return the pull request title carried by the same REST payload', async () => {
|
|
2078
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
2079
|
+
new Response(
|
|
2080
|
+
JSON.stringify({
|
|
2081
|
+
title: 'PR title from REST',
|
|
2082
|
+
state: 'open',
|
|
2083
|
+
merged: false,
|
|
2084
|
+
draft: false,
|
|
2085
|
+
additions: 1,
|
|
2086
|
+
deletions: 1,
|
|
2087
|
+
changed_files: 1,
|
|
2088
|
+
head: { ref: 'feature/foo' },
|
|
2089
|
+
base: { ref: 'main' },
|
|
2090
|
+
user: { login: 'alice' },
|
|
2091
|
+
body: 'pr body',
|
|
2092
|
+
}),
|
|
2093
|
+
{ status: 200, headers: { 'Content-Type': 'application/json' } },
|
|
2094
|
+
),
|
|
2095
|
+
);
|
|
2096
|
+
|
|
2097
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
2098
|
+
const result = await repository.getIssueOrPullRequestState(
|
|
2099
|
+
'https://github.com/HiromiShikata/other-repository/pull/42',
|
|
2100
|
+
);
|
|
2101
|
+
|
|
2102
|
+
expect(result).toEqual({
|
|
2103
|
+
state: 'open',
|
|
2104
|
+
merged: false,
|
|
2105
|
+
isPullRequest: true,
|
|
2106
|
+
title: 'PR title from REST',
|
|
2107
|
+
});
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
it('should throw rather than report an empty title when the payload carries no title', async () => {
|
|
2111
|
+
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
2112
|
+
new Response(JSON.stringify({ state: 'open' }), {
|
|
2113
|
+
status: 200,
|
|
2114
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2115
|
+
}),
|
|
2116
|
+
);
|
|
2117
|
+
|
|
2118
|
+
const { repository } = createApiV3CheerioRestIssueRepository();
|
|
2119
|
+
|
|
2120
|
+
await expect(
|
|
2121
|
+
repository.getIssueOrPullRequestState(
|
|
2122
|
+
'https://github.com/HiromiShikata/other-repository/issues/656',
|
|
2123
|
+
),
|
|
2124
|
+
).rejects.toThrow('Unexpected response shape when fetching state for');
|
|
2125
|
+
});
|
|
2126
|
+
|
|
2044
2127
|
it('should throw when the API responds with a non-2xx status', async () => {
|
|
2045
2128
|
jest.spyOn(global, 'fetch').mockResolvedValueOnce(
|
|
2046
2129
|
new Response('Not Found', {
|
|
@@ -277,12 +277,17 @@ function isIssueOrPullRequestBodyResponse(
|
|
|
277
277
|
|
|
278
278
|
type IssueOrPullRequestStateResponse = {
|
|
279
279
|
state: string;
|
|
280
|
+
title: string;
|
|
280
281
|
};
|
|
281
282
|
|
|
282
283
|
function isIssueOrPullRequestStateResponse(
|
|
283
284
|
value: unknown,
|
|
284
285
|
): value is IssueOrPullRequestStateResponse {
|
|
285
|
-
return
|
|
286
|
+
return (
|
|
287
|
+
isRecord(value) &&
|
|
288
|
+
typeof value.state === 'string' &&
|
|
289
|
+
typeof value.title === 'string'
|
|
290
|
+
);
|
|
286
291
|
}
|
|
287
292
|
|
|
288
293
|
type IssueCommentsResponseItem = {
|
|
@@ -2273,7 +2278,12 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2273
2278
|
|
|
2274
2279
|
getIssueOrPullRequestState = async (
|
|
2275
2280
|
url: string,
|
|
2276
|
-
): Promise<{
|
|
2281
|
+
): Promise<{
|
|
2282
|
+
state: string;
|
|
2283
|
+
merged: boolean;
|
|
2284
|
+
isPullRequest: boolean;
|
|
2285
|
+
title: string;
|
|
2286
|
+
}> => {
|
|
2277
2287
|
const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(url);
|
|
2278
2288
|
if (isPr) {
|
|
2279
2289
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
@@ -2298,7 +2308,12 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2298
2308
|
`Unexpected response shape when fetching state for ${url}`,
|
|
2299
2309
|
);
|
|
2300
2310
|
}
|
|
2301
|
-
return {
|
|
2311
|
+
return {
|
|
2312
|
+
state: body.state,
|
|
2313
|
+
merged: body.merged,
|
|
2314
|
+
isPullRequest: true,
|
|
2315
|
+
title: body.title,
|
|
2316
|
+
};
|
|
2302
2317
|
}
|
|
2303
2318
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
2304
2319
|
fetch(
|
|
@@ -2322,7 +2337,12 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
2322
2337
|
`Unexpected response shape when fetching state for ${url}`,
|
|
2323
2338
|
);
|
|
2324
2339
|
}
|
|
2325
|
-
return {
|
|
2340
|
+
return {
|
|
2341
|
+
state: body.state,
|
|
2342
|
+
merged: false,
|
|
2343
|
+
isPullRequest: false,
|
|
2344
|
+
title: body.title,
|
|
2345
|
+
};
|
|
2326
2346
|
};
|
|
2327
2347
|
|
|
2328
2348
|
getPullRequestSummary = async (
|