@sk8metal/michi-cli 0.8.7 → 0.11.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 (116) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/README.md +77 -847
  3. package/dist/scripts/config/config-schema.d.ts +3 -0
  4. package/dist/scripts/config/config-schema.d.ts.map +1 -1
  5. package/dist/scripts/config/config-schema.js +18 -0
  6. package/dist/scripts/config/config-schema.js.map +1 -1
  7. package/dist/scripts/phase-runner.js +1 -1
  8. package/dist/scripts/phase-runner.js.map +1 -1
  9. package/dist/scripts/utils/multi-repo-validator.d.ts +38 -1
  10. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -1
  11. package/dist/scripts/utils/multi-repo-validator.js +166 -1
  12. package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
  13. package/dist/scripts/utils/tasks-format-validator.js +3 -3
  14. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  15. package/docs/README.md +20 -83
  16. package/docs/getting-started/configuration.md +379 -0
  17. package/docs/getting-started/installation.md +59 -0
  18. package/docs/getting-started/quick-start.md +76 -0
  19. package/docs/guides/ai-tools.md +311 -0
  20. package/docs/guides/atlassian-integration.md +116 -0
  21. package/docs/guides/claude-code.md +155 -0
  22. package/docs/guides/multi-repo.md +117 -0
  23. package/docs/guides/workflow.md +382 -0
  24. package/docs/reference/ai-commands.md +92 -0
  25. package/docs/reference/cli.md +756 -0
  26. package/docs/reference/environment-variables.md +192 -0
  27. package/docs/troubleshooting.md +543 -0
  28. package/package.json +1 -1
  29. package/scripts/__tests__/multi-repo-config-schema.test.ts +106 -0
  30. package/scripts/__tests__/multi-repo-validator.test.ts +229 -1
  31. package/scripts/config/config-schema.ts +20 -0
  32. package/scripts/phase-runner.ts +1 -1
  33. package/scripts/utils/__tests__/multi-repo-validator.test.ts +159 -1
  34. package/scripts/utils/multi-repo-validator.ts +210 -1
  35. package/scripts/utils/tasks-format-validator.ts +3 -3
  36. package/templates/claude/agents/e2e-first-planner/AGENT.md +1 -1
  37. package/templates/claude/agents/mermaid-validator/AGENT.md +257 -0
  38. package/templates/claude/agents/pr-resolver/AGENT.md +15 -3
  39. package/templates/claude/commands/michi/e2e-plan.md +1 -1
  40. package/templates/claude/commands/michi/spec-design.md +2 -2
  41. package/templates/claude/commands/michi/spec-tasks.md +156 -0
  42. package/templates/claude/commands/michi/test-planning.md +1 -1
  43. package/templates/claude/commands/michi/validate-design.md +3 -3
  44. package/templates/claude/commands/michi-multi-repo/impl-all.md +293 -0
  45. package/templates/claude/commands/michi-multi-repo/propagate-specs.md +284 -0
  46. package/templates/claude/commands/michi-multi-repo/spec-design.md +66 -3
  47. package/templates/claude/commands/michi-multi-repo/spec-review.md +261 -0
  48. package/templates/claude/skills/mermaid-validator/SKILL.md +261 -0
  49. package/templates/claude-agent/agents/cross-repo-reviewer.md +194 -0
  50. package/templates/claude-agent/agents/repo-spec-executor.md +113 -0
  51. package/templates/claude-agent/commands/michi/spec-tasks.md +117 -0
  52. package/templates/claude-agent/rules/code-size-monitor.md +26 -0
  53. package/templates/claude-agent/rules/code-size-rules.md +32 -0
  54. package/templates/codex/AGENTS.override.md +1 -1
  55. package/templates/codex/rules/README.md +2 -2
  56. package/templates/cursor/commands/michi/spec-tasks.md +117 -0
  57. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +1 -1
  58. package/docs/context.md +0 -59
  59. package/docs/michi-development/contributing/development.md +0 -341
  60. package/docs/michi-development/contributing/release.md +0 -365
  61. package/docs/michi-development/design/config-unification.md +0 -733
  62. package/docs/michi-development/design/design-config-current-state.md +0 -330
  63. package/docs/michi-development/design/design-config-implementation.md +0 -628
  64. package/docs/michi-development/design/design-config-migration.md +0 -952
  65. package/docs/michi-development/design/design-config-security.md +0 -771
  66. package/docs/michi-development/design/design-config-solution.md +0 -583
  67. package/docs/michi-development/design/design-config-testing.md +0 -892
  68. package/docs/michi-development/testing/manual-verification-flow.md +0 -871
  69. package/docs/michi-development/testing/manual-verification-other-tools.md +0 -1279
  70. package/docs/michi-development/testing/manual-verification-troubleshooting.md +0 -122
  71. package/docs/michi-development/testing/pre-publish-checklist.md +0 -560
  72. package/docs/michi-development/testing-strategy.md +0 -87
  73. package/docs/plan.md +0 -275
  74. package/docs/user-guide/getting-started/github-token-setup.md +0 -510
  75. package/docs/user-guide/getting-started/new-repository-setup.md +0 -704
  76. package/docs/user-guide/getting-started/quick-start.md +0 -212
  77. package/docs/user-guide/getting-started/setup.md +0 -819
  78. package/docs/user-guide/guides/agent-skills-integration.md +0 -222
  79. package/docs/user-guide/guides/customization.md +0 -537
  80. package/docs/user-guide/guides/internationalization.md +0 -540
  81. package/docs/user-guide/guides/migration-guide.md +0 -138
  82. package/docs/user-guide/guides/multi-project.md +0 -368
  83. package/docs/user-guide/guides/multi-repo-guide.md +0 -1147
  84. package/docs/user-guide/guides/phase-automation.md +0 -419
  85. package/docs/user-guide/guides/workflow.md +0 -584
  86. package/docs/user-guide/hands-on/README.md +0 -142
  87. package/docs/user-guide/hands-on/claude-agent-setup.md +0 -597
  88. package/docs/user-guide/hands-on/claude-setup.md +0 -452
  89. package/docs/user-guide/hands-on/cursor-setup.md +0 -353
  90. package/docs/user-guide/hands-on/troubleshooting.md +0 -964
  91. package/docs/user-guide/hands-on/verification-checklist.md +0 -439
  92. package/docs/user-guide/hands-on/workflow-walkthrough.md +0 -909
  93. package/docs/user-guide/reference/config.md +0 -589
  94. package/docs/user-guide/reference/multi-repo-api.md +0 -771
  95. package/docs/user-guide/reference/quick-reference.md +0 -297
  96. package/docs/user-guide/reference/security-test-payloads.md +0 -50
  97. package/docs/user-guide/reference/tasks-template.md +0 -550
  98. package/docs/user-guide/release/ci-setup-java.md +0 -114
  99. package/docs/user-guide/release/ci-setup-nodejs.md +0 -94
  100. package/docs/user-guide/release/ci-setup-php.md +0 -102
  101. package/docs/user-guide/release/ci-setup-troubleshooting.md +0 -94
  102. package/docs/user-guide/release/ci-setup.md +0 -188
  103. package/docs/user-guide/release/release-flow.md +0 -476
  104. package/docs/user-guide/templates/test-specs/README.md +0 -173
  105. package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +0 -553
  106. package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +0 -435
  107. package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +0 -454
  108. package/docs/user-guide/templates/test-specs/security-test-spec-template.md +0 -625
  109. package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +0 -328
  110. package/docs/user-guide/testing/integration-tests.md +0 -312
  111. package/docs/user-guide/testing/tdd-cycle.md +0 -349
  112. package/docs/user-guide/testing/test-execution-flow.md +0 -396
  113. package/docs/user-guide/testing/test-failure-handling.md +0 -521
  114. package/docs/user-guide/testing/test-planning-flow.md +0 -185
  115. package/docs/user-guide/testing-strategy.md +0 -185
  116. package/docs/verification-guide.md +0 -518
