github-issue-tower-defence-management 1.35.2 → 1.36.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 +22 -0
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +1 -27
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -27
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/BaseGitHubRepository.js +3 -7
- package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
- package/bin/adapter/repositories/CheerioProjectRepository.js +11 -11
- package/bin/adapter/repositories/CheerioProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/FetchWebhookRepository.js +5 -1
- package/bin/adapter/repositories/FetchWebhookRepository.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +14 -16
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/KySlackRepository.js +212 -0
- package/bin/adapter/repositories/KySlackRepository.js.map +1 -0
- package/bin/adapter/repositories/issue/ApiV3IssueRepository.js +11 -13
- package/bin/adapter/repositories/issue/ApiV3IssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js +2 -3
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +47 -63
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +12 -8
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/RestIssueRepository.js +36 -69
- package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js +9 -1
- package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js.map +1 -1
- package/jest.config.js +2 -1
- package/package.json +2 -3
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -78
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -33
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -78
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -33
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +43 -33
- package/src/adapter/repositories/BaseGitHubRepository.ts +4 -8
- package/src/adapter/repositories/CheerioProjectRepository.ts +19 -23
- package/src/adapter/repositories/FetchWebhookRepository.ts +2 -1
- package/src/adapter/repositories/GraphqlProjectRepository.ts +63 -69
- package/src/adapter/repositories/{AxiosSlackRepository.test.ts → KySlackRepository.test.ts} +4 -4
- package/src/adapter/repositories/KySlackRepository.ts +297 -0
- package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +31 -33
- package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +3 -3
- package/src/adapter/repositories/issue/CheerioIssueRepository.ts +2 -3
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +30 -22
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +178 -191
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +12 -8
- package/src/adapter/repositories/issue/RestIssueRepository.ts +51 -91
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +20 -0
- package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +10 -1
- package/types/adapter/entry-points/function/getStoryObjectMap.d.ts +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
- package/types/adapter/repositories/CheerioProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/FetchWebhookRepository.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/{AxiosSlackRepository.d.ts → KySlackRepository.d.ts} +3 -2
- package/types/adapter/repositories/KySlackRepository.d.ts.map +1 -0
- package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.d.ts.map +1 -1
- package/types/index.d.ts +1 -1
- package/bin/adapter/repositories/AxiosSlackRepository.js +0 -188
- package/bin/adapter/repositories/AxiosSlackRepository.js.map +0 -1
- package/src/adapter/repositories/AxiosSlackRepository.ts +0 -218
- package/types/adapter/repositories/AxiosSlackRepository.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AssignNoAssigneeIssueToManagerUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,qCAAqC;IAChD,YACW,eAA4D;QAA5D,oBAAe,GAAf,eAAe,CAA6C;QAGvE,QAAG,GAAG,KAAK,EAAE,KAIZ,EAAiB,EAAE;YAClB,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;oBACzD,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"AssignNoAssigneeIssueToManagerUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,qCAAqC;IAChD,YACW,eAA4D;QAA5D,oBAAe,GAAf,eAAe,CAA6C;QAGvE,QAAG,GAAG,KAAK,EAAE,KAIZ,EAAiB,EAAE;YAClB,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;gBACjC,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;oBACzD,SAAS;gBACX,CAAC;gBACD,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxE,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACX,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;wBAC1B,MAAM,CAAC,CAAC;oBACV,CAAC;oBACD,MAAM,IAAI,KAAK,CACb,uCAAuC,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,EAAE,CACjE,CAAC;gBACJ,CAAC;gBACD,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC;IA1BC,CAAC;CA2BL;AA9BD,sFA8BC"}
|
package/jest.config.js
CHANGED
|
@@ -3,8 +3,9 @@ module.exports = {
|
|
|
3
3
|
testEnvironment: 'node',
|
|
4
4
|
transform: {
|
|
5
5
|
'^.+\\.ts?$': 'ts-jest',
|
|
6
|
+
'^.+\\.js$': ['ts-jest', { tsconfig: { allowJs: true } }],
|
|
6
7
|
},
|
|
7
|
-
transformIgnorePatterns: ['<rootDir>/node_modules/'],
|
|
8
|
+
transformIgnorePatterns: ['<rootDir>/node_modules/(?!(ky)/)'],
|
|
8
9
|
collectCoverage: true,
|
|
9
10
|
coverageDirectory: 'reports/coverage',
|
|
10
11
|
reporters: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "github-issue-tower-defence-management",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -72,14 +72,13 @@
|
|
|
72
72
|
"typescript": "^5.6.3"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"axios": "^1.14.0",
|
|
76
|
-
"axios-retry": "^4.5.0",
|
|
77
75
|
"cheerio": "^1.0.0",
|
|
78
76
|
"commander": "^14.0.0",
|
|
79
77
|
"cookie": "^1.0.1",
|
|
80
78
|
"dotenv": "^17.0.0",
|
|
81
79
|
"gh-cookie": "^1.3.22",
|
|
82
80
|
"googleapis": "^146.0.0",
|
|
81
|
+
"ky": "^0.33.3",
|
|
83
82
|
"typia": "^12.0.0",
|
|
84
83
|
"yaml": "^2.6.0"
|
|
85
84
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import YAML from 'yaml';
|
|
3
|
-
import axios from 'axios';
|
|
4
3
|
|
|
5
4
|
jest.mock('fs');
|
|
6
5
|
jest.mock('gh-cookie', () => ({ getCookieContent: jest.fn() }));
|
|
@@ -175,81 +174,4 @@ describe('GetStoryObjectMapUseCaseHandler', () => {
|
|
|
175
174
|
);
|
|
176
175
|
}
|
|
177
176
|
});
|
|
178
|
-
|
|
179
|
-
it('should redact Authorization and cookie headers in verbose mode error', async () => {
|
|
180
|
-
expect.assertions(4);
|
|
181
|
-
const capturedHandlers: Array<(error: unknown) => unknown> = [];
|
|
182
|
-
jest
|
|
183
|
-
.spyOn(axios.interceptors.response, 'use')
|
|
184
|
-
.mockImplementationOnce((_, errorHandler) => {
|
|
185
|
-
if (errorHandler) {
|
|
186
|
-
capturedHandlers.push(errorHandler);
|
|
187
|
-
}
|
|
188
|
-
return 0;
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const handler = new GetStoryObjectMapUseCaseHandler();
|
|
192
|
-
await handler.handle('config.yml', true);
|
|
193
|
-
|
|
194
|
-
expect(capturedHandlers).toHaveLength(1);
|
|
195
|
-
|
|
196
|
-
const mockAxiosError = {
|
|
197
|
-
message: 'Request failed with status code 401',
|
|
198
|
-
code: 'ERR_BAD_RESPONSE',
|
|
199
|
-
config: {
|
|
200
|
-
url: 'https://api.github.com/graphql',
|
|
201
|
-
method: 'post',
|
|
202
|
-
headers: {
|
|
203
|
-
toJSON: () => ({
|
|
204
|
-
Authorization: 'Bearer secret-github-token',
|
|
205
|
-
cookie: 'session=secret-cookie-value',
|
|
206
|
-
'Content-Type': 'application/json',
|
|
207
|
-
}),
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
response: { status: 401 },
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
capturedHandlers[0](mockAxiosError);
|
|
215
|
-
} catch (thrownError) {
|
|
216
|
-
if (!(thrownError instanceof Error)) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
expect(thrownError.message).not.toContain('secret-github-token');
|
|
220
|
-
expect(thrownError.message).not.toContain('secret-cookie-value');
|
|
221
|
-
expect(thrownError.message).toContain('[REDACTED]');
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it('should not expose Authorization header in non-verbose mode error', async () => {
|
|
226
|
-
expect.assertions(2);
|
|
227
|
-
const capturedHandlers: Array<(error: unknown) => unknown> = [];
|
|
228
|
-
jest
|
|
229
|
-
.spyOn(axios.interceptors.response, 'use')
|
|
230
|
-
.mockImplementationOnce((_, errorHandler) => {
|
|
231
|
-
if (errorHandler) {
|
|
232
|
-
capturedHandlers.push(errorHandler);
|
|
233
|
-
}
|
|
234
|
-
return 0;
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const handler = new GetStoryObjectMapUseCaseHandler();
|
|
238
|
-
await handler.handle('config.yml', false);
|
|
239
|
-
|
|
240
|
-
expect(capturedHandlers).toHaveLength(1);
|
|
241
|
-
|
|
242
|
-
const mockAxiosError = {
|
|
243
|
-
response: { status: 403 },
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
capturedHandlers[0](mockAxiosError);
|
|
248
|
-
} catch (thrownError) {
|
|
249
|
-
if (!(thrownError instanceof Error)) {
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
expect(thrownError.message).toBe('API Error: 403');
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
177
|
});
|
|
@@ -11,7 +11,6 @@ import { LocalStorageCacheRepository } from '../../repositories/LocalStorageCach
|
|
|
11
11
|
import { Issue } from '../../../domain/entities/Issue';
|
|
12
12
|
import { Project } from '../../../domain/entities/Project';
|
|
13
13
|
import { BaseGitHubRepository } from '../../repositories/BaseGitHubRepository';
|
|
14
|
-
import axios, { AxiosError } from 'axios';
|
|
15
14
|
import { CheerioProjectRepository } from '../../repositories/CheerioProjectRepository';
|
|
16
15
|
import { GetStoryObjectMapUseCase } from '../../../domain/usecases/GetStoryObjectMapUseCase';
|
|
17
16
|
import { StoryObjectMap } from '../../../domain/entities/StoryObjectMap';
|
|
@@ -19,7 +18,7 @@ import { StoryObjectMap } from '../../../domain/entities/StoryObjectMap';
|
|
|
19
18
|
export class GetStoryObjectMapUseCaseHandler {
|
|
20
19
|
handle = async (
|
|
21
20
|
configFilePath: string,
|
|
22
|
-
|
|
21
|
+
_verbose: boolean,
|
|
23
22
|
allowCacheMinutes?: number,
|
|
24
23
|
): Promise<{
|
|
25
24
|
project: Project;
|
|
@@ -27,37 +26,6 @@ export class GetStoryObjectMapUseCaseHandler {
|
|
|
27
26
|
cacheUsed: boolean;
|
|
28
27
|
storyObjectMap: StoryObjectMap;
|
|
29
28
|
}> => {
|
|
30
|
-
axios.interceptors.response.use(
|
|
31
|
-
(response) => response,
|
|
32
|
-
(error: AxiosError) => {
|
|
33
|
-
if (verbose) {
|
|
34
|
-
const rawHeaders = error.config?.headers?.toJSON() ?? {};
|
|
35
|
-
const sanitizedHeaders: Record<string, unknown> = {};
|
|
36
|
-
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
37
|
-
sanitizedHeaders[key] =
|
|
38
|
-
key.toLowerCase() === 'authorization' ||
|
|
39
|
-
key.toLowerCase() === 'cookie'
|
|
40
|
-
? '[REDACTED]'
|
|
41
|
-
: value;
|
|
42
|
-
}
|
|
43
|
-
throw new Error(
|
|
44
|
-
`API Error: ${JSON.stringify({
|
|
45
|
-
message: error.message,
|
|
46
|
-
code: error.code,
|
|
47
|
-
status: error.response?.status,
|
|
48
|
-
url: error.config?.url,
|
|
49
|
-
method: error.config?.method,
|
|
50
|
-
headers: sanitizedHeaders,
|
|
51
|
-
})}`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
if (error.response) {
|
|
55
|
-
throw new Error(`API Error: ${error.response.status}`);
|
|
56
|
-
}
|
|
57
|
-
throw new Error('Network Error');
|
|
58
|
-
},
|
|
59
|
-
);
|
|
60
|
-
|
|
61
29
|
const configFileContent = fs.readFileSync(configFilePath, 'utf8');
|
|
62
30
|
const input: unknown = YAML.parse(configFileContent);
|
|
63
31
|
type inputType = Parameters<GetStoryObjectMapUseCase['run']>[0] & {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import YAML from 'yaml';
|
|
3
|
-
import axios from 'axios';
|
|
4
3
|
|
|
5
4
|
jest.mock('fs');
|
|
6
5
|
jest.mock('gh-cookie', () => ({ getCookieContent: jest.fn() }));
|
|
@@ -238,81 +237,4 @@ describe('HandleScheduledEventUseCaseHandler', () => {
|
|
|
238
237
|
);
|
|
239
238
|
}
|
|
240
239
|
});
|
|
241
|
-
|
|
242
|
-
it('should redact Authorization and cookie headers in verbose mode error', async () => {
|
|
243
|
-
expect.assertions(4);
|
|
244
|
-
const capturedHandlers: Array<(error: unknown) => unknown> = [];
|
|
245
|
-
jest
|
|
246
|
-
.spyOn(axios.interceptors.response, 'use')
|
|
247
|
-
.mockImplementationOnce((_, errorHandler) => {
|
|
248
|
-
if (errorHandler) {
|
|
249
|
-
capturedHandlers.push(errorHandler);
|
|
250
|
-
}
|
|
251
|
-
return 0;
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
255
|
-
await handler.handle('config.yml', true);
|
|
256
|
-
|
|
257
|
-
expect(capturedHandlers).toHaveLength(1);
|
|
258
|
-
|
|
259
|
-
const mockAxiosError = {
|
|
260
|
-
message: 'Request failed with status code 401',
|
|
261
|
-
code: 'ERR_BAD_RESPONSE',
|
|
262
|
-
config: {
|
|
263
|
-
url: 'https://api.github.com/graphql',
|
|
264
|
-
method: 'post',
|
|
265
|
-
headers: {
|
|
266
|
-
toJSON: () => ({
|
|
267
|
-
Authorization: 'Bearer secret-github-token',
|
|
268
|
-
cookie: 'session=secret-cookie-value',
|
|
269
|
-
'Content-Type': 'application/json',
|
|
270
|
-
}),
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
response: { status: 401 },
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
capturedHandlers[0](mockAxiosError);
|
|
278
|
-
} catch (thrownError) {
|
|
279
|
-
if (!(thrownError instanceof Error)) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
expect(thrownError.message).not.toContain('secret-github-token');
|
|
283
|
-
expect(thrownError.message).not.toContain('secret-cookie-value');
|
|
284
|
-
expect(thrownError.message).toContain('[REDACTED]');
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('should not expose Authorization header in non-verbose mode error', async () => {
|
|
289
|
-
expect.assertions(2);
|
|
290
|
-
const capturedHandlers: Array<(error: unknown) => unknown> = [];
|
|
291
|
-
jest
|
|
292
|
-
.spyOn(axios.interceptors.response, 'use')
|
|
293
|
-
.mockImplementationOnce((_, errorHandler) => {
|
|
294
|
-
if (errorHandler) {
|
|
295
|
-
capturedHandlers.push(errorHandler);
|
|
296
|
-
}
|
|
297
|
-
return 0;
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
301
|
-
await handler.handle('config.yml', false);
|
|
302
|
-
|
|
303
|
-
expect(capturedHandlers).toHaveLength(1);
|
|
304
|
-
|
|
305
|
-
const mockAxiosError = {
|
|
306
|
-
response: { status: 403 },
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
capturedHandlers[0](mockAxiosError);
|
|
311
|
-
} catch (thrownError) {
|
|
312
|
-
if (!(thrownError instanceof Error)) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
expect(thrownError.message).toBe('API Error: 403');
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
240
|
});
|
|
@@ -21,7 +21,6 @@ import { BaseGitHubRepository } from '../../repositories/BaseGitHubRepository';
|
|
|
21
21
|
import { AnalyzeStoriesUseCase } from '../../../domain/usecases/AnalyzeStoriesUseCase';
|
|
22
22
|
import { ClearDependedIssueURLUseCase } from '../../../domain/usecases/ClearDependedIssueURLUseCase';
|
|
23
23
|
import { CreateEstimationIssueUseCase } from '../../../domain/usecases/CreateEstimationIssueUseCase';
|
|
24
|
-
import axios, { AxiosError } from 'axios';
|
|
25
24
|
import { ConvertCheckboxToIssueInStoryIssueUseCase } from '../../../domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase';
|
|
26
25
|
import { ChangeStatusByStoryColorUseCase } from '../../../domain/usecases/ChangeStatusByStoryColorUseCase';
|
|
27
26
|
import { SetNoStoryIssueToStoryUseCase } from '../../../domain/usecases/SetNoStoryIssueToStoryUseCase';
|
|
@@ -40,44 +39,13 @@ import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepositor
|
|
|
40
39
|
export class HandleScheduledEventUseCaseHandler {
|
|
41
40
|
handle = async (
|
|
42
41
|
configFilePath: string,
|
|
43
|
-
|
|
42
|
+
_verbose: boolean,
|
|
44
43
|
): Promise<{
|
|
45
44
|
project: Project;
|
|
46
45
|
issues: Issue[];
|
|
47
46
|
cacheUsed: boolean;
|
|
48
47
|
targetDateTimes: Date[];
|
|
49
48
|
} | null> => {
|
|
50
|
-
axios.interceptors.response.use(
|
|
51
|
-
(response) => response,
|
|
52
|
-
(error: AxiosError) => {
|
|
53
|
-
if (verbose) {
|
|
54
|
-
const rawHeaders = error.config?.headers.toJSON() ?? {};
|
|
55
|
-
const sanitizedHeaders: Record<string, unknown> = {};
|
|
56
|
-
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
57
|
-
sanitizedHeaders[key] =
|
|
58
|
-
key.toLowerCase() === 'authorization' ||
|
|
59
|
-
key.toLowerCase() === 'cookie'
|
|
60
|
-
? '[REDACTED]'
|
|
61
|
-
: value;
|
|
62
|
-
}
|
|
63
|
-
throw new Error(
|
|
64
|
-
`API Error: ${JSON.stringify({
|
|
65
|
-
message: error.message,
|
|
66
|
-
code: error.code,
|
|
67
|
-
status: error.response?.status,
|
|
68
|
-
url: error.config?.url,
|
|
69
|
-
method: error.config?.method,
|
|
70
|
-
headers: sanitizedHeaders,
|
|
71
|
-
})}`,
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
if (error.response) {
|
|
75
|
-
throw new Error(`API Error: ${error.response.status}`);
|
|
76
|
-
}
|
|
77
|
-
throw new Error('Network Error');
|
|
78
|
-
},
|
|
79
|
-
);
|
|
80
|
-
|
|
81
49
|
const configFileContent = fs.readFileSync(configFilePath, 'utf8');
|
|
82
50
|
const input: unknown = YAML.parse(configFileContent);
|
|
83
51
|
type inputType = Parameters<HandleScheduledEventUseCase['run']>[0] & {
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import resetAllMocks = jest.resetAllMocks;
|
|
4
|
-
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
1
|
+
const mockKyGetText = jest.fn<Promise<string>, []>();
|
|
2
|
+
const mockKyGet = jest.fn(() => ({ text: mockKyGetText }));
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
jest.mock('axios', () => ({
|
|
4
|
+
jest.mock('ky', () => ({
|
|
8
5
|
default: {
|
|
9
|
-
get:
|
|
6
|
+
get: mockKyGet,
|
|
7
|
+
post: jest.fn(),
|
|
8
|
+
put: jest.fn(),
|
|
9
|
+
patch: jest.fn(),
|
|
10
|
+
delete: jest.fn(),
|
|
11
|
+
extend: jest.fn(),
|
|
12
|
+
create: jest.fn(),
|
|
13
|
+
stop: jest.fn(),
|
|
10
14
|
},
|
|
11
|
-
|
|
15
|
+
__esModule: true,
|
|
12
16
|
}));
|
|
13
17
|
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import { BaseGitHubRepository } from './BaseGitHubRepository';
|
|
20
|
+
import resetAllMocks = jest.resetAllMocks;
|
|
21
|
+
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
22
|
+
|
|
14
23
|
const mockGetCookieContent = jest.fn<Promise<unknown>, unknown[]>();
|
|
15
24
|
jest.mock('gh-cookie', () => ({
|
|
16
25
|
getCookieContent: (...args: unknown[]): Promise<unknown> =>
|
|
@@ -141,7 +150,8 @@ describe('BaseGitHubRepository', () => {
|
|
|
141
150
|
]);
|
|
142
151
|
|
|
143
152
|
beforeEach(() => {
|
|
144
|
-
|
|
153
|
+
mockKyGet.mockReset().mockReturnValue({ text: mockKyGetText });
|
|
154
|
+
mockKyGetText.mockReset();
|
|
145
155
|
mockGetCookieContent.mockReset();
|
|
146
156
|
mockGetCookieContent.mockResolvedValue(validCookieJson);
|
|
147
157
|
fs.writeFileSync(refreshCookieJsonFilePath, validCookieJson);
|
|
@@ -155,25 +165,25 @@ describe('BaseGitHubRepository', () => {
|
|
|
155
165
|
|
|
156
166
|
it('should return when HTML contains user-login meta tag for current user (logged in)', async () => {
|
|
157
167
|
const repository = new RefreshTestRepository();
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
mockKyGetText.mockResolvedValueOnce(
|
|
169
|
+
`<html><head><meta name="user-login" content="${ghUserName}"></head><body><h1>${ghUserName}</h1></body></html>`,
|
|
170
|
+
);
|
|
161
171
|
|
|
162
172
|
await expect(repository.refreshCookie()).resolves.toBeUndefined();
|
|
163
173
|
|
|
164
|
-
expect(
|
|
174
|
+
expect(mockKyGet).toHaveBeenCalledWith(
|
|
165
175
|
`https://github.com/${ghUserName}`,
|
|
166
176
|
expect.anything(),
|
|
167
177
|
);
|
|
168
|
-
expect(
|
|
178
|
+
expect(mockKyGet).toHaveBeenCalledTimes(1);
|
|
169
179
|
});
|
|
170
180
|
|
|
171
181
|
it('should fail when HTML contains username in content but not in user-login meta tag (not logged in)', async () => {
|
|
172
182
|
const repository = new RefreshTestRepository();
|
|
173
183
|
const notLoggedInHtml = `<html><head><meta name="user-login" content=""></head><body><h1>${ghUserName}</h1><p>Public profile</p></body></html>`;
|
|
174
|
-
|
|
175
|
-
.mockResolvedValueOnce(
|
|
176
|
-
.mockResolvedValueOnce(
|
|
184
|
+
mockKyGetText
|
|
185
|
+
.mockResolvedValueOnce(notLoggedInHtml)
|
|
186
|
+
.mockResolvedValueOnce(notLoggedInHtml);
|
|
177
187
|
|
|
178
188
|
await expect(repository.refreshCookie()).rejects.toThrow(
|
|
179
189
|
'Failed to refresh cookie',
|
|
@@ -182,17 +192,17 @@ describe('BaseGitHubRepository', () => {
|
|
|
182
192
|
|
|
183
193
|
it('should use profile page URL not homepage to check authentication', async () => {
|
|
184
194
|
const repository = new RefreshTestRepository();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
mockKyGetText.mockResolvedValueOnce(
|
|
196
|
+
`<html><head><meta name="user-login" content="${ghUserName}"></head><body></body></html>`,
|
|
197
|
+
);
|
|
188
198
|
|
|
189
199
|
await repository.refreshCookie();
|
|
190
200
|
|
|
191
|
-
expect(
|
|
201
|
+
expect(mockKyGet).toHaveBeenCalledWith(
|
|
192
202
|
`https://github.com/${ghUserName}`,
|
|
193
203
|
expect.anything(),
|
|
194
204
|
);
|
|
195
|
-
expect(
|
|
205
|
+
expect(mockKyGet).not.toHaveBeenCalledWith(
|
|
196
206
|
'https://github.com',
|
|
197
207
|
expect.anything(),
|
|
198
208
|
);
|
|
@@ -201,9 +211,9 @@ describe('BaseGitHubRepository', () => {
|
|
|
201
211
|
it('should throw when both profile page checks fail', async () => {
|
|
202
212
|
const repository = new RefreshTestRepository();
|
|
203
213
|
const notLoggedInHtml = `<html><head><meta name="user-login" content=""></head><body></body></html>`;
|
|
204
|
-
|
|
205
|
-
.mockResolvedValueOnce(
|
|
206
|
-
.mockResolvedValueOnce(
|
|
214
|
+
mockKyGetText
|
|
215
|
+
.mockResolvedValueOnce(notLoggedInHtml)
|
|
216
|
+
.mockResolvedValueOnce(notLoggedInHtml);
|
|
207
217
|
|
|
208
218
|
await expect(repository.refreshCookie()).rejects.toThrow(
|
|
209
219
|
'Failed to refresh cookie',
|
|
@@ -212,16 +222,16 @@ describe('BaseGitHubRepository', () => {
|
|
|
212
222
|
|
|
213
223
|
it('should reset cookie cache before regenerating so new cookie is used', async () => {
|
|
214
224
|
const repository = new RefreshTestRepository();
|
|
215
|
-
|
|
216
|
-
.mockResolvedValueOnce(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
.mockResolvedValueOnce(
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
mockKyGetText
|
|
226
|
+
.mockResolvedValueOnce(
|
|
227
|
+
`<html><head><meta name="user-login" content=""></head><body></body></html>`,
|
|
228
|
+
)
|
|
229
|
+
.mockResolvedValueOnce(
|
|
230
|
+
`<html><head><meta name="user-login" content="${ghUserName}"></head><body></body></html>`,
|
|
231
|
+
);
|
|
222
232
|
|
|
223
233
|
await expect(repository.refreshCookie()).resolves.toBeUndefined();
|
|
224
|
-
expect(
|
|
234
|
+
expect(mockKyGet).toHaveBeenCalledTimes(2);
|
|
225
235
|
expect(mockGetCookieContent).toHaveBeenCalledTimes(1);
|
|
226
236
|
});
|
|
227
237
|
});
|
|
@@ -3,7 +3,7 @@ import { serialize } from 'cookie';
|
|
|
3
3
|
import { getCookieContent } from 'gh-cookie';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
6
|
-
import
|
|
6
|
+
import ky from 'ky';
|
|
7
7
|
|
|
8
8
|
interface Cookie {
|
|
9
9
|
name: string;
|
|
@@ -54,7 +54,7 @@ export class BaseGitHubRepository {
|
|
|
54
54
|
}
|
|
55
55
|
return this.cookie;
|
|
56
56
|
};
|
|
57
|
-
createHeader = async (): Promise<
|
|
57
|
+
createHeader = async (): Promise<Record<string, string>> => {
|
|
58
58
|
const cookie = await this.getCookie();
|
|
59
59
|
const headers = {
|
|
60
60
|
accept:
|
|
@@ -169,18 +169,14 @@ export class BaseGitHubRepository {
|
|
|
169
169
|
}
|
|
170
170
|
const profileUrl = `https://github.com/${this.ghUserName}`;
|
|
171
171
|
const headers = await this.createHeader();
|
|
172
|
-
const
|
|
173
|
-
const html = content.data;
|
|
172
|
+
const html = await ky.get(profileUrl, { headers }).text();
|
|
174
173
|
if (html.includes(`meta name="user-login" content="${this.ghUserName}"`)) {
|
|
175
174
|
return;
|
|
176
175
|
}
|
|
177
176
|
this.localStorageRepository.remove(this.jsonFilePath);
|
|
178
177
|
this.cookie = null;
|
|
179
178
|
const newHeaders = await this.createHeader();
|
|
180
|
-
const
|
|
181
|
-
headers: newHeaders,
|
|
182
|
-
});
|
|
183
|
-
const newHtml = newContent.data;
|
|
179
|
+
const newHtml = await ky.get(profileUrl, { headers: newHeaders }).text();
|
|
184
180
|
if (
|
|
185
181
|
newHtml.includes(`meta name="user-login" content="${this.ghUserName}"`)
|
|
186
182
|
) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ky from 'ky';
|
|
2
2
|
import { BaseGitHubRepository } from './BaseGitHubRepository';
|
|
3
3
|
import { LocalStorageRepository } from './LocalStorageRepository';
|
|
4
4
|
import { FieldOption, Project } from '../../domain/entities/Project';
|
|
@@ -32,34 +32,30 @@ export class CheerioProjectRepository
|
|
|
32
32
|
id: FieldOption['id'] | null;
|
|
33
33
|
})[],
|
|
34
34
|
): Promise<FieldOption[]> => {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
width:
|
|
41
|
-
options: FieldOption[];
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
}>(
|
|
45
|
-
`https://github.com/memexes/${project.databaseId}/columns`,
|
|
46
|
-
{
|
|
47
|
-
memexProjectColumnId: project.story?.databaseId,
|
|
48
|
-
settings: {
|
|
49
|
-
width: 200,
|
|
50
|
-
options: newStoryList,
|
|
35
|
+
const browserHeaders = await this.createHeader();
|
|
36
|
+
const raw = await ky
|
|
37
|
+
.put(`https://github.com/memexes/${project.databaseId}/columns`, {
|
|
38
|
+
json: {
|
|
39
|
+
memexProjectColumnId: project.story?.databaseId,
|
|
40
|
+
settings: { width: 200, options: newStoryList },
|
|
51
41
|
},
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
42
|
headers: {
|
|
55
43
|
'github-verified-fetch': 'true',
|
|
56
44
|
origin: 'https://github.com',
|
|
57
45
|
'x-requested-with': 'XMLHttpRequest',
|
|
58
|
-
...
|
|
46
|
+
...browserHeaders,
|
|
59
47
|
},
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
})
|
|
49
|
+
.json<{
|
|
50
|
+
memexProjectColumn: {
|
|
51
|
+
id: string;
|
|
52
|
+
settings: {
|
|
53
|
+
width: number;
|
|
54
|
+
options: FieldOption[];
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
}>();
|
|
58
|
+
return raw.memexProjectColumn.settings.options.map((v) => ({
|
|
63
59
|
id: v.id,
|
|
64
60
|
name: v.name,
|
|
65
61
|
color: v.color,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import ky from 'ky';
|
|
1
2
|
import { WebhookRepository } from '../../domain/usecases/adapter-interfaces/WebhookRepository';
|
|
2
3
|
|
|
3
4
|
export class FetchWebhookRepository implements WebhookRepository {
|
|
4
5
|
async sendGetRequest(url: string): Promise<void> {
|
|
5
|
-
await
|
|
6
|
+
await ky.get(url);
|
|
6
7
|
}
|
|
7
8
|
}
|