github-issue-tower-defence-management 1.1.0 → 1.3.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.
Files changed (134) hide show
  1. package/.github/workflows/commit-lint.yml +0 -2
  2. package/.github/workflows/empty-format-test-job.yml +28 -0
  3. package/.github/workflows/test.yml +1 -0
  4. package/CHANGELOG.md +37 -38
  5. package/bin/adapter/entry-points/cli/index.js +2 -1
  6. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  7. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +140 -17
  8. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  9. package/bin/adapter/repositories/BaseGitHubRepository.js +3 -9
  10. package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
  11. package/bin/adapter/repositories/GraphqlProjectRepository.js +30 -0
  12. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  13. package/bin/adapter/repositories/LocalStorageRepository.js +6 -0
  14. package/bin/adapter/repositories/LocalStorageRepository.js.map +1 -1
  15. package/bin/adapter/repositories/SystemDateRepository.js +15 -3
  16. package/bin/adapter/repositories/SystemDateRepository.js.map +1 -1
  17. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +69 -9
  18. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  19. package/bin/adapter/repositories/issue/CheerioIssueRepository.js +28 -2
  20. package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -1
  21. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +7 -0
  22. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
  23. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +19 -5
  24. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -1
  25. package/bin/adapter/repositories/issue/RestIssueRepository.js +1 -0
  26. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  27. package/bin/adapter/repositories/utils.js +1 -6
  28. package/bin/adapter/repositories/utils.js.map +1 -1
  29. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +98 -65
  30. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -1
  31. package/bin/domain/usecases/AnalyzeStoriesUseCase.js +173 -0
  32. package/bin/domain/usecases/AnalyzeStoriesUseCase.js.map +1 -0
  33. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js +37 -0
  34. package/bin/domain/usecases/ChangeStatusLongInReviewIssueUseCase.js.map +1 -0
  35. package/bin/domain/usecases/ClearDependedIssueURLUseCase.js +66 -0
  36. package/bin/domain/usecases/ClearDependedIssueURLUseCase.js.map +1 -0
  37. package/bin/domain/usecases/ClearNextActionHourUseCase.js +8 -2
  38. package/bin/domain/usecases/ClearNextActionHourUseCase.js.map +1 -1
  39. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js +63 -0
  40. package/bin/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.js.map +1 -0
  41. package/bin/domain/usecases/CreateEstimationIssueUseCase.js +100 -0
  42. package/bin/domain/usecases/CreateEstimationIssueUseCase.js.map +1 -0
  43. package/bin/domain/usecases/HandleScheduledEventUseCase.js +136 -2
  44. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
  45. package/bin/domain/usecases/utils.js +24 -0
  46. package/bin/domain/usecases/utils.js.map +1 -0
  47. package/package.json +2 -2
  48. package/src/adapter/entry-points/cli/index.test.ts +1 -0
  49. package/src/adapter/entry-points/cli/index.ts +3 -1
  50. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +92 -7
  51. package/src/adapter/repositories/AxiosSlackRepository.test.ts +3 -0
  52. package/src/adapter/repositories/BaseGitHubRepository.test.ts +3 -1
  53. package/src/adapter/repositories/BaseGitHubRepository.ts +3 -12
  54. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +30 -1
  55. package/src/adapter/repositories/GraphqlProjectRepository.ts +41 -0
  56. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +1 -0
  57. package/src/adapter/repositories/LocalStorageRepository.ts +6 -0
  58. package/src/adapter/repositories/SystemDateRepository.ts +17 -3
  59. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +8 -0
  60. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +123 -16
  61. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +3 -0
  62. package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +10 -0
  63. package/src/adapter/repositories/issue/CheerioIssueRepository.ts +37 -1
  64. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +7 -1
  65. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +15 -0
  66. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +7 -1
  67. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +22 -5
  68. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +3 -0
  69. package/src/adapter/repositories/issue/RestIssueRepository.ts +5 -2
  70. package/src/adapter/repositories/utils.test.ts +16 -1
  71. package/src/adapter/repositories/utils.ts +1 -6
  72. package/src/domain/entities/Issue.ts +4 -0
  73. package/src/domain/entities/Project.ts +21 -4
  74. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +151 -115
  75. package/src/domain/usecases/AnalyzeStoriesUseCase.ts +296 -0
  76. package/src/domain/usecases/CONTRIBUTING_FOR_TEST.md +254 -0
  77. package/src/domain/usecases/ChangeStatusLongInReviewIssueUseCase.test.ts +204 -0
  78. package/src/domain/usecases/ChangeStatusLongInReviewIssueUseCase.ts +57 -0
  79. package/src/domain/usecases/ClearDependedIssueURLUseCase.test.ts +840 -0
  80. package/src/domain/usecases/ClearDependedIssueURLUseCase.ts +107 -0
  81. package/src/domain/usecases/ClearNextActionHourUseCase.ts +12 -2
  82. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.test.ts +490 -0
  83. package/src/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.ts +98 -0
  84. package/src/domain/usecases/CreateEstimationIssueUseCase.ts +157 -0
  85. package/src/domain/usecases/GenerateWorkingTimeReportUseCase.test.ts +8 -0
  86. package/src/domain/usecases/HandleScheduledEventUseCase.ts +184 -2
  87. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +2 -0
  88. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +13 -1
  89. package/src/domain/usecases/utils.ts +28 -0
  90. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
  91. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  92. package/types/adapter/repositories/BaseGitHubRepository.d.ts +3 -1
  93. package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
  94. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  95. package/types/adapter/repositories/LocalStorageRepository.d.ts +1 -0
  96. package/types/adapter/repositories/LocalStorageRepository.d.ts.map +1 -1
  97. package/types/adapter/repositories/SystemDateRepository.d.ts +2 -0
  98. package/types/adapter/repositories/SystemDateRepository.d.ts.map +1 -1
  99. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +15 -5
  100. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  101. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +7 -1
  102. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -1
  103. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +3 -0
  104. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
  105. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -1
  106. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +1 -1
  107. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  108. package/types/adapter/repositories/utils.d.ts.map +1 -1
  109. package/types/domain/entities/Issue.d.ts +4 -0
  110. package/types/domain/entities/Issue.d.ts.map +1 -1
  111. package/types/domain/entities/Project.d.ts +21 -4
  112. package/types/domain/entities/Project.d.ts.map +1 -1
  113. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts +13 -8
  114. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts.map +1 -1
  115. package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts +45 -0
  116. package/types/domain/usecases/AnalyzeStoriesUseCase.d.ts.map +1 -0
  117. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts +17 -0
  118. package/types/domain/usecases/ChangeStatusLongInReviewIssueUseCase.d.ts.map +1 -0
  119. package/types/domain/usecases/ClearDependedIssueURLUseCase.d.ts +13 -0
  120. package/types/domain/usecases/ClearDependedIssueURLUseCase.d.ts.map +1 -0
  121. package/types/domain/usecases/ClearNextActionHourUseCase.d.ts.map +1 -1
  122. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts +20 -0
  123. package/types/domain/usecases/ConvertCheckboxToIssueInStoryIssueUseCase.d.ts.map +1 -0
  124. package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts +33 -0
  125. package/types/domain/usecases/CreateEstimationIssueUseCase.d.ts.map +1 -0
  126. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +28 -2
  127. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
  128. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts +2 -0
  129. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts.map +1 -1
  130. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +4 -1
  131. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
  132. package/types/domain/usecases/utils.d.ts +5 -0
  133. package/types/domain/usecases/utils.d.ts.map +1 -0
  134. package/types/index.d.ts +1 -1
