github-issue-tower-defence-management 1.65.0 → 1.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +7 -7
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -0
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/domain/entities/WorkflowStatus.js +2 -6
- package/bin/domain/entities/WorkflowStatus.js.map +1 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js +14 -1
- package/bin/domain/usecases/SetupTowerDefenceProjectUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +59 -121
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +14 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +5 -0
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/entities/WorkflowStatus.ts +2 -5
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.test.ts +432 -36
- package/src/domain/usecases/SetupTowerDefenceProjectUseCase.ts +31 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +87 -306
- package/src/domain/usecases/StartPreparationUseCase.ts +80 -175
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
- package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
- package/types/domain/entities/WorkflowStatus.d.ts +1 -1
- package/types/domain/entities/WorkflowStatus.d.ts.map +1 -1
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts +3 -1
- package/types/domain/usecases/SetupTowerDefenceProjectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +2 -6
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { mock } from 'jest-mock-extended';
|
|
2
2
|
import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
|
|
3
3
|
import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
4
|
+
import { IssueRepository } from './adapter-interfaces/IssueRepository';
|
|
4
5
|
import { FieldOption, Project } from '../entities/Project';
|
|
6
|
+
import { Issue } from '../entities/Issue';
|
|
5
7
|
import {
|
|
6
8
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
7
|
-
AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
8
9
|
AWAITING_WORKSPACE_STATUS_NAME,
|
|
9
10
|
DEFAULT_STATUS_NAME,
|
|
10
11
|
DONE_STATUS_NAME,
|
|
11
12
|
FAILED_PREPARATION_STATUS_NAME,
|
|
12
13
|
ICEBOX_STATUS_NAME,
|
|
13
14
|
IN_TMUX_STATUS_NAME,
|
|
15
|
+
LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
14
16
|
LEGACY_IN_TMUX_STATUS_NAME,
|
|
15
17
|
LEGACY_TODO_STATUS_NAME,
|
|
16
18
|
PC_TODO_STATUS_NAME,
|
|
@@ -45,11 +47,37 @@ const buildCanonicalStatuses = (): FieldOption[] =>
|
|
|
45
47
|
description: '',
|
|
46
48
|
}));
|
|
47
49
|
|
|
50
|
+
const buildIssue = (overrides: Partial<Issue>): Issue => ({
|
|
51
|
+
nameWithOwner: 'test-org/test-repo',
|
|
52
|
+
number: 1,
|
|
53
|
+
title: 'Test issue',
|
|
54
|
+
state: 'OPEN',
|
|
55
|
+
status: null,
|
|
56
|
+
story: null,
|
|
57
|
+
nextActionDate: null,
|
|
58
|
+
nextActionHour: null,
|
|
59
|
+
estimationMinutes: null,
|
|
60
|
+
dependedIssueUrls: [],
|
|
61
|
+
completionDate50PercentConfidence: null,
|
|
62
|
+
url: 'https://github.com/test-org/test-repo/issues/1',
|
|
63
|
+
assignees: [],
|
|
64
|
+
labels: [],
|
|
65
|
+
org: 'test-org',
|
|
66
|
+
repo: 'test-repo',
|
|
67
|
+
body: '',
|
|
68
|
+
itemId: 'item-1',
|
|
69
|
+
isPr: false,
|
|
70
|
+
isInProgress: false,
|
|
71
|
+
isClosed: false,
|
|
72
|
+
createdAt: new Date('2024-01-01'),
|
|
73
|
+
author: 'user',
|
|
74
|
+
...overrides,
|
|
75
|
+
});
|
|
76
|
+
|
|
48
77
|
describe('SetupTowerDefenceProjectUseCase', () => {
|
|
49
|
-
it('should define exactly the
|
|
78
|
+
it('should define exactly the 9 required statuses in the documented order with the documented colors and no descriptions', () => {
|
|
50
79
|
expect(REQUIRED_WORKFLOW_STATUSES).toEqual([
|
|
51
80
|
{ name: DEFAULT_STATUS_NAME, color: 'ORANGE' },
|
|
52
|
-
{ name: AWAITING_TASK_BREAKDOWN_STATUS_NAME, color: 'ORANGE' },
|
|
53
81
|
{ name: AWAITING_WORKSPACE_STATUS_NAME, color: 'BLUE' },
|
|
54
82
|
{ name: PREPARATION_STATUS_NAME, color: 'YELLOW' },
|
|
55
83
|
{ name: FAILED_PREPARATION_STATUS_NAME, color: 'RED' },
|
|
@@ -67,10 +95,19 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
67
95
|
it('should skip update when project already has required statuses in canonical order with no descriptions', async () => {
|
|
68
96
|
const mockProjectRepository =
|
|
69
97
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
98
|
+
const mockIssueRepository =
|
|
99
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
70
100
|
const project = buildProject(buildCanonicalStatuses());
|
|
71
101
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
103
|
+
issues: [],
|
|
104
|
+
cacheUsed: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
108
|
+
mockProjectRepository,
|
|
109
|
+
mockIssueRepository,
|
|
110
|
+
);
|
|
74
111
|
await useCase.run({ projectUrl: project.url });
|
|
75
112
|
|
|
76
113
|
expect(mockProjectRepository.updateStatusList).not.toHaveBeenCalled();
|
|
@@ -79,6 +116,8 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
79
116
|
it('should skip update when project already has required statuses plus extras after them', async () => {
|
|
80
117
|
const mockProjectRepository =
|
|
81
118
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
119
|
+
const mockIssueRepository =
|
|
120
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
82
121
|
const statuses = [
|
|
83
122
|
...buildCanonicalStatuses(),
|
|
84
123
|
{
|
|
@@ -90,8 +129,15 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
90
129
|
];
|
|
91
130
|
const project = buildProject(statuses);
|
|
92
131
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
93
|
-
|
|
94
|
-
|
|
132
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
133
|
+
issues: [],
|
|
134
|
+
cacheUsed: false,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
138
|
+
mockProjectRepository,
|
|
139
|
+
mockIssueRepository,
|
|
140
|
+
);
|
|
95
141
|
await useCase.run({ projectUrl: project.url });
|
|
96
142
|
|
|
97
143
|
expect(mockProjectRepository.updateStatusList).not.toHaveBeenCalled();
|
|
@@ -100,13 +146,22 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
100
146
|
it('should rewrite required statuses with empty descriptions when an existing status carries a description', async () => {
|
|
101
147
|
const mockProjectRepository =
|
|
102
148
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
149
|
+
const mockIssueRepository =
|
|
150
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
103
151
|
const statuses = buildCanonicalStatuses();
|
|
104
152
|
statuses[0] = { ...statuses[0], description: 'stale description' };
|
|
105
153
|
const project = buildProject(statuses);
|
|
106
154
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
107
155
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
157
|
+
issues: [],
|
|
158
|
+
cacheUsed: false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
162
|
+
mockProjectRepository,
|
|
163
|
+
mockIssueRepository,
|
|
164
|
+
);
|
|
110
165
|
await useCase.run({ projectUrl: project.url });
|
|
111
166
|
|
|
112
167
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
@@ -121,6 +176,8 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
121
176
|
it('should add missing required statuses while preserving existing custom statuses', async () => {
|
|
122
177
|
const mockProjectRepository =
|
|
123
178
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
179
|
+
const mockIssueRepository =
|
|
180
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
124
181
|
const statuses: FieldOption[] = [
|
|
125
182
|
{
|
|
126
183
|
id: 'unread-id',
|
|
@@ -138,8 +195,15 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
138
195
|
const project = buildProject(statuses);
|
|
139
196
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
140
197
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
141
|
-
|
|
142
|
-
|
|
198
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
199
|
+
issues: [],
|
|
200
|
+
cacheUsed: false,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
204
|
+
mockProjectRepository,
|
|
205
|
+
mockIssueRepository,
|
|
206
|
+
);
|
|
143
207
|
await useCase.run({ projectUrl: project.url });
|
|
144
208
|
|
|
145
209
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
@@ -152,12 +216,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
152
216
|
color: 'ORANGE',
|
|
153
217
|
description: '',
|
|
154
218
|
},
|
|
155
|
-
{
|
|
156
|
-
id: null,
|
|
157
|
-
name: AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
158
|
-
color: 'ORANGE',
|
|
159
|
-
description: '',
|
|
160
|
-
},
|
|
161
219
|
{
|
|
162
220
|
id: null,
|
|
163
221
|
name: AWAITING_WORKSPACE_STATUS_NAME,
|
|
@@ -219,6 +277,8 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
219
277
|
it('should reorder existing required statuses into canonical order when out of order', async () => {
|
|
220
278
|
const mockProjectRepository =
|
|
221
279
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
280
|
+
const mockIssueRepository =
|
|
281
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
222
282
|
const reversedStatuses: FieldOption[] = REQUIRED_WORKFLOW_STATUSES.slice()
|
|
223
283
|
.reverse()
|
|
224
284
|
.map((required, index) => ({
|
|
@@ -230,15 +290,21 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
230
290
|
const project = buildProject(reversedStatuses);
|
|
231
291
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
232
292
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
233
|
-
|
|
234
|
-
|
|
293
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
294
|
+
issues: [],
|
|
295
|
+
cacheUsed: false,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
299
|
+
mockProjectRepository,
|
|
300
|
+
mockIssueRepository,
|
|
301
|
+
);
|
|
235
302
|
await useCase.run({ projectUrl: project.url });
|
|
236
303
|
|
|
237
304
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
238
305
|
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
239
306
|
expect(payload.map((status) => status.name)).toEqual([
|
|
240
307
|
DEFAULT_STATUS_NAME,
|
|
241
|
-
AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
242
308
|
AWAITING_WORKSPACE_STATUS_NAME,
|
|
243
309
|
PREPARATION_STATUS_NAME,
|
|
244
310
|
FAILED_PREPARATION_STATUS_NAME,
|
|
@@ -253,23 +319,34 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
253
319
|
it('should fix color when an existing required status has wrong color', async () => {
|
|
254
320
|
const mockProjectRepository =
|
|
255
321
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
322
|
+
const mockIssueRepository =
|
|
323
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
256
324
|
const statuses = buildCanonicalStatuses();
|
|
257
|
-
statuses[
|
|
325
|
+
statuses[1] = { ...statuses[1], color: 'RED' };
|
|
258
326
|
const project = buildProject(statuses);
|
|
259
327
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
260
328
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
261
|
-
|
|
262
|
-
|
|
329
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
330
|
+
issues: [],
|
|
331
|
+
cacheUsed: false,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
335
|
+
mockProjectRepository,
|
|
336
|
+
mockIssueRepository,
|
|
337
|
+
);
|
|
263
338
|
await useCase.run({ projectUrl: project.url });
|
|
264
339
|
|
|
265
340
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
266
341
|
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
267
|
-
expect(payload[
|
|
342
|
+
expect(payload[1].color).toBe(REQUIRED_WORKFLOW_STATUSES[1].color);
|
|
268
343
|
});
|
|
269
344
|
|
|
270
345
|
it('should rename legacy "Todo" to "Todo by human" by reusing the existing option ID', async () => {
|
|
271
346
|
const mockProjectRepository =
|
|
272
347
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
348
|
+
const mockIssueRepository =
|
|
349
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
273
350
|
const statuses: FieldOption[] = REQUIRED_WORKFLOW_STATUSES.map(
|
|
274
351
|
(required, index) => ({
|
|
275
352
|
id: `id-${index}`,
|
|
@@ -284,21 +361,30 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
284
361
|
const project = buildProject(statuses);
|
|
285
362
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
286
363
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
287
|
-
|
|
288
|
-
|
|
364
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
365
|
+
issues: [],
|
|
366
|
+
cacheUsed: false,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
370
|
+
mockProjectRepository,
|
|
371
|
+
mockIssueRepository,
|
|
372
|
+
);
|
|
289
373
|
await useCase.run({ projectUrl: project.url });
|
|
290
374
|
|
|
291
375
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
292
376
|
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
293
377
|
const todoEntry = payload.find((s) => s.name === TODO_STATUS_NAME);
|
|
294
378
|
expect(todoEntry).toBeDefined();
|
|
295
|
-
expect(todoEntry?.id).toBe('id-
|
|
379
|
+
expect(todoEntry?.id).toBe('id-5');
|
|
296
380
|
expect(payload.some((s) => s.name === LEGACY_TODO_STATUS_NAME)).toBe(false);
|
|
297
381
|
});
|
|
298
382
|
|
|
299
383
|
it('should rename legacy "In Tmux" to "In Tmux by human" by reusing the existing option ID', async () => {
|
|
300
384
|
const mockProjectRepository =
|
|
301
385
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
386
|
+
const mockIssueRepository =
|
|
387
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
302
388
|
const statuses: FieldOption[] = REQUIRED_WORKFLOW_STATUSES.map(
|
|
303
389
|
(required, index) => ({
|
|
304
390
|
id: `id-${index}`,
|
|
@@ -313,15 +399,22 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
313
399
|
const project = buildProject(statuses);
|
|
314
400
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
315
401
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
316
|
-
|
|
317
|
-
|
|
402
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
403
|
+
issues: [],
|
|
404
|
+
cacheUsed: false,
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
408
|
+
mockProjectRepository,
|
|
409
|
+
mockIssueRepository,
|
|
410
|
+
);
|
|
318
411
|
await useCase.run({ projectUrl: project.url });
|
|
319
412
|
|
|
320
413
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
321
414
|
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
322
415
|
const inTmuxEntry = payload.find((s) => s.name === IN_TMUX_STATUS_NAME);
|
|
323
416
|
expect(inTmuxEntry).toBeDefined();
|
|
324
|
-
expect(inTmuxEntry?.id).toBe('id-
|
|
417
|
+
expect(inTmuxEntry?.id).toBe('id-6');
|
|
325
418
|
expect(payload.some((s) => s.name === LEGACY_IN_TMUX_STATUS_NAME)).toBe(
|
|
326
419
|
false,
|
|
327
420
|
);
|
|
@@ -330,6 +423,8 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
330
423
|
it('should remove "PC Todo" from the status list and not include it in others', async () => {
|
|
331
424
|
const mockProjectRepository =
|
|
332
425
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
426
|
+
const mockIssueRepository =
|
|
427
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
333
428
|
const statuses: FieldOption[] = [
|
|
334
429
|
...buildCanonicalStatuses(),
|
|
335
430
|
{
|
|
@@ -342,8 +437,15 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
342
437
|
const project = buildProject(statuses);
|
|
343
438
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
344
439
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
345
|
-
|
|
346
|
-
|
|
440
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
441
|
+
issues: [],
|
|
442
|
+
cacheUsed: false,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
446
|
+
mockProjectRepository,
|
|
447
|
+
mockIssueRepository,
|
|
448
|
+
);
|
|
347
449
|
await useCase.run({ projectUrl: project.url });
|
|
348
450
|
|
|
349
451
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
@@ -354,6 +456,8 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
354
456
|
it('should migrate a project with legacy statuses: rename Todo and In Tmux by ID, remove PC Todo', async () => {
|
|
355
457
|
const mockProjectRepository =
|
|
356
458
|
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
459
|
+
const mockIssueRepository =
|
|
460
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
357
461
|
const statuses: FieldOption[] = [
|
|
358
462
|
{
|
|
359
463
|
id: 'id-0',
|
|
@@ -363,7 +467,287 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
363
467
|
},
|
|
364
468
|
{
|
|
365
469
|
id: 'id-1',
|
|
366
|
-
name:
|
|
470
|
+
name: AWAITING_WORKSPACE_STATUS_NAME,
|
|
471
|
+
color: 'BLUE',
|
|
472
|
+
description: '',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: 'id-2',
|
|
476
|
+
name: PREPARATION_STATUS_NAME,
|
|
477
|
+
color: 'YELLOW',
|
|
478
|
+
description: '',
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
id: 'id-3',
|
|
482
|
+
name: FAILED_PREPARATION_STATUS_NAME,
|
|
483
|
+
color: 'RED',
|
|
484
|
+
description: '',
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
id: 'id-4',
|
|
488
|
+
name: AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
489
|
+
color: 'GREEN',
|
|
490
|
+
description: '',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
id: 'id-5',
|
|
494
|
+
name: LEGACY_TODO_STATUS_NAME,
|
|
495
|
+
color: 'PINK',
|
|
496
|
+
description: '',
|
|
497
|
+
},
|
|
498
|
+
{ id: 'id-6', name: PC_TODO_STATUS_NAME, color: 'PINK', description: '' },
|
|
499
|
+
{
|
|
500
|
+
id: 'id-7',
|
|
501
|
+
name: LEGACY_IN_TMUX_STATUS_NAME,
|
|
502
|
+
color: 'RED',
|
|
503
|
+
description: '',
|
|
504
|
+
},
|
|
505
|
+
{ id: 'id-8', name: DONE_STATUS_NAME, color: 'PURPLE', description: '' },
|
|
506
|
+
{ id: 'id-9', name: ICEBOX_STATUS_NAME, color: 'GRAY', description: '' },
|
|
507
|
+
];
|
|
508
|
+
const project = buildProject(statuses);
|
|
509
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
510
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
511
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
512
|
+
issues: [],
|
|
513
|
+
cacheUsed: false,
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
517
|
+
mockProjectRepository,
|
|
518
|
+
mockIssueRepository,
|
|
519
|
+
);
|
|
520
|
+
await useCase.run({ projectUrl: project.url });
|
|
521
|
+
|
|
522
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
523
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
524
|
+
|
|
525
|
+
expect(payload.map((s) => s.name)).toEqual([
|
|
526
|
+
DEFAULT_STATUS_NAME,
|
|
527
|
+
AWAITING_WORKSPACE_STATUS_NAME,
|
|
528
|
+
PREPARATION_STATUS_NAME,
|
|
529
|
+
FAILED_PREPARATION_STATUS_NAME,
|
|
530
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
531
|
+
TODO_STATUS_NAME,
|
|
532
|
+
IN_TMUX_STATUS_NAME,
|
|
533
|
+
DONE_STATUS_NAME,
|
|
534
|
+
ICEBOX_STATUS_NAME,
|
|
535
|
+
]);
|
|
536
|
+
|
|
537
|
+
expect(payload.find((s) => s.name === TODO_STATUS_NAME)?.id).toBe('id-5');
|
|
538
|
+
expect(payload.find((s) => s.name === IN_TMUX_STATUS_NAME)?.id).toBe(
|
|
539
|
+
'id-7',
|
|
540
|
+
);
|
|
541
|
+
expect(payload.some((s) => s.name === PC_TODO_STATUS_NAME)).toBe(false);
|
|
542
|
+
expect(payload.some((s) => s.name === LEGACY_TODO_STATUS_NAME)).toBe(false);
|
|
543
|
+
expect(payload.some((s) => s.name === LEGACY_IN_TMUX_STATUS_NAME)).toBe(
|
|
544
|
+
false,
|
|
545
|
+
);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('should migrate issues from "Awaiting Task Breakdown" to "Todo by human" when that status exists', async () => {
|
|
549
|
+
const mockProjectRepository =
|
|
550
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
551
|
+
const mockIssueRepository =
|
|
552
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
553
|
+
const todoStatusId = 'todo-status-id';
|
|
554
|
+
const statuses: FieldOption[] = [
|
|
555
|
+
{
|
|
556
|
+
id: 'id-0',
|
|
557
|
+
name: DEFAULT_STATUS_NAME,
|
|
558
|
+
color: 'ORANGE',
|
|
559
|
+
description: '',
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: 'atb-id',
|
|
563
|
+
name: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
564
|
+
color: 'ORANGE',
|
|
565
|
+
description: '',
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: 'id-1',
|
|
569
|
+
name: AWAITING_WORKSPACE_STATUS_NAME,
|
|
570
|
+
color: 'BLUE',
|
|
571
|
+
description: '',
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: 'id-2',
|
|
575
|
+
name: PREPARATION_STATUS_NAME,
|
|
576
|
+
color: 'YELLOW',
|
|
577
|
+
description: '',
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: 'id-3',
|
|
581
|
+
name: FAILED_PREPARATION_STATUS_NAME,
|
|
582
|
+
color: 'RED',
|
|
583
|
+
description: '',
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
id: 'id-4',
|
|
587
|
+
name: AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
588
|
+
color: 'GREEN',
|
|
589
|
+
description: '',
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
id: todoStatusId,
|
|
593
|
+
name: TODO_STATUS_NAME,
|
|
594
|
+
color: 'PINK',
|
|
595
|
+
description: '',
|
|
596
|
+
},
|
|
597
|
+
{
|
|
598
|
+
id: 'id-6',
|
|
599
|
+
name: IN_TMUX_STATUS_NAME,
|
|
600
|
+
color: 'RED',
|
|
601
|
+
description: '',
|
|
602
|
+
},
|
|
603
|
+
{ id: 'id-7', name: DONE_STATUS_NAME, color: 'PURPLE', description: '' },
|
|
604
|
+
{ id: 'id-8', name: ICEBOX_STATUS_NAME, color: 'GRAY', description: '' },
|
|
605
|
+
];
|
|
606
|
+
const project = buildProject(statuses);
|
|
607
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
608
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
609
|
+
|
|
610
|
+
const atbIssue1 = buildIssue({
|
|
611
|
+
number: 10,
|
|
612
|
+
url: 'https://github.com/test-org/test-repo/issues/10',
|
|
613
|
+
itemId: 'item-10',
|
|
614
|
+
status: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
615
|
+
});
|
|
616
|
+
const atbIssue2 = buildIssue({
|
|
617
|
+
number: 11,
|
|
618
|
+
url: 'https://github.com/test-org/test-repo/issues/11',
|
|
619
|
+
itemId: 'item-11',
|
|
620
|
+
status: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
621
|
+
});
|
|
622
|
+
const otherIssue = buildIssue({
|
|
623
|
+
number: 12,
|
|
624
|
+
url: 'https://github.com/test-org/test-repo/issues/12',
|
|
625
|
+
itemId: 'item-12',
|
|
626
|
+
status: AWAITING_WORKSPACE_STATUS_NAME,
|
|
627
|
+
});
|
|
628
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
629
|
+
issues: [atbIssue1, atbIssue2, otherIssue],
|
|
630
|
+
cacheUsed: false,
|
|
631
|
+
});
|
|
632
|
+
mockIssueRepository.updateStatus.mockResolvedValue(undefined);
|
|
633
|
+
|
|
634
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
635
|
+
mockProjectRepository,
|
|
636
|
+
mockIssueRepository,
|
|
637
|
+
);
|
|
638
|
+
await useCase.run({ projectUrl: project.url });
|
|
639
|
+
|
|
640
|
+
expect(mockIssueRepository.getAllIssues).toHaveBeenCalledWith(
|
|
641
|
+
project.id,
|
|
642
|
+
0,
|
|
643
|
+
);
|
|
644
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledTimes(2);
|
|
645
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
|
|
646
|
+
project,
|
|
647
|
+
atbIssue1,
|
|
648
|
+
todoStatusId,
|
|
649
|
+
);
|
|
650
|
+
expect(mockIssueRepository.updateStatus).toHaveBeenCalledWith(
|
|
651
|
+
project,
|
|
652
|
+
atbIssue2,
|
|
653
|
+
todoStatusId,
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
657
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
658
|
+
expect(
|
|
659
|
+
payload.some(
|
|
660
|
+
(s) => s.name === LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
661
|
+
),
|
|
662
|
+
).toBe(false);
|
|
663
|
+
expect(payload.map((s) => s.name)).toEqual([
|
|
664
|
+
DEFAULT_STATUS_NAME,
|
|
665
|
+
AWAITING_WORKSPACE_STATUS_NAME,
|
|
666
|
+
PREPARATION_STATUS_NAME,
|
|
667
|
+
FAILED_PREPARATION_STATUS_NAME,
|
|
668
|
+
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
669
|
+
TODO_STATUS_NAME,
|
|
670
|
+
IN_TMUX_STATUS_NAME,
|
|
671
|
+
DONE_STATUS_NAME,
|
|
672
|
+
ICEBOX_STATUS_NAME,
|
|
673
|
+
]);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
it('should skip issue migration when "Awaiting Task Breakdown" status does not exist', async () => {
|
|
677
|
+
const mockProjectRepository =
|
|
678
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
679
|
+
const mockIssueRepository =
|
|
680
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
681
|
+
const project = buildProject(buildCanonicalStatuses());
|
|
682
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
683
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
684
|
+
issues: [],
|
|
685
|
+
cacheUsed: false,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
689
|
+
mockProjectRepository,
|
|
690
|
+
mockIssueRepository,
|
|
691
|
+
);
|
|
692
|
+
await useCase.run({ projectUrl: project.url });
|
|
693
|
+
|
|
694
|
+
expect(mockIssueRepository.updateStatus).not.toHaveBeenCalled();
|
|
695
|
+
expect(mockProjectRepository.updateStatusList).not.toHaveBeenCalled();
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it('should remove "Awaiting Task Breakdown" from the status list after migrating all affected issues', async () => {
|
|
699
|
+
const mockProjectRepository =
|
|
700
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
701
|
+
const mockIssueRepository =
|
|
702
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
703
|
+
const statuses: FieldOption[] = [
|
|
704
|
+
...buildCanonicalStatuses(),
|
|
705
|
+
{
|
|
706
|
+
id: 'atb-id',
|
|
707
|
+
name: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
708
|
+
color: 'ORANGE',
|
|
709
|
+
description: '',
|
|
710
|
+
},
|
|
711
|
+
];
|
|
712
|
+
const project = buildProject(statuses);
|
|
713
|
+
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
714
|
+
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
715
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
716
|
+
issues: [],
|
|
717
|
+
cacheUsed: false,
|
|
718
|
+
});
|
|
719
|
+
mockIssueRepository.updateStatus.mockResolvedValue(undefined);
|
|
720
|
+
|
|
721
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
722
|
+
mockProjectRepository,
|
|
723
|
+
mockIssueRepository,
|
|
724
|
+
);
|
|
725
|
+
await useCase.run({ projectUrl: project.url });
|
|
726
|
+
|
|
727
|
+
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
728
|
+
const [, payload] = mockProjectRepository.updateStatusList.mock.calls[0];
|
|
729
|
+
expect(
|
|
730
|
+
payload.some(
|
|
731
|
+
(s) => s.name === LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
732
|
+
),
|
|
733
|
+
).toBe(false);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('should migrate a project with all legacy statuses including Awaiting Task Breakdown', async () => {
|
|
737
|
+
const mockProjectRepository =
|
|
738
|
+
mock<Pick<ProjectRepository, 'getByUrl' | 'updateStatusList'>>();
|
|
739
|
+
const mockIssueRepository =
|
|
740
|
+
mock<Pick<IssueRepository, 'getAllIssues' | 'updateStatus'>>();
|
|
741
|
+
const statuses: FieldOption[] = [
|
|
742
|
+
{
|
|
743
|
+
id: 'id-0',
|
|
744
|
+
name: DEFAULT_STATUS_NAME,
|
|
745
|
+
color: 'ORANGE',
|
|
746
|
+
description: '',
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
id: 'id-1',
|
|
750
|
+
name: LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
367
751
|
color: 'ORANGE',
|
|
368
752
|
description: '',
|
|
369
753
|
},
|
|
@@ -410,8 +794,16 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
410
794
|
const project = buildProject(statuses);
|
|
411
795
|
mockProjectRepository.getByUrl.mockResolvedValue(project);
|
|
412
796
|
mockProjectRepository.updateStatusList.mockResolvedValue([]);
|
|
413
|
-
|
|
414
|
-
|
|
797
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
798
|
+
issues: [],
|
|
799
|
+
cacheUsed: false,
|
|
800
|
+
});
|
|
801
|
+
mockIssueRepository.updateStatus.mockResolvedValue(undefined);
|
|
802
|
+
|
|
803
|
+
const useCase = new SetupTowerDefenceProjectUseCase(
|
|
804
|
+
mockProjectRepository,
|
|
805
|
+
mockIssueRepository,
|
|
806
|
+
);
|
|
415
807
|
await useCase.run({ projectUrl: project.url });
|
|
416
808
|
|
|
417
809
|
expect(mockProjectRepository.updateStatusList).toHaveBeenCalledTimes(1);
|
|
@@ -419,7 +811,6 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
419
811
|
|
|
420
812
|
expect(payload.map((s) => s.name)).toEqual([
|
|
421
813
|
DEFAULT_STATUS_NAME,
|
|
422
|
-
AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
423
814
|
AWAITING_WORKSPACE_STATUS_NAME,
|
|
424
815
|
PREPARATION_STATUS_NAME,
|
|
425
816
|
FAILED_PREPARATION_STATUS_NAME,
|
|
@@ -439,5 +830,10 @@ describe('SetupTowerDefenceProjectUseCase', () => {
|
|
|
439
830
|
expect(payload.some((s) => s.name === LEGACY_IN_TMUX_STATUS_NAME)).toBe(
|
|
440
831
|
false,
|
|
441
832
|
);
|
|
833
|
+
expect(
|
|
834
|
+
payload.some(
|
|
835
|
+
(s) => s.name === LEGACY_AWAITING_TASK_BREAKDOWN_STATUS_NAME,
|
|
836
|
+
),
|
|
837
|
+
).toBe(false);
|
|
442
838
|
});
|
|
443
839
|
});
|