@sk8metal/michi-cli 0.5.0 → 0.8.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 (284) hide show
  1. package/CHANGELOG.md +136 -1
  2. package/README.md +2 -1
  3. package/dist/scripts/config/config-schema.d.ts +35 -0
  4. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  5. package/dist/scripts/config/config-schema.js +56 -0
  6. package/dist/scripts/config/config-schema.js.map +1 -1
  7. package/dist/scripts/confluence-sync.d.ts.map +1 -1
  8. package/dist/scripts/confluence-sync.js +15 -2
  9. package/dist/scripts/confluence-sync.js.map +1 -1
  10. package/dist/scripts/github-actions-client.d.ts +79 -0
  11. package/dist/scripts/github-actions-client.d.ts.map +1 -0
  12. package/dist/scripts/github-actions-client.js +182 -0
  13. package/dist/scripts/github-actions-client.js.map +1 -0
  14. package/dist/scripts/health-check-service.d.ts +46 -0
  15. package/dist/scripts/health-check-service.d.ts.map +1 -0
  16. package/dist/scripts/health-check-service.js +114 -0
  17. package/dist/scripts/health-check-service.js.map +1 -0
  18. package/dist/scripts/markdown-to-confluence.d.ts.map +1 -1
  19. package/dist/scripts/markdown-to-confluence.js +25 -3
  20. package/dist/scripts/markdown-to-confluence.js.map +1 -1
  21. package/dist/scripts/mermaid-converter.d.ts +24 -0
  22. package/dist/scripts/mermaid-converter.d.ts.map +1 -0
  23. package/dist/scripts/mermaid-converter.js +49 -0
  24. package/dist/scripts/mermaid-converter.js.map +1 -0
  25. package/dist/scripts/template/multi-repo-renderer.d.ts +67 -0
  26. package/dist/scripts/template/multi-repo-renderer.d.ts.map +1 -0
  27. package/dist/scripts/template/multi-repo-renderer.js +123 -0
  28. package/dist/scripts/template/multi-repo-renderer.js.map +1 -0
  29. package/dist/scripts/template/renderer.d.ts +4 -0
  30. package/dist/scripts/template/renderer.d.ts.map +1 -1
  31. package/dist/scripts/template/renderer.js.map +1 -1
  32. package/dist/scripts/test-execution-generator.d.ts.map +1 -1
  33. package/dist/scripts/test-execution-generator.js +94 -11
  34. package/dist/scripts/test-execution-generator.js.map +1 -1
  35. package/dist/scripts/test-script-runner.d.ts +33 -0
  36. package/dist/scripts/test-script-runner.d.ts.map +1 -0
  37. package/dist/scripts/test-script-runner.js +77 -0
  38. package/dist/scripts/test-script-runner.js.map +1 -0
  39. package/dist/scripts/utils/config-loader.d.ts +21 -1
  40. package/dist/scripts/utils/config-loader.d.ts.map +1 -1
  41. package/dist/scripts/utils/config-loader.js +149 -3
  42. package/dist/scripts/utils/config-loader.js.map +1 -1
  43. package/dist/scripts/utils/multi-repo-validator.d.ts +30 -0
  44. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -0
  45. package/dist/scripts/utils/multi-repo-validator.js +105 -0
  46. package/dist/scripts/utils/multi-repo-validator.js.map +1 -0
  47. package/dist/scripts/utils/spec-archiver.d.ts +38 -0
  48. package/dist/scripts/utils/spec-archiver.d.ts.map +1 -0
  49. package/dist/scripts/utils/spec-archiver.js +210 -0
  50. package/dist/scripts/utils/spec-archiver.js.map +1 -0
  51. package/dist/scripts/utils/spec-updater.d.ts +4 -0
  52. package/dist/scripts/utils/spec-updater.d.ts.map +1 -1
  53. package/dist/scripts/utils/spec-updater.js.map +1 -1
  54. package/dist/src/cli.d.ts.map +1 -1
  55. package/dist/src/cli.js +262 -14
  56. package/dist/src/cli.js.map +1 -1
  57. package/dist/src/commands/multi-repo-add-repo.d.ts +26 -0
  58. package/dist/src/commands/multi-repo-add-repo.d.ts.map +1 -0
  59. package/dist/src/commands/multi-repo-add-repo.js +56 -0
  60. package/dist/src/commands/multi-repo-add-repo.js.map +1 -0
  61. package/dist/src/commands/multi-repo-ci-status.d.ts +46 -0
  62. package/dist/src/commands/multi-repo-ci-status.d.ts.map +1 -0
  63. package/dist/src/commands/multi-repo-ci-status.js +285 -0
  64. package/dist/src/commands/multi-repo-ci-status.js.map +1 -0
  65. package/dist/src/commands/multi-repo-confluence-sync.d.ts +45 -0
  66. package/dist/src/commands/multi-repo-confluence-sync.d.ts.map +1 -0
  67. package/dist/src/commands/multi-repo-confluence-sync.js +135 -0
  68. package/dist/src/commands/multi-repo-confluence-sync.js.map +1 -0
  69. package/dist/src/commands/multi-repo-init.d.ts +26 -0
  70. package/dist/src/commands/multi-repo-init.d.ts.map +1 -0
  71. package/dist/src/commands/multi-repo-init.js +101 -0
  72. package/dist/src/commands/multi-repo-init.js.map +1 -0
  73. package/dist/src/commands/multi-repo-list.d.ts +28 -0
  74. package/dist/src/commands/multi-repo-list.d.ts.map +1 -0
  75. package/dist/src/commands/multi-repo-list.js +38 -0
  76. package/dist/src/commands/multi-repo-list.js.map +1 -0
  77. package/dist/src/commands/multi-repo-test.d.ts +56 -0
  78. package/dist/src/commands/multi-repo-test.d.ts.map +1 -0
  79. package/dist/src/commands/multi-repo-test.js +70 -0
  80. package/dist/src/commands/multi-repo-test.js.map +1 -0
  81. package/dist/src/commands/setup-existing.d.ts.map +1 -1
  82. package/dist/src/commands/setup-existing.js +30 -8
  83. package/dist/src/commands/setup-existing.js.map +1 -1
  84. package/dist/src/commands/spec-archive.d.ts +17 -0
  85. package/dist/src/commands/spec-archive.d.ts.map +1 -0
  86. package/dist/src/commands/spec-archive.js +40 -0
  87. package/dist/src/commands/spec-archive.js.map +1 -0
  88. package/dist/src/commands/spec-list.d.ts +15 -0
  89. package/dist/src/commands/spec-list.d.ts.map +1 -0
  90. package/dist/src/commands/spec-list.js +55 -0
  91. package/dist/src/commands/spec-list.js.map +1 -0
  92. package/docs/user-guide/guides/multi-repo-guide.md +591 -0
  93. package/docs/user-guide/guides/multi-repo-migration-guide.md +516 -0
  94. package/docs/user-guide/reference/multi-repo-api.md +771 -0
  95. package/docs/user-guide/reference/quick-reference.md +22 -37
  96. package/package.json +1 -4
  97. package/scripts/__tests__/config-loader-multi-repo.test.ts +342 -0
  98. package/scripts/__tests__/github-actions-client.test.ts +543 -0
  99. package/scripts/__tests__/health-check-service.test.ts +142 -0
  100. package/scripts/__tests__/markdown-to-confluence.test.ts +262 -0
  101. package/scripts/__tests__/mermaid-converter.test.ts +236 -0
  102. package/scripts/__tests__/multi-repo-config-schema.test.ts +335 -0
  103. package/scripts/__tests__/multi-repo-validator.test.ts +524 -0
  104. package/scripts/__tests__/spec-archiver.test.ts +512 -0
  105. package/scripts/__tests__/test-script-runner.test.ts +217 -0
  106. package/scripts/config/config-schema.ts +64 -0
  107. package/scripts/confluence-sync.ts +16 -2
  108. package/scripts/github-actions-client.ts +258 -0
  109. package/scripts/health-check-service.ts +171 -0
  110. package/scripts/markdown-to-confluence.ts +37 -6
  111. package/scripts/mermaid-converter.ts +56 -0
  112. package/scripts/template/__tests__/multi-repo-renderer.test.ts +261 -0
  113. package/scripts/template/multi-repo-renderer.ts +172 -0
  114. package/scripts/template/renderer.ts +5 -0
  115. package/scripts/test-execution-generator.ts +104 -11
  116. package/scripts/test-script-runner.ts +130 -0
  117. package/scripts/utils/__tests__/config-validator.test.ts +104 -6
  118. package/scripts/utils/__tests__/multi-repo-validator.test.ts +335 -0
  119. package/scripts/utils/config-loader.ts +197 -3
  120. package/scripts/utils/multi-repo-validator.ts +141 -0
  121. package/scripts/utils/spec-archiver.ts +260 -0
  122. package/scripts/utils/spec-updater.ts +4 -0
  123. package/templates/claude/agents/pr-size-monitor/AGENT.md +330 -0
  124. package/templates/claude/commands/michi/spec-impl.md +208 -35
  125. package/templates/claude-agent/agents/doc-reviewer.md +176 -0
  126. package/templates/claude-agent/rules/doc-review-rules.md +98 -0
  127. package/templates/claude-agent/rules/doc-review.md +91 -0
  128. package/templates/multi-repo/docs/ci-status.md +51 -0
  129. package/templates/multi-repo/docs/release-notes.md +99 -0
  130. package/templates/multi-repo/overview/architecture.md +102 -0
  131. package/templates/multi-repo/overview/requirements.md +68 -0
  132. package/templates/multi-repo/overview/sequence.md +79 -0
  133. package/templates/multi-repo/steering/multi-repo.md +74 -0
  134. package/templates/multi-repo/tests/strategy.md +89 -0
  135. package/dist/scripts/__tests__/create-project.test.d.ts +0 -2
  136. package/dist/scripts/__tests__/create-project.test.d.ts.map +0 -1
  137. package/dist/scripts/__tests__/create-project.test.js +0 -243
  138. package/dist/scripts/__tests__/create-project.test.js.map +0 -1
  139. package/dist/scripts/__tests__/jira-transitions.test.d.ts +0 -5
  140. package/dist/scripts/__tests__/jira-transitions.test.d.ts.map +0 -1
  141. package/dist/scripts/__tests__/jira-transitions.test.js +0 -172
  142. package/dist/scripts/__tests__/jira-transitions.test.js.map +0 -1
  143. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts +0 -2
  144. package/dist/scripts/__tests__/multi-project-estimate.test.d.ts.map +0 -1
  145. package/dist/scripts/__tests__/multi-project-estimate.test.js +0 -118
  146. package/dist/scripts/__tests__/multi-project-estimate.test.js.map +0 -1
  147. package/dist/scripts/__tests__/setup-existing-project.test.d.ts +0 -2
  148. package/dist/scripts/__tests__/setup-existing-project.test.d.ts.map +0 -1
  149. package/dist/scripts/__tests__/setup-existing-project.test.js +0 -208
  150. package/dist/scripts/__tests__/setup-existing-project.test.js.map +0 -1
  151. package/dist/scripts/__tests__/setup-interactive.test.d.ts +0 -2
  152. package/dist/scripts/__tests__/setup-interactive.test.d.ts.map +0 -1
  153. package/dist/scripts/__tests__/setup-interactive.test.js +0 -166
  154. package/dist/scripts/__tests__/setup-interactive.test.js.map +0 -1
  155. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts +0 -5
  156. package/dist/scripts/__tests__/spec-impl-workflow.test.d.ts.map +0 -1
  157. package/dist/scripts/__tests__/spec-impl-workflow.test.js +0 -323
  158. package/dist/scripts/__tests__/spec-impl-workflow.test.js.map +0 -1
  159. package/dist/scripts/__tests__/spec-loader.test.d.ts +0 -5
  160. package/dist/scripts/__tests__/spec-loader.test.d.ts.map +0 -1
  161. package/dist/scripts/__tests__/spec-loader.test.js +0 -153
  162. package/dist/scripts/__tests__/spec-loader.test.js.map +0 -1
  163. package/dist/scripts/__tests__/validate-phase.test.d.ts +0 -5
  164. package/dist/scripts/__tests__/validate-phase.test.d.ts.map +0 -1
  165. package/dist/scripts/__tests__/validate-phase.test.js +0 -249
  166. package/dist/scripts/__tests__/validate-phase.test.js.map +0 -1
  167. package/dist/scripts/constants/__tests__/environments.test.d.ts +0 -2
  168. package/dist/scripts/constants/__tests__/environments.test.d.ts.map +0 -1
  169. package/dist/scripts/constants/__tests__/environments.test.js +0 -125
  170. package/dist/scripts/constants/__tests__/environments.test.js.map +0 -1
  171. package/dist/scripts/constants/__tests__/languages.test.d.ts +0 -2
  172. package/dist/scripts/constants/__tests__/languages.test.d.ts.map +0 -1
  173. package/dist/scripts/constants/__tests__/languages.test.js +0 -82
  174. package/dist/scripts/constants/__tests__/languages.test.js.map +0 -1
  175. package/dist/scripts/create-project.d.ts +0 -16
  176. package/dist/scripts/create-project.d.ts.map +0 -1
  177. package/dist/scripts/create-project.js +0 -334
  178. package/dist/scripts/create-project.js.map +0 -1
  179. package/dist/scripts/list-projects.d.ts +0 -7
  180. package/dist/scripts/list-projects.d.ts.map +0 -1
  181. package/dist/scripts/list-projects.js +0 -88
  182. package/dist/scripts/list-projects.js.map +0 -1
  183. package/dist/scripts/template/__tests__/renderer.test.d.ts +0 -2
  184. package/dist/scripts/template/__tests__/renderer.test.d.ts.map +0 -1
  185. package/dist/scripts/template/__tests__/renderer.test.js +0 -165
  186. package/dist/scripts/template/__tests__/renderer.test.js.map +0 -1
  187. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts +0 -5
  188. package/dist/scripts/utils/__tests__/aidlc-parser.test.d.ts.map +0 -1
  189. package/dist/scripts/utils/__tests__/aidlc-parser.test.js +0 -315
  190. package/dist/scripts/utils/__tests__/aidlc-parser.test.js.map +0 -1
  191. package/dist/scripts/utils/__tests__/business-days.test.d.ts +0 -5
  192. package/dist/scripts/utils/__tests__/business-days.test.d.ts.map +0 -1
  193. package/dist/scripts/utils/__tests__/business-days.test.js +0 -171
  194. package/dist/scripts/utils/__tests__/business-days.test.js.map +0 -1
  195. package/dist/scripts/utils/__tests__/config-loader.test.d.ts +0 -5
  196. package/dist/scripts/utils/__tests__/config-loader.test.d.ts.map +0 -1
  197. package/dist/scripts/utils/__tests__/config-loader.test.js +0 -314
  198. package/dist/scripts/utils/__tests__/config-loader.test.js.map +0 -1
  199. package/dist/scripts/utils/__tests__/config-validator.test.d.ts +0 -5
  200. package/dist/scripts/utils/__tests__/config-validator.test.d.ts.map +0 -1
  201. package/dist/scripts/utils/__tests__/config-validator.test.js +0 -396
  202. package/dist/scripts/utils/__tests__/config-validator.test.js.map +0 -1
  203. package/dist/scripts/utils/__tests__/env-config.test.d.ts +0 -5
  204. package/dist/scripts/utils/__tests__/env-config.test.d.ts.map +0 -1
  205. package/dist/scripts/utils/__tests__/env-config.test.js +0 -216
  206. package/dist/scripts/utils/__tests__/env-config.test.js.map +0 -1
  207. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts +0 -5
  208. package/dist/scripts/utils/__tests__/feature-name-validator.test.d.ts.map +0 -1
  209. package/dist/scripts/utils/__tests__/feature-name-validator.test.js +0 -106
  210. package/dist/scripts/utils/__tests__/feature-name-validator.test.js.map +0 -1
  211. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts +0 -5
  212. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.d.ts.map +0 -1
  213. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js +0 -202
  214. package/dist/scripts/utils/__tests__/jira-issue-type-fetcher.test.js.map +0 -1
  215. package/dist/scripts/utils/__tests__/project-meta.test.d.ts +0 -6
  216. package/dist/scripts/utils/__tests__/project-meta.test.d.ts.map +0 -1
  217. package/dist/scripts/utils/__tests__/project-meta.test.js +0 -154
  218. package/dist/scripts/utils/__tests__/project-meta.test.js.map +0 -1
  219. package/dist/scripts/utils/__tests__/security-validator.test.d.ts +0 -6
  220. package/dist/scripts/utils/__tests__/security-validator.test.d.ts.map +0 -1
  221. package/dist/scripts/utils/__tests__/security-validator.test.js +0 -219
  222. package/dist/scripts/utils/__tests__/security-validator.test.js.map +0 -1
  223. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts +0 -5
  224. package/dist/scripts/utils/__tests__/spec-updater.test.d.ts.map +0 -1
  225. package/dist/scripts/utils/__tests__/spec-updater.test.js +0 -158
  226. package/dist/scripts/utils/__tests__/spec-updater.test.js.map +0 -1
  227. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts +0 -5
  228. package/dist/scripts/utils/__tests__/tasks-converter.test.d.ts.map +0 -1
  229. package/dist/scripts/utils/__tests__/tasks-converter.test.js +0 -500
  230. package/dist/scripts/utils/__tests__/tasks-converter.test.js.map +0 -1
  231. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts +0 -5
  232. package/dist/scripts/utils/__tests__/tasks-format-validator.test.d.ts.map +0 -1
  233. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js +0 -314
  234. package/dist/scripts/utils/__tests__/tasks-format-validator.test.js.map +0 -1
  235. package/dist/scripts/utils/__tests__/test-runner.test.d.ts +0 -5
  236. package/dist/scripts/utils/__tests__/test-runner.test.d.ts.map +0 -1
  237. package/dist/scripts/utils/__tests__/test-runner.test.js +0 -64
  238. package/dist/scripts/utils/__tests__/test-runner.test.js.map +0 -1
  239. package/dist/src/__tests__/cli.test.d.ts +0 -5
  240. package/dist/src/__tests__/cli.test.d.ts.map +0 -1
  241. package/dist/src/__tests__/cli.test.js +0 -58
  242. package/dist/src/__tests__/cli.test.js.map +0 -1
  243. package/dist/src/__tests__/integration/internationalization.test.d.ts +0 -8
  244. package/dist/src/__tests__/integration/internationalization.test.d.ts.map +0 -1
  245. package/dist/src/__tests__/integration/internationalization.test.js +0 -333
  246. package/dist/src/__tests__/integration/internationalization.test.js.map +0 -1
  247. package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts +0 -5
  248. package/dist/src/__tests__/integration/setup/claude-agent.test.d.ts.map +0 -1
  249. package/dist/src/__tests__/integration/setup/claude-agent.test.js +0 -122
  250. package/dist/src/__tests__/integration/setup/claude-agent.test.js.map +0 -1
  251. package/dist/src/__tests__/integration/setup/claude.test.d.ts +0 -5
  252. package/dist/src/__tests__/integration/setup/claude.test.d.ts.map +0 -1
  253. package/dist/src/__tests__/integration/setup/claude.test.js +0 -193
  254. package/dist/src/__tests__/integration/setup/claude.test.js.map +0 -1
  255. package/dist/src/__tests__/integration/setup/cursor.test.d.ts +0 -5
  256. package/dist/src/__tests__/integration/setup/cursor.test.d.ts.map +0 -1
  257. package/dist/src/__tests__/integration/setup/cursor.test.js +0 -166
  258. package/dist/src/__tests__/integration/setup/cursor.test.js.map +0 -1
  259. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts +0 -32
  260. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.d.ts.map +0 -1
  261. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js +0 -72
  262. package/dist/src/__tests__/integration/setup/helpers/fs-assertions.js.map +0 -1
  263. package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts +0 -38
  264. package/dist/src/__tests__/integration/setup/helpers/test-project.d.ts.map +0 -1
  265. package/dist/src/__tests__/integration/setup/helpers/test-project.js +0 -83
  266. package/dist/src/__tests__/integration/setup/helpers/test-project.js.map +0 -1
  267. package/dist/src/__tests__/integration/setup/init.test.d.ts +0 -5
  268. package/dist/src/__tests__/integration/setup/init.test.d.ts.map +0 -1
  269. package/dist/src/__tests__/integration/setup/init.test.js +0 -352
  270. package/dist/src/__tests__/integration/setup/init.test.js.map +0 -1
  271. package/dist/src/__tests__/integration/setup/validation.test.d.ts +0 -5
  272. package/dist/src/__tests__/integration/setup/validation.test.d.ts.map +0 -1
  273. package/dist/src/__tests__/integration/setup/validation.test.js +0 -301
  274. package/dist/src/__tests__/integration/setup/validation.test.js.map +0 -1
  275. package/dist/src/commands/__tests__/init.test.d.ts +0 -5
  276. package/dist/src/commands/__tests__/init.test.d.ts.map +0 -1
  277. package/dist/src/commands/__tests__/init.test.js +0 -255
  278. package/dist/src/commands/__tests__/init.test.js.map +0 -1
  279. package/dist/src/commands/__tests__/migrate.test.d.ts +0 -5
  280. package/dist/src/commands/__tests__/migrate.test.d.ts.map +0 -1
  281. package/dist/src/commands/__tests__/migrate.test.js +0 -216
  282. package/dist/src/commands/__tests__/migrate.test.js.map +0 -1
  283. package/scripts/create-project.ts +0 -386
  284. package/scripts/list-projects.ts +0 -112