@@ -0,0 +1,840 @@
1
+ import { mock } from 'jest-mock-extended';
2
+ import { IssueRepository } from './adapter-interfaces/IssueRepository';
3
+ import { ClearDependedIssueURLUseCase } from './ClearDependedIssueURLUseCase';
4
+ import { Project } from '../entities/Project';
5
+ import { Issue } from '../entities/Issue';
6
+
7
+ describe('ClearDependedIssueURLUseCase', () => {
8
+ jest.setTimeout(30 * 1000);
9
+ const mockIssueRepository = mock<IssueRepository>();
10
+ describe('run', () => {
11
+ const basicProject = {
12
+ ...mock<Project>(),
13
+ dependedIssueUrlSeparatedByComma: {
14
+ name: 'Depended Issue URL Separated By Comma',
15
+ fieldId: 'fieldId',
16
+ },
17
+ };
18
+ const basicIssueOne = {
19
+ ...mock<Issue>(),
20
+ url: 'url1',
21
+ dependedIssueUrls: [],
22
+ isClosed: true,
23
+ };
24
+ const basicIssueTwo = {
25
+ ...mock<Issue>(),
26
+ url: 'url2',
27
+ dependedIssueUrls: ['url1'],
28
+ isClosed: false,
29
+ };
30
+ const basicIssueThree = {
31
+ ...mock<Issue>(),
32
+ url: 'url3',
33
+ dependedIssueUrls: ['url1', 'url2'],
34
+ isClosed: false,
35
+ };
36
+ const testCases: {
37
+ name: string;
38
+ input: {
39
+ project: Project;
40
+ issues: Issue[];
41
+ cacheUsed: boolean;
42
+ };
43
+ expectedIssueRepositoryClearProjectFieldCalls: [Project, string, Issue][];
44
+ expectedIssueRepositoryUpdateTextFieldCalls: [
45
+ Project,
46
+ string,
47
+ Issue,
48
+ string,
49
+ ][];
50
+ expectedIssueRepositoryCreateCommentCalls: [Issue, string][];
51
+ }[] = [
52
+ {
53
+ name: 'should not call clearProjectField and createComment when dependedIssueUrlSeparatedByComma is not set',
54
+ input: {
55
+ project: {
56
+ ...basicProject,
57
+ dependedIssueUrlSeparatedByComma: null,
58
+ },
59
+ issues: [basicIssueOne, basicIssueTwo, basicIssueThree],
60
+ cacheUsed: false,
61
+ },
62
+ expectedIssueRepositoryClearProjectFieldCalls: [],
63
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
64
+ expectedIssueRepositoryCreateCommentCalls: [],
65
+ },
66
+ {
67
+ name: 'should not call clearProjectField and createComment when dependedIssueUrls is empty',
68
+ input: {
69
+ project: basicProject,
70
+ issues: [
71
+ basicIssueOne,
72
+ {
73
+ ...basicIssueTwo,
74
+ dependedIssueUrls: [],
75
+ },
76
+ {
77
+ ...basicIssueThree,
78
+ dependedIssueUrls: [],
79
+ },
80
+ ],
81
+ cacheUsed: false,
82
+ },
83
+ expectedIssueRepositoryClearProjectFieldCalls: [],
84
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
85
+ expectedIssueRepositoryCreateCommentCalls: [],
86
+ },
87
+ {
88
+ name: 'should call clearProjectField and createComment when dependedIssue is not found',
89
+ input: {
90
+ project: basicProject,
91
+ issues: [
92
+ basicIssueOne,
93
+ {
94
+ ...basicIssueTwo,
95
+ dependedIssueUrls: ['url4'],
96
+ },
97
+ {
98
+ ...basicIssueThree,
99
+ dependedIssueUrls: ['url5', 'url6'],
100
+ },
101
+ ],
102
+ cacheUsed: false,
103
+ },
104
+ expectedIssueRepositoryClearProjectFieldCalls: [
105
+ [
106
+ basicProject,
107
+ 'fieldId',
108
+ {
109
+ ...basicIssueTwo,
110
+ dependedIssueUrls: ['url4'],
111
+ },
112
+ ],
113
+ [
114
+ basicProject,
115
+ 'fieldId',
116
+ {
117
+ ...basicIssueThree,
118
+ dependedIssueUrls: ['url5', 'url6'],
119
+ },
120
+ ],
121
+ ],
122
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
123
+ expectedIssueRepositoryCreateCommentCalls: [
124
+ [
125
+ { ...basicIssueTwo, dependedIssueUrls: ['url4'] },
126
+ 'Closed all depended issues:\n- url4',
127
+ ],
128
+ [
129
+ { ...basicIssueThree, dependedIssueUrls: ['url5', 'url6'] },
130
+ 'Closed all depended issues:\n- url5\n- url6',
131
+ ],
132
+ ],
133
+ },
134
+ {
135
+ name: 'should not call clearProjectField and createComment when dependedIssue is not closed',
136
+ input: {
137
+ project: basicProject,
138
+ issues: [
139
+ {
140
+ ...basicIssueOne,
141
+ isClosed: false,
142
+ },
143
+ basicIssueTwo,
144
+ basicIssueThree,
145
+ ],
146
+ cacheUsed: false,
147
+ },
148
+ expectedIssueRepositoryClearProjectFieldCalls: [],
149
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
150
+ expectedIssueRepositoryCreateCommentCalls: [],
151
+ },
152
+ {
153
+ name: 'should call clearProjectField and createComment when dependedIssue is closed',
154
+ input: {
155
+ project: basicProject,
156
+ issues: [basicIssueOne, basicIssueTwo, basicIssueThree],
157
+ cacheUsed: false,
158
+ },
159
+ expectedIssueRepositoryClearProjectFieldCalls: [
160
+ [basicProject, 'fieldId', basicIssueTwo],
161
+ ],
162
+ expectedIssueRepositoryUpdateTextFieldCalls: [
163
+ [basicProject, 'fieldId', basicIssueThree, 'url2'],
164
+ ],
165
+ expectedIssueRepositoryCreateCommentCalls: [
166
+ [basicIssueTwo, 'Closed all depended issues:\n- url1'],
167
+ [basicIssueThree, 'Closed depended issues:\n- url1'],
168
+ ],
169
+ },
170
+ {
171
+ name: 'should call clearProjectField and createComment once for one closed dependedIssue',
172
+ input: {
173
+ project: basicProject,
174
+ issues: [
175
+ basicIssueOne,
176
+ basicIssueTwo,
177
+ {
178
+ ...basicIssueThree,
179
+ dependedIssueUrls: ['url2'],
180
+ },
181
+ ],
182
+ cacheUsed: false,
183
+ },
184
+ expectedIssueRepositoryClearProjectFieldCalls: [
185
+ [basicProject, 'fieldId', basicIssueTwo],
186
+ ],
187
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
188
+ expectedIssueRepositoryCreateCommentCalls: [
189
+ [basicIssueTwo, 'Closed all depended issues:\n- url1'],
190
+ ],
191
+ },
192
+ {
193
+ name: 'should call clearProjectField and createComment twice for two closed dependedIssues',
194
+ input: {
195
+ project: basicProject,
196
+ issues: [
197
+ basicIssueOne,
198
+ {
199
+ ...basicIssueTwo,
200
+ isClosed: true,
201
+ },
202
+ basicIssueThree,
203
+ ],
204
+ cacheUsed: false,
205
+ },
206
+ expectedIssueRepositoryClearProjectFieldCalls: [
207
+ [basicProject, 'fieldId', basicIssueThree],
208
+ ],
209
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
210
+ expectedIssueRepositoryCreateCommentCalls: [
211
+ [basicIssueThree, 'Closed all depended issues:\n- url1\n- url2'],
212
+ ],
213
+ },
214
+ {
215
+ name: 'should not call clearProjectField and createComment when dependedIssue is closed and cacheUsed is true',
216
+ input: {
217
+ project: basicProject,
218
+ issues: [basicIssueOne, basicIssueTwo, basicIssueThree],
219
+ cacheUsed: true,
220
+ },
221
+ expectedIssueRepositoryClearProjectFieldCalls: [],
222
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
223
+ expectedIssueRepositoryCreateCommentCalls: [],
224
+ },
225
+ {
226
+ name: 'should not call clearProjectField and createComment when target issue is closed',
227
+ input: {
228
+ project: basicProject,
229
+ issues: [
230
+ basicIssueOne,
231
+ {
232
+ ...basicIssueTwo,
233
+ isClosed: true,
234
+ },
235
+ {
236
+ ...basicIssueThree,
237
+ isClosed: true,
238
+ },
239
+ ],
240
+ cacheUsed: false,
241
+ },
242
+ expectedIssueRepositoryClearProjectFieldCalls: [],
243
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
244
+ expectedIssueRepositoryCreateCommentCalls: [],
245
+ },
246
+ {
247
+ name: 'should call clearProjectField and createComment when dependedIssue depends on each other',
248
+ input: {
249
+ project: basicProject,
250
+ issues: [
251
+ basicIssueOne,
252
+ {
253
+ ...basicIssueTwo,
254
+ dependedIssueUrls: ['url3'],
255
+ },
256
+ {
257
+ ...basicIssueThree,
258
+ dependedIssueUrls: ['url2'],
259
+ },
260
+ ],
261
+ cacheUsed: false,
262
+ },
263
+ expectedIssueRepositoryClearProjectFieldCalls: [
264
+ [
265
+ basicProject,
266
+ 'fieldId',
267
+ {
268
+ ...basicIssueTwo,
269
+ dependedIssueUrls: ['url3'],
270
+ },
271
+ ],
272
+ [
273
+ basicProject,
274
+ 'fieldId',
275
+ {
276
+ ...basicIssueThree,
277
+ dependedIssueUrls: ['url2'],
278
+ },
279
+ ],
280
+ ],
281
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
282
+ expectedIssueRepositoryCreateCommentCalls: [
283
+ [
284
+ {
285
+ ...basicIssueTwo,
286
+ dependedIssueUrls: ['url3'],
287
+ },
288
+ 'Circular dependency removed:\n- url3',
289
+ ],
290
+ [
291
+ {
292
+ ...basicIssueThree,
293
+ dependedIssueUrls: ['url2'],
294
+ },
295
+ 'Circular dependency removed:\n- url2',
296
+ ],
297
+ ],
298
+ },
299
+ {
300
+ name: 'should call clearProjectField and createComment when issue depends on own',
301
+ input: {
302
+ project: basicProject,
303
+ issues: [
304
+ basicIssueOne,
305
+ {
306
+ ...basicIssueTwo,
307
+ dependedIssueUrls: ['url2'],
308
+ },
309
+ {
310
+ ...basicIssueThree,
311
+ dependedIssueUrls: ['url3'],
312
+ },
313
+ ],
314
+ cacheUsed: false,
315
+ },
316
+ expectedIssueRepositoryClearProjectFieldCalls: [
317
+ [
318
+ basicProject,
319
+ 'fieldId',
320
+ {
321
+ ...basicIssueTwo,
322
+ dependedIssueUrls: ['url2'],
323
+ },
324
+ ],
325
+ [
326
+ basicProject,
327
+ 'fieldId',
328
+ {
329
+ ...basicIssueThree,
330
+ dependedIssueUrls: ['url3'],
331
+ },
332
+ ],
333
+ ],
334
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
335
+ expectedIssueRepositoryCreateCommentCalls: [
336
+ [
337
+ {
338
+ ...basicIssueTwo,
339
+ dependedIssueUrls: ['url2'],
340
+ },
341
+ 'Circular dependency removed:\n- url2',
342
+ ],
343
+ [
344
+ {
345
+ ...basicIssueThree,
346
+ dependedIssueUrls: ['url3'],
347
+ },
348
+ 'Circular dependency removed:\n- url3',
349
+ ],
350
+ ],
351
+ },
352
+ {
353
+ name: 'should call clearProjectField and createComment when issue depends circular',
354
+ input: {
355
+ project: basicProject,
356
+ issues: [
357
+ { ...basicIssueOne, dependedIssueUrls: ['url3'], isClosed: false },
358
+ basicIssueTwo,
359
+ basicIssueThree,
360
+ ],
361
+
362
+ cacheUsed: false,
363
+ },
364
+ expectedIssueRepositoryClearProjectFieldCalls: [
365
+ [
366
+ basicProject,
367
+ 'fieldId',
368
+ {
369
+ ...basicIssueOne,
370
+ dependedIssueUrls: ['url3'],
371
+ isClosed: false,
372
+ },
373
+ ],
374
+ [basicProject, 'fieldId', basicIssueTwo],
375
+ [basicProject, 'fieldId', basicIssueThree],
376
+ ],
377
+ expectedIssueRepositoryUpdateTextFieldCalls: [],
378
+ expectedIssueRepositoryCreateCommentCalls: [
379
+ [
380
+ {
381
+ ...basicIssueOne,
382
+ dependedIssueUrls: ['url3'],
383
+ isClosed: false,
384
+ },
385
+ 'Circular dependency removed:\n- url3',
386
+ ],
387
+ [basicIssueTwo, 'Circular dependency removed:\n- url1'],
388
+ [basicIssueThree, 'Circular dependency removed:\n- url1\n- url2'],
389
+ ],
390
+ },
391
+ ];
392
+ testCases.forEach(
393
+ ({
394
+ name,
395
+ input,
396
+ expectedIssueRepositoryClearProjectFieldCalls,
397
+ expectedIssueRepositoryCreateCommentCalls,
398
+ }) => {
399
+ it(name, async () => {
400
+ jest.clearAllMocks();
401
+ const useCase = new ClearDependedIssueURLUseCase(mockIssueRepository);
402
+ await useCase.run(input);
403
+ expect(mockIssueRepository.clearProjectField.mock.calls).toEqual(
404
+ expectedIssueRepositoryClearProjectFieldCalls,
405
+ );
406
+ expect(mockIssueRepository.createComment.mock.calls).toEqual(
407
+ expectedIssueRepositoryCreateCommentCalls,
408
+ );
409
+ });
410
+ },
411
+ );
412
+ });
413
+ });
414
+
415
+ // import { Issue } from '../entities/Issue';
416
+ // import { IssueRepository } from './adapter-interfaces/IssueRepository';
417
+ // import { Project } from '../entities/Project';
418
+ //
419
+ // export class ClearDependedIssueURLUseCase {
420
+ // constructor(
421
+ // readonly issueRepository: Pick<IssueRepository, 'clearProjectField' | 'createComment'>,
422
+ // ) {}
423
+ //
424
+ // run = async (input: {
425
+ // project: Project;
426
+ // issues: Issue[];
427
+ // cacheUsed: boolean;
428
+ // }): Promise<void> => {
429
+ // const dependedIssueUrlSeparatedByComma = input.project.dependedIssueUrlSeparatedByComma;
430
+ // if (!dependedIssueUrlSeparatedByComma || input.cacheUsed) {
431
+ // return;
432
+ // }
433
+ // for (const issue of input.issues) {
434
+ // if (issue.dependedIssueUrls.length <= 0 || issue.isClosed) {
435
+ // continue;
436
+ // }
437
+ // for(const dependedIssueUrl of issue.dependedIssueUrls) {
438
+ // const dependedIssue = input.issues.find(issue => issue.url === dependedIssueUrl);
439
+ // if (!dependedIssue) {
440
+ // continue;
441
+ // }else if (!dependedIssue.isClosed){
442
+ // continue;
443
+ // }
444
+ // await this.issueRepository.clearProjectField(
445
+ // input.project,
446
+ // dependedIssueUrlSeparatedByComma.fieldId,
447
+ // issue,
448
+ // );
449
+ // await this.issueRepository.createComment(
450
+ // issue,
451
+ // `Depended issue ${dependedIssueUrl} is closed.`
452
+ // );
453
+ // await new Promise((resolve) => setTimeout(resolve, 5000));
454
+ // }
455
+ // }
456
+ // };
457
+ // }
458
+
459
+ // import { Issue } from '../entities/Issue';
460
+ // import { Member } from '../entities/Member';
461
+ // import { IssueRepository } from './adapter-interfaces/IssueRepository';
462
+ // import {
463
+ // GenerateWorkingTimeReportUseCase,
464
+ // WorkingReportTimelineEvent,
465
+ // } from './GenerateWorkingTimeReportUseCase';
466
+ // import { SpreadsheetRepository } from './adapter-interfaces/SpreadsheetRepository';
467
+ // import { DateRepository } from './adapter-interfaces/DateRepository';
468
+ // import { mock } from 'jest-mock-extended';
469
+ //
470
+ // describe('GenerateWorkingTimeReportUseCase', () => {
471
+ // jest.setTimeout(30 * 1000);
472
+ // const mockIssueRepository = mock<IssueRepository>();
473
+ // const mockSpreadsheetRepository = mock<SpreadsheetRepository>();
474
+ // const mockDateRepository = mock<DateRepository>();
475
+ //
476
+ // const useCase = new GenerateWorkingTimeReportUseCase(
477
+ // mockIssueRepository,
478
+ // mockSpreadsheetRepository,
479
+ // mockDateRepository,
480
+ // );
481
+ // describe('getWorkingReportIssueTemplate', () => {
482
+ // interface TestCase {
483
+ // name: string;
484
+ // input: {
485
+ // reportIssueTemplate?: string;
486
+ // manager: Member['name'];
487
+ // spreadsheetUrl: string;
488
+ // };
489
+ // expected: string;
490
+ // }
491
+ //
492
+ // const testCases: TestCase[] = [
493
+ // {
494
+ // name: 'should return custom template when provided',
495
+ // input: {
496
+ // reportIssueTemplate: 'Custom template for {AUTHOR}',
497
+ // manager: 'manager1',
498
+ // spreadsheetUrl: 'https://example.com',
499
+ // },
500
+ // expected: 'Custom template for {AUTHOR}',
501
+ // },
502
+ // {
503
+ // name: 'should return default template when no custom template provided',
504
+ // input: {
505
+ // manager: 'manager1',
506
+ // spreadsheetUrl: 'https://example.com',
507
+ // },
508
+ // expected: `
509
+ // Please confirm each working time and total working time and assign to :bow:
510
+ // Fix warnings if you have :warning: mark in Detail section.
511
+ // If you have any questions, please put comment and assign to @manager1 :pray:
512
+ //
513
+ // ## Working report for {AUTHOR} on {DATE_WITH_DAY_OF_WEEK}
514
+ // ### Total
515
+ // \`\`\`
516
+ // {TOTAL_WORKING_TIME_HHMM}
517
+ // \`\`\`
518
+ //
519
+ // ### Detail
520
+ // {TIMELINE_DETAILS}
521
+ //
522
+ // Summary of working report: https://example.com
523
+ // `,
524
+ // },
525
+ // ];
526
+ //
527
+ // testCases.forEach(({ name, input, expected }) => {
528
+ // it(name, async () => {
529
+ // const result = await useCase.getWorkingReportIssueTemplate(input);
530
+ // expect(result).toBe(expected);
531
+ // });
532
+ // });
533
+ // });
534
+ //
535
+ // describe('filterTimelineAndSortByAuthor', () => {
536
+ // interface TestCase {
537
+ // name: string;
538
+ // input: {
539
+ // issues: Issue[];
540
+ // targetDate: Date;
541
+ // author: Member['name'];
542
+ // workingTimeThresholdHour: number;
543
+ // };
544
+ // expected: WorkingReportTimelineEvent[];
545
+ // }
546
+ //
547
+ // const testCases: TestCase[] = [
548
+ // {
549
+ // name: 'should filter and sort timeline events correctly',
550
+ // input: {
551
+ // issues: [
552
+ // {
553
+ // nameWithOwner: 'org/repo',
554
+ // number: 1,
555
+ // title: 'Issue 1',
556
+ // state: 'OPEN',
557
+ // url: 'https://example.com/1',
558
+ // assignees: ['user1'],
559
+ // labels: [],
560
+ // workingTimeline: [
561
+ // {
562
+ // author: 'user1',
563
+ // startedAt: new Date('2024-01-01T09:00:00Z'),
564
+ // endedAt: new Date('2024-01-01T12:00:00Z'),
565
+ // durationMinutes: 180,
566
+ // },
567
+ // ],
568
+ // status: 'In Progress',
569
+ // story: 'test story',
570
+ // nextActionDate: new Date('2024-01-02'),
571
+ // nextActionHour: 10,
572
+ // estimationMinutes: 180,
573
+ // org: 'org',
574
+ // repo: 'repo',
575
+ // body: 'test body',
576
+ // itemId: 'itemId',
577
+ // isPr: false,
578
+ // },
579
+ // ],
580
+ // targetDate: new Date('2024-01-01'),
581
+ // author: 'user1',
582
+ // workingTimeThresholdHour: 6,
583
+ // },
584
+ // expected: [
585
+ // {
586
+ // issueUrl: 'https://example.com/1',
587
+ // startHhmm: '09:00',
588
+ // endHhmm: '12:00',
589
+ // durationHhmm: '03:00',
590
+ // warnings: [],
591
+ // labels: [],
592
+ // nameWithOwner: 'org/repo',
593
+ // issueTitle: 'Issue 1',
594
+ // },
595
+ // ],
596
+ // },
597
+ // ];
598
+ //
599
+ // testCases.forEach(({ name, input, expected }) => {
600
+ // it(name, () => {
601
+ // const result = useCase.filterTimelineAndSortByAuthor(
602
+ // input.issues,
603
+ // input.targetDate,
604
+ // input.author,
605
+ // input.workingTimeThresholdHour,
606
+ // );
607
+ // expect(result).toEqual(expected);
608
+ // });
609
+ // });
610
+ // });
611
+ //
612
+ // describe('convertIsoToHhmm', () => {
613
+ // interface TestCase {
614
+ // name: string;
615
+ // input: string;
616
+ // expected: string;
617
+ // }
618
+ //
619
+ // const testCases: TestCase[] = [
620
+ // {
621
+ // name: 'should convert ISO string to HH:mm format',
622
+ // input: '2024-01-01T09:30:00Z',
623
+ // expected: '09:30',
624
+ // },
625
+ // {
626
+ // name: 'should pad single digit hours and minutes',
627
+ // input: '2024-01-01T05:05:00Z',
628
+ // expected: '05:05',
629
+ // },
630
+ // ];
631
+ //
632
+ // testCases.forEach(({ name, input, expected }) => {
633
+ // it(name, () => {
634
+ // const result = useCase.convertIsoToHhmm(input);
635
+ // expect(result).toBe(expected);
636
+ // });
637
+ // });
638
+ // });
639
+ //
640
+ // describe('calculateDuration', () => {
641
+ // interface TestCase {
642
+ // name: string;
643
+ // input: {
644
+ // startIsoString: string;
645
+ // endIsoString: string;
646
+ // };
647
+ // expected: string;
648
+ // }
649
+ //
650
+ // const testCases: TestCase[] = [
651
+ // {
652
+ // name: 'should calculate duration correctly',
653
+ // input: {
654
+ // startIsoString: '2024-01-01T09:00:00Z',
655
+ // endIsoString: '2024-01-01T12:30:00Z',
656
+ // },
657
+ // expected: '03:30',
658
+ // },
659
+ // {
660
+ // name: 'should handle same hour different minutes',
661
+ // input: {
662
+ // startIsoString: '2024-01-01T09:00:00Z',
663
+ // endIsoString: '2024-01-01T09:30:00Z',
664
+ // },
665
+ // expected: '00:30',
666
+ // },
667
+ // ];
668
+ //
669
+ // testCases.forEach(({ name, input, expected }) => {
670
+ // it(name, () => {
671
+ // const result = useCase.calculateDuration(
672
+ // input.startIsoString,
673
+ // input.endIsoString,
674
+ // );
675
+ // expect(result).toBe(expected);
676
+ // });
677
+ // });
678
+ // });
679
+ //
680
+ // describe('calculateTotalHhmm', () => {
681
+ // interface TestCase {
682
+ // name: string;
683
+ // input: WorkingReportTimelineEvent[];
684
+ // expected: string;
685
+ // }
686
+ //
687
+ // const testCases: TestCase[] = [
688
+ // {
689
+ // name: 'should calculate total duration correctly',
690
+ // input: [
691
+ // {
692
+ // issueUrl: 'https://example.com/1',
693
+ // startHhmm: '09:00',
694
+ // endHhmm: '12:00',
695
+ // durationHhmm: '03:00',
696
+ // warnings: [],
697
+ // labels: [],
698
+ // issueTitle: 'Issue 1',
699
+ // nameWithOwner: 'org/repo',
700
+ // },
701
+ // {
702
+ // issueUrl: 'https://example.com/2',
703
+ // startHhmm: '13:00',
704
+ // endHhmm: '15:30',
705
+ // durationHhmm: '02:30',
706
+ // warnings: [],
707
+ // labels: [],
708
+ // issueTitle: 'Issue 2',
709
+ // nameWithOwner: 'org/repo',
710
+ // },
711
+ // ],
712
+ // expected: '05:30',
713
+ // },
714
+ // ];
715
+ //
716
+ // testCases.forEach(({ name, input, expected }) => {
717
+ // it(name, () => {
718
+ // mockDateRepository.formatDurationToHHMM.mockImplementation(
719
+ // (durationMinutes: number) => {
720
+ // if (durationMinutes === 330) return '05:30';
721
+ // return '';
722
+ // },
723
+ // );
724
+ // const result = useCase.calculateTotalHhmm(input);
725
+ // expect(result).toBe(expected);
726
+ // });
727
+ // });
728
+ // });
729
+ //
730
+ // describe('applyReplacementToTemplate', () => {
731
+ // interface TestCase {
732
+ // name: string;
733
+ // input: {
734
+ // template: string;
735
+ // replacement: Record<string, string>;
736
+ // };
737
+ // expected: string | Error;
738
+ // }
739
+ //
740
+ // const testCases: TestCase[] = [
741
+ // {
742
+ // name: 'should replace all placeholders correctly',
743
+ // input: {
744
+ // template: 'Hello {NAME}, your score is {SCORE}',
745
+ // replacement: {
746
+ // NAME: 'John',
747
+ // SCORE: '100',
748
+ // },
749
+ // },
750
+ // expected: 'Hello John, your score is 100',
751
+ // },
752
+ // {
753
+ // name: 'should throw error for unknown placeholders',
754
+ // input: {
755
+ // template: 'Hello {NAME}, your score is {UNKNOWN}',
756
+ // replacement: {
757
+ // NAME: 'John',
758
+ // },
759
+ // },
760
+ // expected: new Error('Failed to replace. Unknown keys: UNKNOWN'),
761
+ // },
762
+ // ];
763
+ //
764
+ // testCases.forEach(({ name, input, expected }) => {
765
+ // it(name, () => {
766
+ // if (expected instanceof Error) {
767
+ // expect(() => useCase.applyReplacementToTemplate(input)).toThrowError(
768
+ // expected.message,
769
+ // );
770
+ // } else {
771
+ // const result = useCase.applyReplacementToTemplate(input);
772
+ // expect(result).toBe(expected);
773
+ // }
774
+ // });
775
+ // });
776
+ // });
777
+ //
778
+ // describe('run', () => {
779
+ // interface TestCase {
780
+ // name: string;
781
+ // input: Parameters<GenerateWorkingTimeReportUseCase['run']>[0];
782
+ // expectedCalls: number;
783
+ // }
784
+ //
785
+ // const testCases: TestCase[] = [
786
+ // {
787
+ // name: 'should create issues for all members',
788
+ // input: {
789
+ // issues: [
790
+ // {
791
+ // nameWithOwner: 'org/repo',
792
+ // number: 1,
793
+ // title: 'Issue 1',
794
+ // state: 'OPEN',
795
+ // url: 'https://example.com/1',
796
+ // assignees: ['user1'],
797
+ // labels: [],
798
+ // workingTimeline: [
799
+ // {
800
+ // author: 'user1',
801
+ // startedAt: new Date(),
802
+ // endedAt: new Date(),
803
+ // durationMinutes: 60,
804
+ // },
805
+ // ],
806
+ // status: 'In Progress',
807
+ // story: 'test story',
808
+ // nextActionDate: new Date(),
809
+ // nextActionHour: 10,
810
+ // estimationMinutes: 60,
811
+ // org: 'org',
812
+ // repo: 'repo',
813
+ // body: 'test body',
814
+ // itemId: 'itemId',
815
+ // isPr: false,
816
+ // },
817
+ // ],
818
+ // members: ['user1', 'user2'],
819
+ // manager: 'manager1',
820
+ // spreadsheetUrl: 'https://example.com',
821
+ // org: 'testOrg',
822
+ // repo: 'testRepo',
823
+ // reportIssueLabels: ['report'],
824
+ // warningThresholdHour: 6,
825
+ // targetDate: new Date(),
826
+ // },
827
+ // expectedCalls: 2,
828
+ // },
829
+ // ];
830
+ //
831
+ // testCases.forEach(({ name, input, expectedCalls }) => {
832
+ // it(name, async () => {
833
+ // await useCase.run(input);
834
+ // expect(mockIssueRepository.createNewIssue).toHaveBeenCalledTimes(
835
+ // expectedCalls,
836
+ // );
837
+ // });
838
+ // });
839
+ // });
840
+ // });