github-issue-tower-defence-management 1.1.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 (221) hide show
  1. package/.env.example +6 -0
  2. package/.eslintrc.cjs +55 -0
  3. package/.github/CODEOWNERS +2 -0
  4. package/.github/workflows/assign-all-cards-to-owner.yml +14 -0
  5. package/.github/workflows/commit-lint.yml +54 -0
  6. package/.github/workflows/configs/commitlint.config.js +27 -0
  7. package/.github/workflows/create-pr.yml +64 -0
  8. package/.github/workflows/format.yml +25 -0
  9. package/.github/workflows/publish.yml +47 -0
  10. package/.github/workflows/test.yml +45 -0
  11. package/.github/workflows/umino-project.yml +181 -0
  12. package/.prettierignore +22 -0
  13. package/.prettierrc +5 -0
  14. package/CHANGELOG.md +49 -0
  15. package/CONTRIBUTING.md +107 -0
  16. package/README.md +108 -0
  17. package/bin/adapter/entry-points/cli/index.js +26 -0
  18. package/bin/adapter/entry-points/cli/index.js.map +1 -0
  19. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +142 -0
  20. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -0
  21. package/bin/adapter/repositories/AxiosSlackRepository.js +124 -0
  22. package/bin/adapter/repositories/AxiosSlackRepository.js.map +1 -0
  23. package/bin/adapter/repositories/BaseGitHubRepository.js +136 -0
  24. package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -0
  25. package/bin/adapter/repositories/GoogleSpreadsheetRepository.js +123 -0
  26. package/bin/adapter/repositories/GoogleSpreadsheetRepository.js.map +1 -0
  27. package/bin/adapter/repositories/GraphqlProjectRepository.js +167 -0
  28. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -0
  29. package/bin/adapter/repositories/LocalStorageCacheRepository.js +46 -0
  30. package/bin/adapter/repositories/LocalStorageCacheRepository.js.map +1 -0
  31. package/bin/adapter/repositories/LocalStorageRepository.js +30 -0
  32. package/bin/adapter/repositories/LocalStorageRepository.js.map +1 -0
  33. package/bin/adapter/repositories/SystemDateRepository.js +23 -0
  34. package/bin/adapter/repositories/SystemDateRepository.js.map +1 -0
  35. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +148 -0
  36. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -0
  37. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js +48 -0
  38. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js.map +1 -0
  39. package/bin/adapter/repositories/issue/CheerioIssueRepository.js +120 -0
  40. package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -0
  41. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +485 -0
  42. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -0
  43. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +114 -0
  44. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -0
  45. package/bin/adapter/repositories/issue/RestIssueRepository.js +79 -0
  46. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -0
  47. package/bin/adapter/repositories/issue/issueTimelineUtils.js +38 -0
  48. package/bin/adapter/repositories/issue/issueTimelineUtils.js.map +1 -0
  49. package/bin/adapter/repositories/utils.js +45 -0
  50. package/bin/adapter/repositories/utils.js.map +1 -0
  51. package/bin/domain/entities/Issue.js +3 -0
  52. package/bin/domain/entities/Issue.js.map +1 -0
  53. package/bin/domain/entities/Member.js +3 -0
  54. package/bin/domain/entities/Member.js.map +1 -0
  55. package/bin/domain/entities/Project.js +3 -0
  56. package/bin/domain/entities/Project.js.map +1 -0
  57. package/bin/domain/entities/ProjectField.js +3 -0
  58. package/bin/domain/entities/ProjectField.js.map +1 -0
  59. package/bin/domain/entities/ProjectFieldSingleSelect.js +3 -0
  60. package/bin/domain/entities/ProjectFieldSingleSelect.js.map +1 -0
  61. package/bin/domain/entities/ProjectFieldSingleSelectOption.js +3 -0
  62. package/bin/domain/entities/ProjectFieldSingleSelectOption.js.map +1 -0
  63. package/bin/domain/entities/WorkingTime.js +3 -0
  64. package/bin/domain/entities/WorkingTime.js.map +1 -0
  65. package/bin/domain/usecases/ActionAnnouncementUseCase.js +46 -0
  66. package/bin/domain/usecases/ActionAnnouncementUseCase.js.map +1 -0
  67. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js +116 -0
  68. package/bin/domain/usecases/AnalyzeProblemByIssueUseCase.js.map +1 -0
  69. package/bin/domain/usecases/ClearNextActionHourUseCase.js +38 -0
  70. package/bin/domain/usecases/ClearNextActionHourUseCase.js.map +1 -0
  71. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js +180 -0
  72. package/bin/domain/usecases/GenerateWorkingTimeReportUseCase.js.map +1 -0
  73. package/bin/domain/usecases/HandleScheduledEventUseCase.js +122 -0
  74. package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -0
  75. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +35 -0
  76. package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -0
  77. package/bin/domain/usecases/adapter-interfaces/DateRepository.js +3 -0
  78. package/bin/domain/usecases/adapter-interfaces/DateRepository.js.map +1 -0
  79. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js +3 -0
  80. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js.map +1 -0
  81. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js +3 -0
  82. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js.map +1 -0
  83. package/bin/domain/usecases/adapter-interfaces/SlackRepository.js +3 -0
  84. package/bin/domain/usecases/adapter-interfaces/SlackRepository.js.map +1 -0
  85. package/bin/domain/usecases/adapter-interfaces/SpreadsheetRepository.js +3 -0
  86. package/bin/domain/usecases/adapter-interfaces/SpreadsheetRepository.js.map +1 -0
  87. package/bin/index.js +13 -0
  88. package/bin/index.js.map +1 -0
  89. package/commitlint.config.js +6 -0
  90. package/jest.config.js +19 -0
  91. package/package.json +80 -0
  92. package/renovate.json +37 -0
  93. package/src/adapter/entry-points/cli/index.test.ts +20 -0
  94. package/src/adapter/entry-points/cli/index.ts +36 -0
  95. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +95 -0
  96. package/src/adapter/repositories/AxiosSlackRepository.test.ts +119 -0
  97. package/src/adapter/repositories/AxiosSlackRepository.ts +184 -0
  98. package/src/adapter/repositories/BaseGitHubRepository.test.ts +95 -0
  99. package/src/adapter/repositories/BaseGitHubRepository.ts +172 -0
  100. package/src/adapter/repositories/GoogleSpreadsheetRepository.test.ts +124 -0
  101. package/src/adapter/repositories/GoogleSpreadsheetRepository.ts +151 -0
  102. package/src/adapter/repositories/GraphqlProjectRepository.test.ts +46 -0
  103. package/src/adapter/repositories/GraphqlProjectRepository.ts +236 -0
  104. package/src/adapter/repositories/LocalStorageCacheRepository.test.ts +146 -0
  105. package/src/adapter/repositories/LocalStorageCacheRepository.ts +53 -0
  106. package/src/adapter/repositories/LocalStorageRepository.integration.test.ts +142 -0
  107. package/src/adapter/repositories/LocalStorageRepository.test.ts +161 -0
  108. package/src/adapter/repositories/LocalStorageRepository.ts +21 -0
  109. package/src/adapter/repositories/SystemDateRepository.ts +20 -0
  110. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +158 -0
  111. package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +274 -0
  112. package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +26 -0
  113. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +59 -0
  114. package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +6610 -0
  115. package/src/adapter/repositories/issue/CheerioIssueRepository.ts +127 -0
  116. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +49 -0
  117. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +745 -0
  118. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +71 -0
  119. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +263 -0
  120. package/src/adapter/repositories/issue/RestIssueRepository.test.ts +29 -0
  121. package/src/adapter/repositories/issue/RestIssueRepository.ts +105 -0
  122. package/src/adapter/repositories/issue/issueTimelineUtils.test.ts +79 -0
  123. package/src/adapter/repositories/issue/issueTimelineUtils.ts +52 -0
  124. package/src/adapter/repositories/utils.test.ts +40 -0
  125. package/src/adapter/repositories/utils.ts +50 -0
  126. package/src/domain/entities/Issue.ts +23 -0
  127. package/src/domain/entities/Member.ts +3 -0
  128. package/src/domain/entities/Project.ts +29 -0
  129. package/src/domain/entities/ProjectField.ts +3 -0
  130. package/src/domain/entities/ProjectFieldSingleSelect.ts +8 -0
  131. package/src/domain/entities/ProjectFieldSingleSelectOption.ts +8 -0
  132. package/src/domain/entities/WorkingTime.ts +8 -0
  133. package/src/domain/usecases/ActionAnnouncementUseCase.ts +76 -0
  134. package/src/domain/usecases/AnalyzeProblemByIssueUseCase.ts +209 -0
  135. package/src/domain/usecases/ClearNextActionHourUseCase.ts +51 -0
  136. package/src/domain/usecases/GenerateWorkingTimeReportUseCase.test.ts +382 -0
  137. package/src/domain/usecases/GenerateWorkingTimeReportUseCase.ts +284 -0
  138. package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +58 -0
  139. package/src/domain/usecases/HandleScheduledEventUseCase.ts +209 -0
  140. package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +46 -0
  141. package/src/domain/usecases/adapter-interfaces/DateRepository.ts +5 -0
  142. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +44 -0
  143. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +6 -0
  144. package/src/domain/usecases/adapter-interfaces/SlackRepository.ts +20 -0
  145. package/src/domain/usecases/adapter-interfaces/SpreadsheetRepository.ts +18 -0
  146. package/src/index.test.ts +8 -0
  147. package/src/index.ts +7 -0
  148. package/tsconfig.build.json +11 -0
  149. package/tsconfig.json +22 -0
  150. package/types/adapter/entry-points/cli/index.d.ts +3 -0
  151. package/types/adapter/entry-points/cli/index.d.ts.map +1 -0
  152. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +11 -0
  153. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -0
  154. package/types/adapter/repositories/AxiosSlackRepository.d.ts +13 -0
  155. package/types/adapter/repositories/AxiosSlackRepository.d.ts.map +1 -0
  156. package/types/adapter/repositories/BaseGitHubRepository.d.ts +32 -0
  157. package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -0
  158. package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts +13 -0
  159. package/types/adapter/repositories/GoogleSpreadsheetRepository.d.ts.map +1 -0
  160. package/types/adapter/repositories/GraphqlProjectRepository.d.ts +13 -0
  161. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -0
  162. package/types/adapter/repositories/LocalStorageCacheRepository.d.ts +12 -0
  163. package/types/adapter/repositories/LocalStorageCacheRepository.d.ts.map +1 -0
  164. package/types/adapter/repositories/LocalStorageRepository.d.ts +7 -0
  165. package/types/adapter/repositories/LocalStorageRepository.d.ts.map +1 -0
  166. package/types/adapter/repositories/SystemDateRepository.d.ts +7 -0
  167. package/types/adapter/repositories/SystemDateRepository.d.ts.map +1 -0
  168. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +38 -0
  169. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -0
  170. package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts +17 -0
  171. package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts.map +1 -0
  172. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +31 -0
  173. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -0
  174. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +39 -0
  175. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -0
  176. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +83 -0
  177. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -0
  178. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +16 -0
  179. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -0
  180. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts +12 -0
  181. package/types/adapter/repositories/issue/issueTimelineUtils.d.ts.map +1 -0
  182. package/types/adapter/repositories/utils.d.ts +4 -0
  183. package/types/adapter/repositories/utils.d.ts.map +1 -0
  184. package/types/domain/entities/Issue.d.ts +24 -0
  185. package/types/domain/entities/Issue.d.ts.map +1 -0
  186. package/types/domain/entities/Member.d.ts +4 -0
  187. package/types/domain/entities/Member.d.ts.map +1 -0
  188. package/types/domain/entities/Project.d.ts +29 -0
  189. package/types/domain/entities/Project.d.ts.map +1 -0
  190. package/types/domain/entities/ProjectField.d.ts +3 -0
  191. package/types/domain/entities/ProjectField.d.ts.map +1 -0
  192. package/types/domain/entities/ProjectFieldSingleSelect.d.ts +8 -0
  193. package/types/domain/entities/ProjectFieldSingleSelect.d.ts.map +1 -0
  194. package/types/domain/entities/ProjectFieldSingleSelectOption.d.ts +8 -0
  195. package/types/domain/entities/ProjectFieldSingleSelectOption.d.ts.map +1 -0
  196. package/types/domain/entities/WorkingTime.d.ts +8 -0
  197. package/types/domain/entities/WorkingTime.d.ts.map +1 -0
  198. package/types/domain/usecases/ActionAnnouncementUseCase.d.ts +17 -0
  199. package/types/domain/usecases/ActionAnnouncementUseCase.d.ts.map +1 -0
  200. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts +26 -0
  201. package/types/domain/usecases/AnalyzeProblemByIssueUseCase.d.ts.map +1 -0
  202. package/types/domain/usecases/ClearNextActionHourUseCase.d.ts +14 -0
  203. package/types/domain/usecases/ClearNextActionHourUseCase.d.ts.map +1 -0
  204. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts +50 -0
  205. package/types/domain/usecases/GenerateWorkingTimeReportUseCase.d.ts.map +1 -0
  206. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +63 -0
  207. package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -0
  208. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +14 -0
  209. package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -0
  210. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts +6 -0
  211. package/types/domain/usecases/adapter-interfaces/DateRepository.d.ts.map +1 -0
  212. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +23 -0
  213. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -0
  214. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +6 -0
  215. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -0
  216. package/types/domain/usecases/adapter-interfaces/SlackRepository.d.ts +9 -0
  217. package/types/domain/usecases/adapter-interfaces/SlackRepository.d.ts.map +1 -0
  218. package/types/domain/usecases/adapter-interfaces/SpreadsheetRepository.d.ts +6 -0
  219. package/types/domain/usecases/adapter-interfaces/SpreadsheetRepository.d.ts.map +1 -0
  220. package/types/index.d.ts +10 -0
  221. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,53 @@