@@ -0,0 +1,543 @@
1
+ /**
2
+ * Tests for GitHub Actions Client
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import { Octokit } from '@octokit/rest';
7
+
8
+ // GitHubActionsClientの型定義(実装前)
9
+ interface IGitHubWorkflowRun {
10
+ id: number;
11
+ name: string;
12
+ head_branch: string;
13
+ status: 'completed' | 'in_progress' | 'queued';
14
+ conclusion: 'success' | 'failure' | 'cancelled' | 'skipped' | null;
15
+ created_at: string;
16
+ updated_at: string;
17
+ html_url: string;
18
+ }
19
+
20
+ // モック用
21
+ vi.mock('@octokit/rest', () => ({
22
+ Octokit: vi.fn(),
23
+ }));
24
+
25
+ describe('GitHubActionsClient', () => {
26
+ let originalEnv: NodeJS.ProcessEnv;
27
+
28
+ beforeEach(() => {
29
+ vi.clearAllMocks();
30
+ originalEnv = { ...process.env };
31
+ });
32
+
33
+ afterEach(() => {
34
+ vi.restoreAllMocks();
35
+ process.env = originalEnv;
36
+ });
37
+
38
+ describe('初期化', () => {
39
+ it('GITHUB_TOKENが設定されている場合、正常に初期化される', async () => {
40
+ process.env.GITHUB_TOKEN = 'test-token';
41
+
42
+ const { GitHubActionsClient } = await import(
43
+ '../github-actions-client.js'
44
+ );
45
+ const client = new GitHubActionsClient();
46
+
47
+ expect(client).toBeDefined();
48
+ });
49
+
50
+ it('GITHUB_TOKENが未設定の場合、エラーをスロー', async () => {
51
+ delete process.env.GITHUB_TOKEN;
52
+
53
+ const { GitHubActionsClient } = await import(
54
+ '../github-actions-client.js'
55
+ );
56
+
57
+ expect(() => new GitHubActionsClient()).toThrow(
58
+ 'GITHUB_TOKENが設定されていません'
59
+ );
60
+ });
61
+ });
62
+
63
+ describe('getLatestWorkflowRun', () => {
64
+ beforeEach(() => {
65
+ process.env.GITHUB_TOKEN = 'test-token';
66
+ });
67
+
68
+ it('正常ケース: 最新のWorkflow Runを取得', async () => {
69
+ const mockRun: IGitHubWorkflowRun = {
70
+ id: 123,
71
+ name: 'CI',
72
+ head_branch: 'main',
73
+ status: 'completed',
74
+ conclusion: 'success',
75
+ created_at: '2025-12-15T10:00:00Z',
76
+ updated_at: '2025-12-15T10:05:00Z',
77
+ html_url: 'https://github.com/owner/repo/actions/runs/123',
78
+ };
79
+
80
+ const mockListWorkflowRunsForRepo = vi
81
+ .fn()
82
+ .mockResolvedValue({
83
+ data: {
84
+ workflow_runs: [mockRun],
85
+ },
86
+ });
87
+
88
+ vi.mocked(Octokit).mockImplementation(function () {
89
+ return {
90
+ actions: {
91
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
92
+ },
93
+ } as unknown as InstanceType<typeof Octokit>;
94
+ });
95
+
96
+ const { GitHubActionsClient } = await import(
97
+ '../github-actions-client.js'
98
+ );
99
+ const client = new GitHubActionsClient();
100
+
101
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
102
+
103
+ expect(result.success).toBe(true);
104
+ if (result.success) {
105
+ expect(result.data.id).toBe(123);
106
+ expect(result.data.conclusion).toBe('success');
107
+ }
108
+ });
109
+
110
+ it('404 Not Found: リポジトリが存在しない', async () => {
111
+ const mockListWorkflowRunsForRepo = vi.fn().mockRejectedValue({
112
+ status: 404,
113
+ message: 'Not Found',
114
+ });
115
+
116
+ vi.mocked(Octokit).mockImplementation(function () {
117
+ return {
118
+ actions: {
119
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
120
+ },
121
+ } as unknown as InstanceType<typeof Octokit>;
122
+ });
123
+
124
+ const { GitHubActionsClient } = await import(
125
+ '../github-actions-client.js'
126
+ );
127
+ const client = new GitHubActionsClient();
128
+
129
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
130
+
131
+ expect(result.success).toBe(false);
132
+ if (!result.success) {
133
+ expect(result.error.type).toBe('NOT_FOUND');
134
+ }
135
+ });
136
+
137
+ it('403 Rate Limit: レート制限超過', async () => {
138
+ const mockListWorkflowRunsForRepo = vi.fn().mockRejectedValue({
139
+ status: 403,
140
+ message: 'Rate limit exceeded',
141
+ response: {
142
+ headers: {
143
+ 'x-ratelimit-reset': String(
144
+ Math.floor(Date.now() / 1000) + 3600
145
+ ),
146
+ },
147
+ },
148
+ });
149
+
150
+ vi.mocked(Octokit).mockImplementation(function () {
151
+ return {
152
+ actions: {
153
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
154
+ },
155
+ } as unknown as InstanceType<typeof Octokit>;
156
+ });
157
+
158
+ const { GitHubActionsClient } = await import(
159
+ '../github-actions-client.js'
160
+ );
161
+ const client = new GitHubActionsClient();
162
+
163
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
164
+
165
+ expect(result.success).toBe(false);
166
+ if (!result.success) {
167
+ expect(result.error.type).toBe('RATE_LIMIT_EXCEEDED');
168
+ if (result.error.type === 'RATE_LIMIT_EXCEEDED') {
169
+ expect(result.error.retryAfter).toBeGreaterThan(0);
170
+ }
171
+ }
172
+ });
173
+
174
+ it('401 Unauthorized: 認証エラー', async () => {
175
+ const mockListWorkflowRunsForRepo = vi.fn().mockRejectedValue({
176
+ status: 401,
177
+ message: 'Unauthorized',
178
+ });
179
+
180
+ vi.mocked(Octokit).mockImplementation(function () {
181
+ return {
182
+ actions: {
183
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
184
+ },
185
+ } as unknown as InstanceType<typeof Octokit>;
186
+ });
187
+
188
+ const { GitHubActionsClient } = await import(
189
+ '../github-actions-client.js'
190
+ );
191
+ const client = new GitHubActionsClient();
192
+
193
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
194
+
195
+ expect(result.success).toBe(false);
196
+ if (!result.success) {
197
+ expect(result.error.type).toBe('UNAUTHORIZED');
198
+ }
199
+ });
200
+
201
+ it('500 Server Error: サーバーエラー', async () => {
202
+ const mockListWorkflowRunsForRepo = vi.fn().mockRejectedValue({
203
+ status: 500,
204
+ message: 'Internal Server Error',
205
+ });
206
+
207
+ vi.mocked(Octokit).mockImplementation(function () {
208
+ return {
209
+ actions: {
210
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
211
+ },
212
+ } as unknown as InstanceType<typeof Octokit>;
213
+ });
214
+
215
+ const { GitHubActionsClient } = await import(
216
+ '../github-actions-client.js'
217
+ );
218
+ const client = new GitHubActionsClient();
219
+
220
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
221
+
222
+ expect(result.success).toBe(false);
223
+ if (!result.success) {
224
+ expect(result.error.type).toBe('SERVER_ERROR');
225
+ if (result.error.type === 'SERVER_ERROR') {
226
+ expect(result.error.statusCode).toBe(500);
227
+ }
228
+ }
229
+ });
230
+
231
+ it('ブランチ指定でフィルタリング', async () => {
232
+ const mockRun: IGitHubWorkflowRun = {
233
+ id: 456,
234
+ name: 'CI',
235
+ head_branch: 'develop',
236
+ status: 'completed',
237
+ conclusion: 'failure',
238
+ created_at: '2025-12-15T11:00:00Z',
239
+ updated_at: '2025-12-15T11:05:00Z',
240
+ html_url: 'https://github.com/owner/repo/actions/runs/456',
241
+ };
242
+
243
+ const mockListWorkflowRunsForRepo = vi
244
+ .fn()
245
+ .mockResolvedValue({
246
+ data: {
247
+ workflow_runs: [mockRun],
248
+ },
249
+ });
250
+
251
+ vi.mocked(Octokit).mockImplementation(function () {
252
+ return {
253
+ actions: {
254
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
255
+ },
256
+ } as unknown as InstanceType<typeof Octokit>;
257
+ });
258
+
259
+ const { GitHubActionsClient } = await import(
260
+ '../github-actions-client.js'
261
+ );
262
+ const client = new GitHubActionsClient();
263
+
264
+ await client.getLatestWorkflowRun('owner', 'repo', 'develop');
265
+
266
+ expect(mockListWorkflowRunsForRepo).toHaveBeenCalledWith(
267
+ expect.objectContaining({
268
+ owner: 'owner',
269
+ repo: 'repo',
270
+ branch: 'develop',
271
+ status: 'completed',
272
+ per_page: 1,
273
+ })
274
+ );
275
+ });
276
+ });
277
+
278
+ describe('exponentialBackoff (Task 6.2)', () => {
279
+ beforeEach(() => {
280
+ process.env.GITHUB_TOKEN = 'test-token';
281
+ vi.useFakeTimers();
282
+ });
283
+
284
+ afterEach(() => {
285
+ vi.useRealTimers();
286
+ });
287
+
288
+ it('再試行ロジック: 1秒、2秒、4秒の待機', async () => {
289
+ let attemptCount = 0;
290
+ const mockListWorkflowRunsForRepo = vi.fn().mockImplementation(() => {
291
+ attemptCount++;
292
+ if (attemptCount < 3) {
293
+ return Promise.reject({
294
+ status: 403,
295
+ message: 'Rate limit exceeded',
296
+ response: {
297
+ headers: {
298
+ 'x-ratelimit-reset': String(
299
+ Math.floor(Date.now() / 1000) + 3600
300
+ ),
301
+ },
302
+ },
303
+ });
304
+ }
305
+ return Promise.resolve({
306
+ data: {
307
+ workflow_runs: [
308
+ {
309
+ id: 789,
310
+ name: 'CI',
311
+ head_branch: 'main',
312
+ status: 'completed',
313
+ conclusion: 'success',
314
+ created_at: '2025-12-15T12:00:00Z',
315
+ updated_at: '2025-12-15T12:05:00Z',
316
+ html_url: 'https://github.com/owner/repo/actions/runs/789',
317
+ },
318
+ ],
319
+ },
320
+ });
321
+ });
322
+
323
+ vi.mocked(Octokit).mockImplementation(function () {
324
+ return {
325
+ actions: {
326
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
327
+ },
328
+ } as unknown as InstanceType<typeof Octokit>;
329
+ });
330
+
331
+ const { GitHubActionsClient } = await import(
332
+ '../github-actions-client.js'
333
+ );
334
+ const client = new GitHubActionsClient();
335
+
336
+ const resultPromise = client.getLatestWorkflowRun('owner', 'repo', 'main');
337
+
338
+ // 1回目の失敗後、1秒待機
339
+ await vi.advanceTimersByTimeAsync(1000);
340
+ // 2回目の失敗後、2秒待機
341
+ await vi.advanceTimersByTimeAsync(2000);
342
+ // 3回目は成功
343
+
344
+ const result = await resultPromise;
345
+
346
+ expect(result.success).toBe(true);
347
+ expect(attemptCount).toBe(3);
348
+ });
349
+
350
+ it('最大再試行回数: 3回', async () => {
351
+ const mockListWorkflowRunsForRepo = vi.fn().mockRejectedValue({
352
+ status: 403,
353
+ message: 'Rate limit exceeded',
354
+ response: {
355
+ headers: {
356
+ 'x-ratelimit-reset': String(
357
+ Math.floor(Date.now() / 1000) + 3600
358
+ ),
359
+ },
360
+ },
361
+ });
362
+
363
+ vi.mocked(Octokit).mockImplementation(function () {
364
+ return {
365
+ actions: {
366
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
367
+ },
368
+ } as unknown as InstanceType<typeof Octokit>;
369
+ });
370
+
371
+ const { GitHubActionsClient } = await import(
372
+ '../github-actions-client.js'
373
+ );
374
+ const client = new GitHubActionsClient();
375
+
376
+ const resultPromise = client.getLatestWorkflowRun('owner', 'repo', 'main');
377
+
378
+ // 3回すべて失敗
379
+ await vi.advanceTimersByTimeAsync(1000);
380
+ await vi.advanceTimersByTimeAsync(2000);
381
+ await vi.advanceTimersByTimeAsync(4000);
382
+
383
+ const result = await resultPromise;
384
+
385
+ expect(result.success).toBe(false);
386
+ expect(mockListWorkflowRunsForRepo).toHaveBeenCalledTimes(3);
387
+ });
388
+
389
+ it('成功時の早期リターン', async () => {
390
+ const mockRun: IGitHubWorkflowRun = {
391
+ id: 999,
392
+ name: 'CI',
393
+ head_branch: 'main',
394
+ status: 'completed',
395
+ conclusion: 'success',
396
+ created_at: '2025-12-15T13:00:00Z',
397
+ updated_at: '2025-12-15T13:05:00Z',
398
+ html_url: 'https://github.com/owner/repo/actions/runs/999',
399
+ };
400
+
401
+ const mockListWorkflowRunsForRepo = vi
402
+ .fn()
403
+ .mockResolvedValue({
404
+ data: {
405
+ workflow_runs: [mockRun],
406
+ },
407
+ });
408
+
409
+ vi.mocked(Octokit).mockImplementation(function () {
410
+ return {
411
+ actions: {
412
+ listWorkflowRunsForRepo: mockListWorkflowRunsForRepo,
413
+ },
414
+ } as unknown as InstanceType<typeof Octokit>;
415
+ });
416
+
417
+ const { GitHubActionsClient } = await import(
418
+ '../github-actions-client.js'
419
+ );
420
+ const client = new GitHubActionsClient();
421
+
422
+ const result = await client.getLatestWorkflowRun('owner', 'repo', 'main');
423
+
424
+ expect(result.success).toBe(true);
425
+ expect(mockListWorkflowRunsForRepo).toHaveBeenCalledTimes(1);
426
+ });
427
+ });
428
+
429
+ describe('parseGitHubWorkflowRun (Task 6.3)', () => {
430
+ it('status/conclusionマッピング: completed + success → success', async () => {
431
+ const { parseGitHubWorkflowRun } = await import(
432
+ '../github-actions-client.js'
433
+ );
434
+
435
+ const run: IGitHubWorkflowRun = {
436
+ id: 1,
437
+ name: 'CI',
438
+ head_branch: 'main',
439
+ status: 'completed',
440
+ conclusion: 'success',
441
+ created_at: '2025-12-15T10:00:00Z',
442
+ updated_at: '2025-12-15T10:05:00Z',
443
+ html_url: 'https://github.com/owner/repo/actions/runs/1',
444
+ };
445
+
446
+ const result = parseGitHubWorkflowRun(run);
447
+
448
+ expect(result.status).toBe('success');
449
+ expect(result.testStatus).toBe('passed');
450
+ });
451
+
452
+ it('status/conclusionマッピング: completed + failure → failure', async () => {
453
+ const { parseGitHubWorkflowRun } = await import(
454
+ '../github-actions-client.js'
455
+ );
456
+
457
+ const run: IGitHubWorkflowRun = {
458
+ id: 2,
459
+ name: 'CI',
460
+ head_branch: 'main',
461
+ status: 'completed',
462
+ conclusion: 'failure',
463
+ created_at: '2025-12-15T10:00:00Z',
464
+ updated_at: '2025-12-15T10:05:00Z',
465
+ html_url: 'https://github.com/owner/repo/actions/runs/2',
466
+ };
467
+
468
+ const result = parseGitHubWorkflowRun(run);
469
+
470
+ expect(result.status).toBe('failure');
471
+ expect(result.testStatus).toBe('failed');
472
+ expect(result.failureDetails).toContain('https://github.com');
473
+ });
474
+
475
+ it('status/conclusionマッピング: in_progress → running', async () => {
476
+ const { parseGitHubWorkflowRun } = await import(
477
+ '../github-actions-client.js'
478
+ );
479
+
480
+ const run: IGitHubWorkflowRun = {
481
+ id: 3,
482
+ name: 'CI',
483
+ head_branch: 'main',
484
+ status: 'in_progress',
485
+ conclusion: null,
486
+ created_at: '2025-12-15T10:00:00Z',
487
+ updated_at: '2025-12-15T10:05:00Z',
488
+ html_url: 'https://github.com/owner/repo/actions/runs/3',
489
+ };
490
+
491
+ const result = parseGitHubWorkflowRun(run);
492
+
493
+ expect(result.status).toBe('running');
494
+ expect(result.testStatus).toBe('unknown');
495
+ });
496
+
497
+ it('status/conclusionマッピング: cancelled → unknown', async () => {
498
+ const { parseGitHubWorkflowRun } = await import(
499
+ '../github-actions-client.js'
500
+ );
501
+
502
+ const run: IGitHubWorkflowRun = {
503
+ id: 4,
504
+ name: 'CI',
505
+ head_branch: 'main',
506
+ status: 'completed',
507
+ conclusion: 'cancelled',
508
+ created_at: '2025-12-15T10:00:00Z',
509
+ updated_at: '2025-12-15T10:05:00Z',
510
+ html_url: 'https://github.com/owner/repo/actions/runs/4',
511
+ };
512
+
513
+ const result = parseGitHubWorkflowRun(run);
514
+
515
+ expect(result.status).toBe('unknown');
516
+ expect(result.testStatus).toBe('skipped');
517
+ });
518
+
519
+ it('日時変換: ISO 8601形式 → Date オブジェクト', async () => {
520
+ const { parseGitHubWorkflowRun } = await import(
521
+ '../github-actions-client.js'
522
+ );
523
+
524
+ const run: IGitHubWorkflowRun = {
525
+ id: 5,
526
+ name: 'CI',
527
+ head_branch: 'main',
528
+ status: 'completed',
529
+ conclusion: 'success',
530
+ created_at: '2025-12-15T10:30:45Z',
531
+ updated_at: '2025-12-15T10:35:30Z',
532
+ html_url: 'https://github.com/owner/repo/actions/runs/5',
533
+ };
534
+
535
+ const result = parseGitHubWorkflowRun(run);
536
+
537
+ expect(result.lastExecutionTime).toBeInstanceOf(Date);
538
+ expect(result.lastExecutionTime.toISOString()).toBe(
539
+ '2025-12-15T10:35:30.000Z'
540
+ );
541
+ });
542
+ });
543
+ });
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Tests for HealthCheckService
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
6
+ import { HealthCheckService } from '../health-check-service.js';
7
+ import * as fs from 'fs';
8
+ import * as child_process from 'child_process';
9
+
10
+ vi.mock('fs');
11
+ vi.mock('child_process');
12
+
13
+ // カスタムエラー型の定義
14
+ interface ExecError extends Error {
15
+ status?: number;
16
+ code?: string;
17
+ }
18
+
19
+ describe('HealthCheckService', () => {
20
+ let service: HealthCheckService;
21
+
22
+ beforeEach(() => {
23
+ vi.clearAllMocks();
24
+ service = new HealthCheckService();
25
+ });
26
+
27
+ afterEach(() => {
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ describe('ヘルスチェックスクリプトが存在しない場合', () => {
32
+ it('スキップして成功を返す', async () => {
33
+ vi.spyOn(fs, 'existsSync').mockReturnValue(false);
34
+
35
+ const result = await service.runHealthCheck('my-project');
36
+
37
+ expect(result.success).toBe(true);
38
+ expect(result.servicesStatus).toEqual([]);
39
+ });
40
+ });
41
+
42
+ describe('ヘルスチェックスクリプトが存在する場合', () => {
43
+ beforeEach(() => {
44
+ vi.spyOn(fs, 'existsSync').mockReturnValue(true);
45
+ });
46
+
47
+ it('終了コード0の場合は成功', async () => {
48
+ vi.spyOn(child_process, 'execSync').mockReturnValue(
49
+ 'database: healthy\nredis: healthy\n'
50
+ );
51
+
52
+ const result = await service.runHealthCheck('my-project');
53
+
54
+ expect(result.success).toBe(true);
55
+ expect(result.servicesStatus.length).toBeGreaterThan(0);
56
+ expect(result.servicesStatus.every((s) => s.status === 'healthy')).toBe(true);
57
+ });
58
+
59
+ it('終了コード非0の場合は失敗', async () => {
60
+ const error: ExecError = new Error('Command failed');
61
+ error.status = 1;
62
+ vi.spyOn(child_process, 'execSync').mockImplementation(() => {
63
+ throw error;
64
+ });
65
+
66
+ const result = await service.runHealthCheck('my-project');
67
+
68
+ expect(result.success).toBe(false);
69
+ });
70
+
71
+ it('スクリプト出力を解析してサービスステータスを返す', async () => {
72
+ vi.spyOn(child_process, 'execSync').mockReturnValue(
73
+ 'database: healthy\nredis: unhealthy - Connection timeout\napi: healthy\n'
74
+ );
75
+
76
+ const result = await service.runHealthCheck('my-project');
77
+
78
+ expect(result.success).toBe(false); // unhealthyなサービスがあるため
79
+ expect(result.servicesStatus).toEqual([
80
+ { serviceName: 'database', status: 'healthy', message: undefined },
81
+ { serviceName: 'redis', status: 'unhealthy', message: 'Connection timeout' },
82
+ { serviceName: 'api', status: 'healthy', message: undefined },
83
+ ]);
84
+ });
85
+
86
+ it('スクリプト実行タイムアウトの場合はエラー', async () => {
87
+ const error: ExecError = new Error('Timeout');
88
+ error.code = 'ETIMEDOUT';
89
+ vi.spyOn(child_process, 'execSync').mockImplementation(() => {
90
+ throw error;
91
+ });
92
+
93
+ const result = await service.runHealthCheck('my-project');
94
+
95
+ expect(result.success).toBe(false);
96
+ expect(result.servicesStatus.length).toBe(0);
97
+ });
98
+ });
99
+
100
+ describe('エラーハンドリング', () => {
101
+ beforeEach(() => {
102
+ vi.spyOn(fs, 'existsSync').mockReturnValue(true);
103
+ });
104
+
105
+ it('スクリプト実行中のエラーを適切にハンドリング', async () => {
106
+ const error = new Error('Script execution failed');
107
+ vi.spyOn(child_process, 'execSync').mockImplementation(() => {
108
+ throw error;
109
+ });
110
+
111
+ const result = await service.runHealthCheck('my-project');
112
+
113
+ expect(result.success).toBe(false);
114
+ });
115
+
116
+ it('スクリプト出力が不正な形式の場合はエラー', async () => {
117
+ vi.spyOn(child_process, 'execSync').mockReturnValue(
118
+ 'invalid output format\n'
119
+ );
120
+
121
+ const result = await service.runHealthCheck('my-project');
122
+
123
+ // 不正な形式でも、スクリプトが終了コード0で終了すれば成功とする
124
+ expect(result.success).toBe(true);
125
+ expect(result.servicesStatus.length).toBe(0);
126
+ });
127
+ });
128
+
129
+ describe('カスタムプロジェクトルート', () => {
130
+ it('カスタムプロジェクトルートを指定できる', async () => {
131
+ const customRoot = '/custom/path';
132
+ vi.spyOn(fs, 'existsSync').mockReturnValue(false);
133
+
134
+ const result = await service.runHealthCheck('my-project', customRoot);
135
+
136
+ expect(result.success).toBe(true);
137
+ expect(fs.existsSync).toHaveBeenCalledWith(
138
+ expect.stringContaining(`${customRoot}/docs/michi/my-project/tests/scripts/health-check.sh`)
139
+ );
140
+ });
141
+ });
142
+ });