@@ -1,892 +0,0 @@
1
- # Michi 設定統合設計書 - テスト戦略
2
-
3
- **バージョン**: 1.0
4
- **作成日**: 2025-01-11
5
- **ステータス**: Draft
6
- **親ドキュメント**: [config-unification.md](./config-unification.md)
7
-
8
- ---
9
-
10
- ## 8. テスト戦略
11
-
12
- 設定統一に伴う変更を安全に実施するためのテスト戦略です。
13
-
14
- ### 8.1 テストスコープ
15
-
16
- #### 8.1.1 対象範囲
17
-
18
- 設定統一により影響を受ける主要な機能をテスト対象とします:
19
-
20
- | カテゴリ | テスト対象 | テストレベル |
21
- |---------|-----------|------------|
22
- | **設定読み込み** | ConfigLoader の3層マージ | 単体 + 統合 |
23
- | **バリデーション** | Zodスキーマによる検証 | 単体 |
24
- | **パス解決** | リポジトリURLのパース | 単体 |
25
- | **コマンドライン** | michi init / migrate | 統合 + E2E |
26
- | **外部API** | Confluence/JIRA/GitHub連携 | 統合 + E2E |
27
- | **マイグレーション** | 既存設定の移行処理 | 統合 + E2E |
28
- | **後方互換性** | 旧形式設定のサポート | 統合 |
29
-
30
- #### 8.1.2 テスト優先順位
31
-
32
- **P0 (Critical): リリースブロッカー**
33
- - ConfigLoader の3層マージロジック
34
- - 必須項目のバリデーション
35
- - リポジトリURLパーサー
36
- - `michi migrate` コマンド
37
-
38
- **P1 (High): リリース前に必須**
39
- - `michi init` コマンド(新規・既存)
40
- - Confluence/JIRA/GitHub連携の動作確認
41
- - 設定ファイルのパーミッション
42
- - エラーメッセージの内容
43
-
44
- **P2 (Medium): リリース後でも対応可能**
45
- - 詳細なエラーメッセージ
46
- - ドライランモード
47
- - ロールバック機能
48
- - 設定バリデーションの詳細
49
-
50
- ### 8.2 単体テスト
51
-
52
- #### 8.2.1 ConfigLoader のテスト
53
-
54
- **ファイル**: `src/config/__tests__/config-loader.test.ts`
55
-
56
- ```typescript
57
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
58
- import { ConfigLoader } from '../config-loader.js';
59
- import { mkdirSync, writeFileSync, rmSync } from 'fs';
60
- import { tmpdir } from 'os';
61
- import { join } from 'path';
62
-
63
- describe('ConfigLoader', () => {
64
- let testDir: string;
65
- let globalEnvPath: string;
66
- let projectDir: string;
67
-
68
- beforeEach(() => {
69
- // テスト用の一時ディレクトリ作成
70
- testDir = join(tmpdir(), `michi-test-${Date.now()}`);
71
- mkdirSync(testDir, { recursive: true });
72
-
73
- // グローバル設定ディレクトリ
74
- globalEnvPath = join(testDir, '.michi', '.env');
75
- mkdirSync(join(testDir, '.michi'), { recursive: true });
76
-
77
- // プロジェクトディレクトリ
78
- projectDir = join(testDir, 'project');
79
- mkdirSync(join(projectDir, '.michi'), { recursive: true });
80
-
81
- // 環境変数を上書き
82
- process.env.HOME = testDir;
83
- });
84
-
85
- afterEach(() => {
86
- // テスト後のクリーンアップ
87
- rmSync(testDir, { recursive: true, force: true });
88
- });
89
-
90
- describe('3層マージ', () => {
91
- it('グローバル設定を正しく読み込む', () => {
92
- // グローバル .env を作成
93
- writeFileSync(globalEnvPath, `
94
- CONFLUENCE_URL=https://global.atlassian.net
95
- JIRA_URL=https://global.atlassian.net
96
- GITHUB_TOKEN=global-token
97
- `.trim());
98
-
99
- const loader = new ConfigLoader(projectDir);
100
- const config = loader.load();
101
-
102
- expect(config.confluence?.url).toBe('https://global.atlassian.net');
103
- expect(config.jira?.url).toBe('https://global.atlassian.net');
104
- expect(config.github?.token).toBe('global-token');
105
- });
106
-
107
- it('プロジェクト設定がグローバル設定を上書きする', () => {
108
- // グローバル .env
109
- writeFileSync(globalEnvPath, `
110
- CONFLUENCE_URL=https://global.atlassian.net
111
- GITHUB_TOKEN=global-token
112
- `.trim());
113
-
114
- // プロジェクト .env
115
- writeFileSync(join(projectDir, '.env'), `
116
- CONFLUENCE_URL=https://project.atlassian.net
117
- `.trim());
118
-
119
- const loader = new ConfigLoader(projectDir);
120
- const config = loader.load();
121
-
122
- // プロジェクト設定が優先される
123
- expect(config.confluence?.url).toBe('https://project.atlassian.net');
124
- // グローバル設定は保持される
125
- expect(config.github?.token).toBe('global-token');
126
- });
127
-
128
- it('プロジェクト config.json がグローバル設定を上書きする', () => {
129
- // グローバル .env
130
- writeFileSync(globalEnvPath, `
131
- CONFLUENCE_URL=https://global.atlassian.net
132
- `.trim());
133
-
134
- // プロジェクト config.json
135
- writeFileSync(join(projectDir, '.michi', 'config.json'), JSON.stringify({
136
- confluence: {
137
- pageCreationGranularity: 'by-section'
138
- }
139
- }, null, 2));
140
-
141
- const loader = new ConfigLoader(projectDir);
142
- const config = loader.load();
143
-
144
- expect(config.confluence?.url).toBe('https://global.atlassian.net');
145
- expect(config.confluence?.pageCreationGranularity).toBe('by-section');
146
- });
147
-
148
- it('優先順位: プロジェクト .env > プロジェクト config.json > グローバル .env', () => {
149
- // グローバル .env
150
- writeFileSync(globalEnvPath, `
151
- CONFLUENCE_SPACE=GLOBAL
152
- JIRA_PROJECT=GLOBAL
153
- `.trim());
154
-
155
- // プロジェクト config.json
156
- writeFileSync(join(projectDir, '.michi', 'config.json'), JSON.stringify({
157
- confluence: { space: 'CONFIG' },
158
- jira: { project: 'CONFIG' }
159
- }, null, 2));
160
-
161
- // プロジェクト .env
162
- writeFileSync(join(projectDir, '.env'), `
163
- CONFLUENCE_SPACE=PROJECT
164
- `.trim());
165
-
166
- const loader = new ConfigLoader(projectDir);
167
- const config = loader.load();
168
-
169
- // プロジェクト .env が最優先
170
- expect(config.confluence?.space).toBe('PROJECT');
171
- // プロジェクト config.json が次
172
- expect(config.jira?.project).toBe('CONFIG');
173
- });
174
- });
175
-
176
- describe('リポジトリURLパーサー', () => {
177
- it('HTTPS形式のURLを正しくパースする', () => {
178
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
179
- projectId: 'test-project',
180
- repository: 'https://github.com/myorg/myrepo.git'
181
- }, null, 2));
182
-
183
- const loader = new ConfigLoader(projectDir);
184
- const config = loader.load();
185
-
186
- expect(config.github?.repository).toBe('https://github.com/myorg/myrepo.git');
187
- expect(config.github?.repositoryOrg).toBe('myorg');
188
- expect(config.github?.repositoryName).toBe('myrepo');
189
- expect(config.github?.repositoryShort).toBe('myorg/myrepo');
190
- });
191
-
192
- it('SSH形式のURLを正しくパースする', () => {
193
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
194
- projectId: 'test-project',
195
- repository: 'git@github.com:myorg/myrepo.git'
196
- }, null, 2));
197
-
198
- const loader = new ConfigLoader(projectDir);
199
- const config = loader.load();
200
-
201
- expect(config.github?.repositoryOrg).toBe('myorg');
202
- expect(config.github?.repositoryName).toBe('myrepo');
203
- expect(config.github?.repositoryShort).toBe('myorg/myrepo');
204
- });
205
-
206
- it('.git拡張子なしのURLを正しくパースする', () => {
207
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
208
- projectId: 'test-project',
209
- repository: 'https://github.com/myorg/myrepo'
210
- }, null, 2));
211
-
212
- const loader = new ConfigLoader(projectDir);
213
- const config = loader.load();
214
-
215
- expect(config.github?.repositoryOrg).toBe('myorg');
216
- expect(config.github?.repositoryName).toBe('myrepo');
217
- });
218
-
219
- it('不正な形式のURLでエラーをスローする', () => {
220
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
221
- projectId: 'test-project',
222
- repository: 'invalid-url'
223
- }, null, 2));
224
-
225
- const loader = new ConfigLoader(projectDir);
226
-
227
- expect(() => loader.load()).toThrow('Invalid GitHub repository URL');
228
- });
229
- });
230
-
231
- describe('バリデーション', () => {
232
- it('必須項目が不足している場合エラーをスローする', () => {
233
- // グローバル .env なし、プロジェクト .env も最小限
234
- writeFileSync(join(projectDir, '.env'), `
235
- # 何も設定しない
236
- `.trim());
237
-
238
- const loader = new ConfigLoader(projectDir);
239
-
240
- expect(() => loader.load()).toThrow();
241
- });
242
-
243
- it('CONFLUENCE_URL が不正な場合エラーをスローする', () => {
244
- writeFileSync(globalEnvPath, `
245
- CONFLUENCE_URL=not-a-url
246
- `.trim());
247
-
248
- const loader = new ConfigLoader(projectDir);
249
-
250
- expect(() => loader.load()).toThrow();
251
- });
252
- });
253
-
254
- describe('後方互換性', () => {
255
- it('GITHUB_REPO が .env に存在する場合、警告を表示する', () => {
256
- const consoleSpy = vi.spyOn(console, 'warn');
257
-
258
- writeFileSync(join(projectDir, '.env'), `
259
- GITHUB_REPO=myorg/myrepo
260
- `.trim());
261
-
262
- const loader = new ConfigLoader(projectDir);
263
- loader.load();
264
-
265
- expect(consoleSpy).toHaveBeenCalledWith(
266
- expect.stringContaining('GITHUB_REPO is deprecated')
267
- );
268
- });
269
-
270
- it('project.json に repository が存在する場合、GITHUB_REPO は無視される', () => {
271
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
272
- projectId: 'test-project',
273
- repository: 'https://github.com/correct/repo.git'
274
- }, null, 2));
275
-
276
- writeFileSync(join(projectDir, '.env'), `
277
- GITHUB_REPO=wrong/repo
278
- `.trim());
279
-
280
- const loader = new ConfigLoader(projectDir);
281
- const config = loader.load();
282
-
283
- expect(config.github?.repositoryShort).toBe('correct/repo');
284
- });
285
- });
286
- });
287
- ```
288
-
289
- #### 8.2.2 バリデーションのテスト
290
-
291
- **ファイル**: `src/config/__tests__/validation.test.ts`
292
-
293
- ```typescript
294
- import { describe, it, expect } from 'vitest';
295
- import { AppConfigSchema } from '../config-schema.js';
296
-
297
- describe('設定バリデーション', () => {
298
- describe('Confluence設定', () => {
299
- it('有効なConfluence設定を受け入れる', () => {
300
- const config = {
301
- confluence: {
302
- url: 'https://example.atlassian.net',
303
- username: 'user@example.com',
304
- apiToken: 'token123',
305
- space: 'DEV',
306
- pageCreationGranularity: 'single'
307
- }
308
- };
309
-
310
- expect(() => AppConfigSchema.parse(config)).not.toThrow();
311
- });
312
-
313
- it('不正なURL形式を拒否する', () => {
314
- const config = {
315
- confluence: {
316
- url: 'not-a-url',
317
- username: 'user@example.com',
318
- apiToken: 'token123'
319
- }
320
- };
321
-
322
- expect(() => AppConfigSchema.parse(config)).toThrow();
323
- });
324
-
325
- it('pageCreationGranularityの不正な値を拒否する', () => {
326
- const config = {
327
- confluence: {
328
- url: 'https://example.atlassian.net',
329
- pageCreationGranularity: 'invalid-value'
330
- }
331
- };
332
-
333
- expect(() => AppConfigSchema.parse(config)).toThrow();
334
- });
335
- });
336
-
337
- describe('JIRA設定', () => {
338
- it('有効なJIRA設定を受け入れる', () => {
339
- const config = {
340
- jira: {
341
- url: 'https://example.atlassian.net',
342
- username: 'user@example.com',
343
- apiToken: 'token123',
344
- project: 'MYPROJ',
345
- createEpic: true,
346
- storyCreationGranularity: 'all'
347
- }
348
- };
349
-
350
- expect(() => AppConfigSchema.parse(config)).not.toThrow();
351
- });
352
- });
353
-
354
- describe('GitHub設定', () => {
355
- it('有効なGitHub設定を受け入れる', () => {
356
- const config = {
357
- github: {
358
- token: 'ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
359
- username: 'octocat',
360
- email: 'octocat@github.com',
361
- org: 'myorg'
362
- }
363
- };
364
-
365
- expect(() => AppConfigSchema.parse(config)).not.toThrow();
366
- });
367
-
368
- it('トークンの形式を検証する', () => {
369
- const config = {
370
- github: {
371
- token: 'invalid-token-format',
372
- username: 'octocat'
373
- }
374
- };
375
-
376
- expect(() => AppConfigSchema.parse(config)).toThrow();
377
- });
378
- });
379
- });
380
- ```
381
-
382
- ### 8.3 統合テスト
383
-
384
- #### 8.3.1 コマンドラインテスト
385
-
386
- **ファイル**: `src/__tests__/integration/cli.test.ts`
387
-
388
- ```typescript
389
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
390
- import { execSync } from 'child_process';
391
- import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
392
- import { tmpdir } from 'os';
393
- import { join } from 'path';
394
-
395
- describe('CLI統合テスト', () => {
396
- let testDir: string;
397
-
398
- beforeEach(() => {
399
- testDir = join(tmpdir(), `michi-cli-test-${Date.now()}`);
400
- mkdirSync(testDir, { recursive: true });
401
- process.chdir(testDir);
402
- });
403
-
404
- afterEach(() => {
405
- rmSync(testDir, { recursive: true, force: true });
406
- });
407
-
408
- describe('michi init', () => {
409
- it('新規プロジェクトを初期化できる', () => {
410
- // Git リポジトリを初期化
411
- execSync('git init', { cwd: testDir });
412
- execSync('git config user.name "Test User"', { cwd: testDir });
413
- execSync('git config user.email "test@example.com"', { cwd: testDir });
414
-
415
- // michi init を実行(対話的入力をスキップするため --yes オプションを使用)
416
- const output = execSync('michi init --yes --project-id test-project', {
417
- cwd: testDir,
418
- encoding: 'utf-8'
419
- });
420
-
421
- expect(output).toContain('プロジェクトの初期化が完了しました');
422
- expect(existsSync(join(testDir, '.michi', 'project.json'))).toBe(true);
423
- expect(existsSync(join(testDir, '.env'))).toBe(true);
424
- });
425
-
426
- it('既存プロジェクトにMichiを追加できる (--existing)', () => {
427
- // 既存のGitリポジトリをシミュレート
428
- execSync('git init', { cwd: testDir });
429
- execSync('git remote add origin https://github.com/test/repo.git', { cwd: testDir });
430
-
431
- const output = execSync('michi init --existing --yes', {
432
- cwd: testDir,
433
- encoding: 'utf-8'
434
- });
435
-
436
- expect(output).toContain('既存プロジェクトへの追加が完了しました');
437
-
438
- // project.json の repository が自動設定される
439
- const projectJson = JSON.parse(
440
- readFileSync(join(testDir, '.michi', 'project.json'), 'utf-8')
441
- );
442
- expect(projectJson.repository).toBe('https://github.com/test/repo.git');
443
- });
444
-
445
- it('Gitリポジトリがない場合、--existing でエラーになる', () => {
446
- expect(() => {
447
- execSync('michi init --existing --yes', {
448
- cwd: testDir,
449
- encoding: 'utf-8'
450
- });
451
- }).toThrow();
452
- });
453
- });
454
-
455
- describe('michi migrate', () => {
456
- beforeEach(() => {
457
- // 旧形式の設定ファイルを作成
458
- mkdirSync(join(testDir, '.michi'), { recursive: true });
459
-
460
- writeFileSync(join(testDir, '.env'), `
461
- CONFLUENCE_URL=https://example.atlassian.net
462
- CONFLUENCE_USERNAME=user@example.com
463
- CONFLUENCE_API_TOKEN=token123
464
- JIRA_URL=https://example.atlassian.net
465
- JIRA_USERNAME=user@example.com
466
- JIRA_API_TOKEN=token456
467
- GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
468
- GITHUB_USERNAME=testuser
469
- GITHUB_EMAIL=test@example.com
470
- GITHUB_ORG=myorg
471
- GITHUB_REPO=myorg/myrepo
472
- `.trim());
473
-
474
- writeFileSync(join(testDir, '.michi', 'project.json'), JSON.stringify({
475
- projectId: 'test-project'
476
- }, null, 2));
477
- });
478
-
479
- it('設定を正しく移行する', () => {
480
- const output = execSync('michi migrate --force', {
481
- cwd: testDir,
482
- encoding: 'utf-8',
483
- env: { ...process.env, HOME: testDir }
484
- });
485
-
486
- expect(output).toContain('設定の移行が完了しました');
487
-
488
- // グローバル .env が作成される
489
- const globalEnv = join(testDir, '.michi', '.env');
490
- expect(existsSync(globalEnv)).toBe(true);
491
-
492
- const globalContent = readFileSync(globalEnv, 'utf-8');
493
- expect(globalContent).toContain('CONFLUENCE_URL=');
494
- expect(globalContent).toContain('JIRA_URL=');
495
- expect(globalContent).toContain('GITHUB_TOKEN=');
496
-
497
- // プロジェクト .env から組織設定が削除される
498
- const projectEnv = readFileSync(join(testDir, '.env'), 'utf-8');
499
- expect(projectEnv).not.toContain('CONFLUENCE_URL=');
500
- expect(projectEnv).not.toContain('GITHUB_REPO=');
501
-
502
- // project.json に repository が追加される
503
- const projectJson = JSON.parse(
504
- readFileSync(join(testDir, '.michi', 'project.json'), 'utf-8')
505
- );
506
- expect(projectJson.repository).toBe('https://github.com/myorg/myrepo.git');
507
- });
508
-
509
- it('--dry-run で変更をシミュレートする', () => {
510
- const output = execSync('michi migrate --dry-run', {
511
- cwd: testDir,
512
- encoding: 'utf-8',
513
- env: { ...process.env, HOME: testDir }
514
- });
515
-
516
- expect(output).toContain('ドライランモード');
517
- expect(output).toContain('実際の変更は行われませんでした');
518
-
519
- // ファイルが変更されていないことを確認
520
- const globalEnv = join(testDir, '.michi', '.env');
521
- expect(existsSync(globalEnv)).toBe(false);
522
- });
523
-
524
- it('バックアップを作成する', () => {
525
- execSync('michi migrate --force', {
526
- cwd: testDir,
527
- encoding: 'utf-8',
528
- env: { ...process.env, HOME: testDir }
529
- });
530
-
531
- // バックアップディレクトリが作成されている
532
- const backups = readdirSync(testDir).filter(f => f.startsWith('.michi-backup-'));
533
- expect(backups.length).toBeGreaterThan(0);
534
- });
535
- });
536
-
537
- describe('michi config:validate', () => {
538
- it('有効な設定を検証する', () => {
539
- // グローバル設定
540
- mkdirSync(join(testDir, '.michi'), { recursive: true });
541
- writeFileSync(join(testDir, '.michi', '.env'), `
542
- CONFLUENCE_URL=https://example.atlassian.net
543
- JIRA_URL=https://example.atlassian.net
544
- GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
545
- `.trim());
546
-
547
- // プロジェクト設定
548
- const projectDir = join(testDir, 'project');
549
- mkdirSync(join(projectDir, '.michi'), { recursive: true });
550
- writeFileSync(join(projectDir, '.michi', 'project.json'), JSON.stringify({
551
- projectId: 'test-project',
552
- repository: 'https://github.com/myorg/myrepo.git'
553
- }, null, 2));
554
-
555
- const output = execSync('michi config:validate', {
556
- cwd: projectDir,
557
- encoding: 'utf-8',
558
- env: { ...process.env, HOME: testDir }
559
- });
560
-
561
- expect(output).toContain('設定ファイルは有効です');
562
- });
563
-
564
- it('不正な設定でエラーを表示する', () => {
565
- mkdirSync(join(testDir, '.michi'), { recursive: true });
566
- writeFileSync(join(testDir, '.env'), `
567
- CONFLUENCE_URL=not-a-url
568
- `.trim());
569
-
570
- expect(() => {
571
- execSync('michi config:validate', {
572
- cwd: testDir,
573
- encoding: 'utf-8'
574
- });
575
- }).toThrow();
576
- });
577
- });
578
- });
579
- ```
580
-
581
- #### 8.3.2 外部API連携テスト
582
-
583
- **ファイル**: `src/__tests__/integration/external-api.test.ts`
584
-
585
- ```typescript
586
- import { describe, it, expect, beforeAll } from 'vitest';
587
- import { ConfigLoader } from '../../config/config-loader.js';
588
- import { ConfluenceClient } from '../../confluence/client.js';
589
- import { JiraClient } from '../../jira/client.js';
590
- import { GitHubClient } from '../../github/client.js';
591
-
592
- // 注: これらのテストは実際のAPIキーが必要なため、CI環境では skip される
593
- // ローカルで実行する場合は、~/.michi/.env に実際の認証情報を設定すること
594
-
595
- describe('外部API連携テスト', () => {
596
- let config: ReturnType<ConfigLoader['load']>;
597
-
598
- beforeAll(() => {
599
- const loader = new ConfigLoader(process.cwd());
600
- config = loader.load();
601
- });
602
-
603
- describe('Confluence連携', () => {
604
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
605
- 'Confluenceに接続できる',
606
- async () => {
607
- const client = new ConfluenceClient(config);
608
- const spaces = await client.getSpaces();
609
-
610
- expect(Array.isArray(spaces)).toBe(true);
611
- }
612
- );
613
-
614
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
615
- 'スペース情報を取得できる',
616
- async () => {
617
- const client = new ConfluenceClient(config);
618
- const space = await client.getSpace(config.confluence!.space!);
619
-
620
- expect(space).toBeDefined();
621
- expect(space.key).toBe(config.confluence!.space);
622
- }
623
- );
624
- });
625
-
626
- describe('JIRA連携', () => {
627
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
628
- 'JIRAに接続できる',
629
- async () => {
630
- const client = new JiraClient(config);
631
- const projects = await client.getProjects();
632
-
633
- expect(Array.isArray(projects)).toBe(true);
634
- }
635
- );
636
-
637
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
638
- 'プロジェクト情報を取得できる',
639
- async () => {
640
- const client = new JiraClient(config);
641
- const project = await client.getProject(config.jira!.project!);
642
-
643
- expect(project).toBeDefined();
644
- expect(project.key).toBe(config.jira!.project);
645
- }
646
- );
647
- });
648
-
649
- describe('GitHub連携', () => {
650
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
651
- 'GitHubに接続できる',
652
- async () => {
653
- const client = new GitHubClient(config);
654
- const user = await client.getCurrentUser();
655
-
656
- expect(user).toBeDefined();
657
- expect(user.login).toBe(config.github!.username);
658
- }
659
- );
660
-
661
- it.skipIf(!process.env.RUN_INTEGRATION_TESTS)(
662
- 'リポジトリ情報を取得できる',
663
- async () => {
664
- const client = new GitHubClient(config);
665
- const repo = await client.getRepository();
666
-
667
- expect(repo).toBeDefined();
668
- expect(repo.full_name).toBe(config.github!.repositoryShort);
669
- }
670
- );
671
- });
672
- });
673
- ```
674
-
675
- ### 8.4 E2Eテスト
676
-
677
- #### 8.4.1 完全なワークフローテスト
678
-
679
- **ファイル**: `src/__tests__/e2e/full-workflow.test.ts`
680
-
681
- ```typescript
682
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
683
- import { execSync } from 'child_process';
684
- import { mkdirSync, rmSync } from 'fs';
685
- import { tmpdir } from 'os';
686
- import { join } from 'path';
687
-
688
- describe('E2E: 完全なワークフロー', () => {
689
- let testDir: string;
690
- let projectDir: string;
691
-
692
- beforeAll(() => {
693
- testDir = join(tmpdir(), `michi-e2e-${Date.now()}`);
694
- projectDir = join(testDir, 'my-project');
695
-
696
- mkdirSync(projectDir, { recursive: true });
697
- process.chdir(projectDir);
698
-
699
- // Git リポジトリを初期化
700
- execSync('git init', { cwd: projectDir });
701
- execSync('git config user.name "Test User"', { cwd: projectDir });
702
- execSync('git config user.email "test@example.com"', { cwd: projectDir });
703
- });
704
-
705
- afterAll(() => {
706
- rmSync(testDir, { recursive: true, force: true });
707
- });
708
-
709
- it('新規プロジェクト作成 → 設定 → Confluence同期', async () => {
710
- // ステップ1: プロジェクト初期化
711
- execSync('michi init --yes --project-id my-project', {
712
- cwd: projectDir,
713
- encoding: 'utf-8'
714
- });
715
-
716
- // ステップ2: グローバル設定を作成
717
- execSync('npm run config:global', {
718
- cwd: projectDir,
719
- input: 'n\nn\nn\n', // すべての設定をスキップ
720
- encoding: 'utf-8',
721
- env: { ...process.env, HOME: testDir }
722
- });
723
-
724
- // ステップ3: Confluence同期(ドライラン)
725
- const output = execSync(
726
- 'michi confluence:sync test-feature requirements --dry-run',
727
- {
728
- cwd: projectDir,
729
- encoding: 'utf-8',
730
- env: { ...process.env, HOME: testDir }
731
- }
732
- );
733
-
734
- expect(output).toContain('ドライランモード');
735
- }, 30000); // 30秒タイムアウト
736
-
737
- it('既存プロジェクト → 移行 → 検証', async () => {
738
- // ステップ1: 旧形式の設定を作成
739
- mkdirSync(join(projectDir, '.michi'), { recursive: true });
740
- writeFileSync(join(projectDir, '.env'), `
741
- CONFLUENCE_URL=https://example.atlassian.net
742
- GITHUB_REPO=myorg/myrepo
743
- `.trim());
744
-
745
- // ステップ2: 移行実行
746
- execSync('michi migrate --force', {
747
- cwd: projectDir,
748
- encoding: 'utf-8',
749
- env: { ...process.env, HOME: testDir }
750
- });
751
-
752
- // ステップ3: 設定検証
753
- const output = execSync('michi config:validate', {
754
- cwd: projectDir,
755
- encoding: 'utf-8',
756
- env: { ...process.env, HOME: testDir }
757
- });
758
-
759
- expect(output).toContain('設定ファイルは有効です');
760
- }, 30000);
761
- });
762
- ```
763
-
764
- ### 8.5 カバレッジ目標
765
-
766
- #### 8.5.1 目標値
767
-
768
- | カテゴリ | 目標カバレッジ | 現在値 | 期限 |
769
- |---------|-------------|--------|------|
770
- | **全体** | 95% | TBD | リリース前 |
771
- | **ConfigLoader** | 100% | TBD | Week 1 |
772
- | **バリデーション** | 100% | TBD | Week 1 |
773
- | **CLIコマンド** | 90% | TBD | Week 2 |
774
- | **マイグレーション** | 95% | TBD | Week 2 |
775
- | **外部API** | 80% | TBD | Week 3 |
776
-
777
- #### 8.5.2 カバレッジ計測
778
-
779
- ```bash
780
- # カバレッジレポートの生成
781
- npm run test:coverage
782
-
783
- # HTMLレポートの確認
784
- open coverage/index.html
785
-
786
- # 最小カバレッジのチェック(CI用)
787
- npm run test:coverage -- --min-coverage=95
788
- ```
789
-
790
- #### 8.5.3 除外項目
791
-
792
- 以下のファイルはカバレッジ計測から除外します:
793
-
794
- - テストファイル: `**/*.test.ts`, `**/*.spec.ts`
795
- - 型定義ファイル: `**/*.d.ts`
796
- - 設定ファイル: `**/config/**/*.ts` (一部)
797
- - CLIエントリポイント: `src/cli.ts` (メイン関数のみ)
798
-
799
- ### 8.6 テスト実行
800
-
801
- #### 8.6.1 ローカルでのテスト実行
802
-
803
- ```bash
804
- # すべてのテストを実行
805
- npm run test
806
-
807
- # 単体テストのみ
808
- npm run test src/config/__tests__
809
-
810
- # 統合テストのみ
811
- npm run test src/__tests__/integration
812
-
813
- # E2Eテストのみ
814
- npm run test src/__tests__/e2e
815
-
816
- # ウォッチモード(開発時)
817
- npm run test -- --watch
818
-
819
- # 特定のファイルのみ
820
- npm run test src/config/__tests__/config-loader.test.ts
821
- ```
822
-
823
- #### 8.6.2 CI/CDでのテスト実行
824
-
825
- **GitHub Actions**: `.github/workflows/test.yml`
826
-
827
- ```yaml
828
- name: Test
829
-
830
- on:
831
- push:
832
- branches: [main, develop]
833
- pull_request:
834
- branches: [main, develop]
835
-
836
- jobs:
837
- test:
838
- runs-on: ubuntu-latest
839
-
840
- steps:
841
- - uses: actions/checkout@v4
842
-
843
- - name: Setup Node.js
844
- uses: actions/setup-node@v4
845
- with:
846
- node-version: '20'
847
- cache: 'npm'
848
-
849
- - name: Install dependencies
850
- run: npm ci
851
-
852
- - name: Run unit tests
853
- run: npm run test:run
854
-
855
- - name: Run integration tests
856
- run: npm run test:run src/__tests__/integration
857
- env:
858
- RUN_INTEGRATION_TESTS: 'false' # CI環境では外部APIテストをスキップ
859
-
860
- - name: Generate coverage report
861
- run: npm run test:coverage
862
-
863
- - name: Upload coverage to Codecov
864
- uses: codecov/codecov-action@v3
865
- with:
866
- files: ./coverage/coverage-final.json
867
-
868
- - name: Check coverage threshold
869
- run: npm run test:coverage -- --min-coverage=95
870
- ```
871
-
872
- #### 8.6.3 テスト実行のベストプラクティス
873
-
874
- 1. **開発時**
875
- - ウォッチモードで関連するテストのみを実行
876
- - 変更したファイルに対応するテストを優先
877
-
878
- 2. **コミット前**
879
- - すべての単体テストを実行
880
- - 関連する統合テストを実行
881
-
882
- 3. **PR作成時**
883
- - すべてのテストを実行
884
- - カバレッジレポートを確認
885
-
886
- 4. **リリース前**
887
- - すべてのテストを実行(E2E含む)
888
- - 外部APIテストを手動で実行
889
- - カバレッジが目標値を満たしているか確認
890
-
891
- ---
892
-