1
+ import { LocalStorageRepository } from './LocalStorageRepository';
2
+
3
+ export class LocalStorageCacheRepository {
4
+ constructor(
5
+ readonly localStorageRepository: LocalStorageRepository,
6
+ readonly cachePath = './tmp/cache',
7
+ ) {}
8
+
9
+ getLatest = async (
10
+ key: string,
11
+ ): Promise<{
12
+ value: object;
13
+ timestamp: Date;
14
+ } | null> => {
15
+ const dirPath = `${this.cachePath}/${key}`;
16
+ const latestFile = this.localStorageRepository
17
+ .listFiles(dirPath)
18
+ .sort((a, b) => a.localeCompare(b))
19
+ .reverse()[0];
20
+ if (!latestFile) {
21
+ return null;
22
+ }
23
+ const valueStr = this.localStorageRepository.read(
24
+ `${dirPath}/${latestFile}`,
25
+ );
26
+ if (!valueStr) {
27
+ return null;
28
+ }
29
+ let value: unknown;
30
+ try {
31
+ value = JSON.parse(valueStr);
32
+ } catch (e) {
33
+ return null;
34
+ }
35
+ if (typeof value !== 'object' || value === null) {
36
+ return null;
37
+ }
38
+ const timestampStr = latestFile.split('.')[0];
39
+ return {
40
+ value,
41
+ timestamp: new Date(timestampStr),
42
+ };
43
+ };
44
+ set = async <T>(key: string, value: T): Promise<void> => {
45
+ const dirPath = `${this.cachePath}/${key}`;
46
+ this.localStorageRepository.mkdir(dirPath);
47
+ const timestamp = new Date().toISOString();
48
+ this.localStorageRepository.write(
49
+ `${dirPath}/${timestamp}.json`,
50
+ JSON.stringify(value),
51
+ );
52
+ };
53
+ }
@@ -0,0 +1,142 @@
1
+ import fs from 'fs';
2
+ import { LocalStorageRepository } from './LocalStorageRepository';
3
+
4
+ type WriteParams = Parameters<LocalStorageRepository['write']>;
5
+ type ReadParams = Parameters<LocalStorageRepository['read']>;
6
+ type ReadReturn = ReturnType<LocalStorageRepository['read']>;
7
+ type ListFilesParams = Parameters<LocalStorageRepository['listFiles']>;
8
+ type ListFilesReturn = ReturnType<LocalStorageRepository['listFiles']>;
9
+ type MkdirParams = Parameters<LocalStorageRepository['mkdir']>;
10
+
11
+ describe('LocalStorageRepository', () => {
12
+ const repository = new LocalStorageRepository();
13
+
14
+ describe('write', () => {
15
+ const testCases: Array<{
16
+ name: string;
17
+ params: WriteParams;
18
+ expected: string;
19
+ }> = [
20
+ {
21
+ name: 'writes content to file',
22
+ params: ['tmp/test/LocalStorageRepository/file.txt', 'content'],
23
+ expected: 'content',
24
+ },
25
+ {
26
+ name: 'writes empty string to file',
27
+ params: ['tmp/test/LocalStorageRepository/fileempty.txt', ''],
28
+ expected: '',
29
+ },
30
+ ];
31
+
32
+ testCases.forEach(({ name, params }) => {
33
+ test(name, () => {
34
+ if (fs.existsSync(params[0])) {
35
+ fs.unlinkSync(params[0]);
36
+ }
37
+ repository.write(...params);
38
+ const content = fs.readFileSync(params[0], 'utf8');
39
+ expect(content).toBe(params[1]);
40
+ });
41
+ });
42
+ });
43
+
44
+ describe('read', () => {
45
+ const testCases: Array<{
46
+ name: string;
47
+ params: ReadParams;
48
+ content: string;
49
+ expected: ReadReturn;
50
+ }> = [
51
+ {
52
+ name: 'reads content from file',
53
+ params: ['tmp/test/LocalStorageRepository/file.txt'],
54
+ content: 'file content',
55
+ expected: 'file content',
56
+ },
57
+ {
58
+ name: 'reads empty file',
59
+ params: ['tmp/test/LocalStorageRepository/fileempty.txt'],
60
+ content: '',
61
+ expected: '',
62
+ },
63
+ ];
64
+
65
+ testCases.forEach(({ name, params, content, expected }) => {
66
+ test(name, () => {
67
+ if (fs.existsSync(params[0])) {
68
+ fs.unlinkSync(params[0]);
69
+ }
70
+ repository.write(params[0], content);
71
+ const result = repository.read(...params);
72
+ expect(result).toBe(expected);
73
+ });
74
+ });
75
+ });
76
+
77
+ describe('listFiles', () => {
78
+ const testCases: Array<{
79
+ name: string;
80
+ params: ListFilesParams;
81
+ readdirSyncReturn: string[];
82
+ expected: ListFilesReturn;
83
+ }> = [
84
+ {
85
+ name: 'lists files in existing directory',
86
+ params: ['tmp/test/LocalStorageRepository/exists'],
87
+ readdirSyncReturn: ['file1.txt', 'file2.txt'],
88
+ expected: ['file1.txt', 'file2.txt'],
89
+ },
90
+ {
91
+ name: 'returns empty array for non-existing directory',
92
+ params: ['tmp/test/LocalStorageRepository/nonexistent'],
93
+ readdirSyncReturn: [],
94
+ expected: [],
95
+ },
96
+ ];
97
+
98
+ testCases.forEach(({ name, params, readdirSyncReturn, expected }) => {
99
+ test(name, () => {
100
+ if (readdirSyncReturn.length > 0) {
101
+ if (!fs.existsSync(params[0])) {
102
+ fs.mkdirSync(params[0], { recursive: true });
103
+ }
104
+ readdirSyncReturn.forEach((file) => {
105
+ fs.writeFileSync(`${params[0]}/${file}`, 'content', 'utf8');
106
+ });
107
+ }
108
+ const result = repository.listFiles(...params);
109
+ expect(result).toEqual(expected);
110
+ });
111
+ });
112
+ });
113
+
114
+ describe('mkdir', () => {
115
+ const testCases: Array<{
116
+ name: string;
117
+ params: MkdirParams;
118
+ expected: void;
119
+ }> = [
120
+ {
121
+ name: 'creates directory with recursive option',
122
+ params: ['tmp/test/LocalStorageRepository/newdir'],
123
+ expected: undefined,
124
+ },
125
+ {
126
+ name: 'creates nested directories',
127
+ params: ['tmp/test/LocalStorageRepository/nested/newdir'],
128
+ expected: undefined,
129
+ },
130
+ ];
131
+
132
+ testCases.forEach(({ name, params }) => {
133
+ test(name, () => {
134
+ if (fs.existsSync(params[0])) {
135
+ fs.rmdirSync(params[0], { recursive: true });
136
+ }
137
+ repository.mkdir(...params);
138
+ expect(fs.existsSync(params[0])).toBe(true);
139
+ });
140
+ });
141
+ });
142
+ });
@@ -0,0 +1,161 @@
1
+ import fs from 'fs';
2
+ import { LocalStorageRepository } from './LocalStorageRepository';
3
+
4
+ type WriteParams = Parameters<LocalStorageRepository['write']>;
5
+ type ReadParams = Parameters<LocalStorageRepository['read']>;
6
+ type ReadReturn = ReturnType<LocalStorageRepository['read']>;
7
+ type ListFilesParams = Parameters<LocalStorageRepository['listFiles']>;
8
+ type ListFilesReturn = ReturnType<LocalStorageRepository['listFiles']>;
9
+ type MkdirParams = Parameters<LocalStorageRepository['mkdir']>;
10
+
11
+ describe('LocalStorageRepository', () => {
12
+ let repository: LocalStorageRepository;
13
+ let mockWriteFileSync: jest.SpyInstance;
14
+ let mockReadFileSync: jest.SpyInstance;
15
+ let mockReaddirSync: jest.SpyInstance;
16
+ let mockMkdirSync: jest.SpyInstance;
17
+ let mockExistsSync: jest.SpyInstance;
18
+
19
+ beforeEach(() => {
20
+ repository = new LocalStorageRepository();
21
+ mockWriteFileSync = jest.spyOn(fs, 'writeFileSync').mockImplementation();
22
+ mockReadFileSync = jest.spyOn(fs, 'readFileSync').mockImplementation();
23
+ mockReaddirSync = jest.spyOn(fs, 'readdirSync').mockImplementation();
24
+ mockMkdirSync = jest.spyOn(fs, 'mkdirSync').mockImplementation();
25
+ mockExistsSync = jest.spyOn(fs, 'existsSync').mockImplementation();
26
+ });
27
+
28
+ afterEach(() => {
29
+ jest.clearAllMocks();
30
+ });
31
+
32
+ describe('write', () => {
33
+ const testCases: Array<{
34
+ name: string;
35
+ params: WriteParams;
36
+ expected: void;
37
+ }> = [
38
+ {
39
+ name: 'writes content to file',
40
+ params: ['/path/to/file.txt', 'content'],
41
+ expected: undefined,
42
+ },
43
+ {
44
+ name: 'writes empty string to file',
45
+ params: ['/path/to/file.txt', ''],
46
+ expected: undefined,
47
+ },
48
+ ];
49
+
50
+ testCases.forEach(({ name, params }) => {
51
+ test(name, () => {
52
+ repository.write(...params);
53
+ expect(mockWriteFileSync).toHaveBeenCalledWith(
54
+ params[0],
55
+ params[1],
56
+ 'utf8',
57
+ );
58
+ });
59
+ });
60
+ });
61
+
62
+ describe('read', () => {
63
+ const testCases: Array<{
64
+ name: string;
65
+ params: ReadParams;
66
+ mockReturn: string;
67
+ expected: ReadReturn;
68
+ }> = [
69
+ {
70
+ name: 'reads content from file',
71
+ params: ['/path/to/file.txt'],
72
+ mockReturn: 'file content',
73
+ expected: 'file content',
74
+ },
75
+ {
76
+ name: 'reads empty file',
77
+ params: ['/path/to/empty.txt'],
78
+ mockReturn: '',
79
+ expected: '',
80
+ },
81
+ ];
82
+
83
+ testCases.forEach(({ name, params, mockReturn, expected }) => {
84
+ test(name, () => {
85
+ mockReadFileSync.mockReturnValue(mockReturn);
86
+ const result = repository.read(...params);
87
+ expect(result).toBe(expected);
88
+ expect(mockReadFileSync).toHaveBeenCalledWith(params[0], 'utf8');
89
+ });
90
+ });
91
+ });
92
+
93
+ describe('listFiles', () => {
94
+ const testCases: Array<{
95
+ name: string;
96
+ params: ListFilesParams;
97
+ existsSyncReturn: boolean;
98
+ readdirSyncReturn: string[];
99
+ expected: ListFilesReturn;
100
+ }> = [
101
+ {
102
+ name: 'lists files in existing directory',
103
+ params: ['/path/to/dir'],
104
+ existsSyncReturn: true,
105
+ readdirSyncReturn: ['file1.txt', 'file2.txt'],
106
+ expected: ['file1.txt', 'file2.txt'],
107
+ },
108
+ {
109
+ name: 'returns empty array for non-existing directory',
110
+ params: ['/path/to/nonexistent'],
111
+ existsSyncReturn: false,
112
+ readdirSyncReturn: [],
113
+ expected: [],
114
+ },
115
+ ];
116
+
117
+ testCases.forEach(
118
+ ({ name, params, existsSyncReturn, readdirSyncReturn, expected }) => {
119
+ test(name, () => {
120
+ mockExistsSync.mockReturnValue(existsSyncReturn);
121
+ mockReaddirSync.mockReturnValue(readdirSyncReturn);
122
+ const result = repository.listFiles(...params);
123
+ expect(result).toEqual(expected);
124
+ if (existsSyncReturn) {
125
+ expect(mockReaddirSync).toHaveBeenCalledWith(params[0]);
126
+ } else {
127
+ expect(mockReaddirSync).not.toHaveBeenCalled();
128
+ }
129
+ });
130
+ },
131
+ );
132
+ });
133
+
134
+ describe('mkdir', () => {
135
+ const testCases: Array<{
136
+ name: string;
137
+ params: MkdirParams;
138
+ expected: void;
139
+ }> = [
140
+ {
141
+ name: 'creates directory with recursive option',
142
+ params: ['/path/to/new/dir'],
143
+ expected: undefined,
144
+ },
145
+ {
146
+ name: 'creates nested directories',
147
+ params: ['/deeply/nested/directory/path'],
148
+ expected: undefined,
149
+ },
150
+ ];
151
+
152
+ testCases.forEach(({ name, params }) => {
153
+ test(name, () => {
154
+ repository.mkdir(...params);
155
+ expect(mockMkdirSync).toHaveBeenCalledWith(params[0], {
156
+ recursive: true,
157
+ });
158
+ });
159
+ });
160
+ });
161
+ });
@@ -0,0 +1,21 @@
1
+ import fs from 'fs';
2
+
3
+ export class LocalStorageRepository {
4
+ write = (path: string, value: string) => {
5
+ const dirPath = path.split('/').slice(0, -1).join('/');
6
+ this.mkdir(dirPath);
7
+ fs.writeFileSync(path, value, 'utf8');
8
+ };
9
+ read = (path: string): string | null => {
10
+ return fs.readFileSync(path, 'utf8');
11
+ };
12
+ listFiles = (dirPath: string): string[] => {
13
+ if (!fs.existsSync(dirPath)) {
14
+ return [];
15
+ }
16
+ return fs.readdirSync(dirPath);
17
+ };
18
+ mkdir = (dirPath: string) => {
19
+ fs.mkdirSync(dirPath, { recursive: true });
20
+ };
21
+ }
@@ -0,0 +1,20 @@
1
+ import { DateRepository } from '../../domain/usecases/adapter-interfaces/DateRepository';
2
+
3
+ export class SystemDateRepository implements DateRepository {
4
+ now = async () => new Date();
5
+ formatDurationToHHMM = (durationMinutes: number): string => {
6
+ const hours = Math.floor(durationMinutes / 60);
7
+ const minutes = Math.floor(durationMinutes % 60);
8
+ return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
9
+ };
10
+ formatDateTimeWithDayOfWeek = (date: Date): string => {
11
+ const dayOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][
12
+ date.getDay()
13
+ ];
14
+ const month = String(date.getMonth() + 1).padStart(2, '0');
15
+ const day = String(date.getDate()).padStart(2, '0');
16
+ const hours = String(date.getHours()).padStart(2, '0');
17
+ const minutes = String(date.getMinutes()).padStart(2, '0');
18
+ return `${date.getFullYear()}/${month}/${day} (${dayOfWeek}) ${hours}:${minutes}`;
19
+ };
20
+ }
@@ -0,0 +1,158 @@
1
+ import { mock } from 'jest-mock-extended';
2
+ import { ApiV3CheerioRestIssueRepository } from './ApiV3CheerioRestIssueRepository';
3
+ import { ApiV3IssueRepository } from './ApiV3IssueRepository';
4
+ import { CheerioIssueRepository } from './CheerioIssueRepository';
5
+ import { RestIssueRepository } from './RestIssueRepository';
6
+ import { GraphqlProjectItemRepository } from './GraphqlProjectItemRepository';
7
+ import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
8
+
9
+ describe('ApiV3CheerioRestIssueRepository', () => {
10
+ describe('convertProjectItemAndCheerioIssueToIssue', () => {
11
+ const testCases: {
12
+ name: string;
13
+ params: Parameters<
14
+ ApiV3CheerioRestIssueRepository['convertProjectItemAndCheerioIssueToIssue']
15
+ >;
16
+ expected: Awaited<
17
+ ReturnType<
18
+ ApiV3CheerioRestIssueRepository['convertProjectItemAndCheerioIssueToIssue']
19
+ >
20
+ >;
21
+ }[] = [
22
+ {
23
+ name: 'normal case',
24
+ params: [
25
+ {
26
+ id: 'test-id-1',
27
+ nameWithOwner: 'HiromiShikata/test-repository',
28
+ number: 38,
29
+ title: 'test-title',
30
+ state: 'OPEN',
31
+ url: 'https://github.com/HiromiShikata/test-repository/issues/38',
32
+ body: 'test-body',
33
+ customFields: [
34
+ { name: 'nextActionDate', value: '2000-01-01' },
35
+ { name: 'nextActionHour', value: '1' },
36
+ { name: 'remainingEstimationMinutes', value: '60' },
37
+ { name: 'story', value: 'test-story' },
38
+ { name: 'status', value: 'test-status' },
39
+ ],
40
+ },
41
+ {
42
+ url: 'https://github.com/HiromiShikata/test-repository/issues/38',
43
+ title: 'test-title',
44
+ status: 'test-status',
45
+ assignees: [],
46
+ labels: [],
47
+ project: '',
48
+ statusTimeline: [],
49
+ inProgressTimeline: [],
50
+ },
51
+ ],
52
+ expected: {
53
+ assignees: [],
54
+ body: 'test-body',
55
+ estimationMinutes: null,
56
+ isPr: false,
57
+ itemId: 'test-id-1',
58
+ labels: [],
59
+ nameWithOwner: 'HiromiShikata/test-repository',
60
+ nextActionDate: new Date('2000-01-01'),
61
+ nextActionHour: 1,
62
+ number: 38,
63
+ org: 'HiromiShikata',
64
+ repo: 'test-repository',
65
+ state: 'OPEN',
66
+ status: 'test-status',
67
+ story: 'test-story',
68
+ title: 'test-title',
69
+ url: 'https://github.com/HiromiShikata/test-repository/issues/38',
70
+ workingTimeline: [],
71
+ },
72
+ },
73
+ ];
74
+ test.each(testCases)('%s', async (arg) => {
75
+ const { repository } = createApiV3CheerioRestIssueRepository();
76
+ const result = await repository.convertProjectItemAndCheerioIssueToIssue(
77
+ ...arg.params,
78
+ );
79
+ expect(result).toEqual(arg.expected);
80
+ });
81
+ });
82
+ describe('getAllIssuesFromCache', () => {
83
+ const testCases: {
84
+ name: string;
85
+ params: Parameters<
86
+ ApiV3CheerioRestIssueRepository['getAllIssuesFromCache']
87
+ >;
88
+ expected: Awaited<
89
+ ReturnType<ApiV3CheerioRestIssueRepository['getAllIssuesFromCache']>
90
+ >;
91
+ }[] = [
92
+ {
93
+ name: 'normal case',
94
+ params: ['test-key', 1],
95
+ expected: null,
96
+ },
97
+ ];
98
+ test.each(testCases)('%s', async (arg) => {
99
+ const { repository, localStorageCacheRepository } =
100
+ createApiV3CheerioRestIssueRepository();
101
+ localStorageCacheRepository.getLatest.mockResolvedValue({
102
+ timestamp: new Date('2000-01-01'),
103
+ value: [],
104
+ });
105
+ const result = await repository.getAllIssuesFromCache(...arg.params);
106
+ expect(result).toEqual(arg.expected);
107
+ });
108
+ });
109
+ describe('getAllIssues', () => {
110
+ const testCases: {
111
+ name: string;
112
+ params: Parameters<ApiV3CheerioRestIssueRepository['getAllIssues']>;
113
+ expected: Awaited<
114
+ ReturnType<ApiV3CheerioRestIssueRepository['getAllIssues']>
115
+ >;
116
+ }[] = [
117
+ {
118
+ name: 'normal case',
119
+ params: ['test-project-id', 1],
120
+ expected: { issues: [], cacheUsed: false },
121
+ },
122
+ ];
123
+ test.each(testCases)('%s', async (arg) => {
124
+ const {
125
+ repository,
126
+ graphqlProjectItemRepository,
127
+ localStorageCacheRepository,
128
+ } = createApiV3CheerioRestIssueRepository();
129
+ graphqlProjectItemRepository.fetchProjectItems.mockResolvedValue([]);
130
+ localStorageCacheRepository.set.mockResolvedValue();
131
+ const result = await repository.getAllIssues(...arg.params);
132
+ expect(result).toEqual(arg.expected);
133
+ });
134
+ });
135
+
136
+ const createApiV3CheerioRestIssueRepository = () => {
137
+ const apiV3IssueRepository = mock<ApiV3IssueRepository>();
138
+ const cheerioIssueRepository = mock<CheerioIssueRepository>();
139
+ const restIssueRepository = mock<RestIssueRepository>();
140
+ const graphqlProjectItemRepository = mock<GraphqlProjectItemRepository>();
141
+ const localStorageCacheRepository = mock<LocalStorageCacheRepository>();
142
+ const repository = new ApiV3CheerioRestIssueRepository(
143
+ apiV3IssueRepository,
144
+ cheerioIssueRepository,
145
+ restIssueRepository,
146
+ graphqlProjectItemRepository,
147
+ localStorageCacheRepository,
148
+ );
149
+ return {
150
+ repository,
151
+ apiV3IssueRepository,
152
+ cheerioIssueRepository,
153
+ restIssueRepository,
154
+ graphqlProjectItemRepository,
155
+ localStorageCacheRepository,
156
+ };
157
+ };
158
+ });