@sk8metal/michi-cli 0.10.1 → 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 (104) hide show
  1. package/README.md +77 -847
  2. package/dist/scripts/phase-runner.js +1 -1
  3. package/dist/scripts/phase-runner.js.map +1 -1
  4. package/dist/scripts/utils/multi-repo-validator.d.ts +18 -0
  5. package/dist/scripts/utils/multi-repo-validator.d.ts.map +1 -1
  6. package/dist/scripts/utils/multi-repo-validator.js +42 -0
  7. package/dist/scripts/utils/multi-repo-validator.js.map +1 -1
  8. package/dist/scripts/utils/tasks-format-validator.js +3 -3
  9. package/dist/scripts/utils/tasks-format-validator.js.map +1 -1
  10. package/docs/README.md +20 -83
  11. package/docs/getting-started/configuration.md +379 -0
  12. package/docs/getting-started/installation.md +59 -0
  13. package/docs/getting-started/quick-start.md +76 -0
  14. package/docs/guides/ai-tools.md +311 -0
  15. package/docs/guides/atlassian-integration.md +116 -0
  16. package/docs/guides/claude-code.md +155 -0
  17. package/docs/guides/multi-repo.md +117 -0
  18. package/docs/guides/workflow.md +382 -0
  19. package/docs/reference/ai-commands.md +92 -0
  20. package/docs/reference/cli.md +756 -0
  21. package/docs/reference/environment-variables.md +192 -0
  22. package/docs/troubleshooting.md +543 -0
  23. package/package.json +1 -1
  24. package/scripts/phase-runner.ts +1 -1
  25. package/scripts/utils/__tests__/multi-repo-validator.test.ts +159 -1
  26. package/scripts/utils/multi-repo-validator.ts +50 -0
  27. package/scripts/utils/tasks-format-validator.ts +3 -3
  28. package/templates/claude/agents/e2e-first-planner/AGENT.md +1 -1
  29. package/templates/claude/agents/pr-resolver/AGENT.md +15 -3
  30. package/templates/claude/commands/michi/e2e-plan.md +1 -1
  31. package/templates/claude/commands/michi/spec-design.md +2 -2
  32. package/templates/claude/commands/michi/spec-tasks.md +156 -0
  33. package/templates/claude/commands/michi/test-planning.md +1 -1
  34. package/templates/claude/commands/michi/validate-design.md +3 -3
  35. package/templates/claude/commands/michi-multi-repo/impl-all.md +30 -1
  36. package/templates/claude/commands/michi-multi-repo/propagate-specs.md +14 -1
  37. package/templates/claude/commands/michi-multi-repo/spec-review.md +16 -2
  38. package/templates/claude-agent/agents/repo-spec-executor.md +1 -1
  39. package/templates/claude-agent/commands/michi/spec-tasks.md +117 -0
  40. package/templates/claude-agent/rules/code-size-monitor.md +26 -0
  41. package/templates/claude-agent/rules/code-size-rules.md +32 -0
  42. package/templates/codex/AGENTS.override.md +1 -1
  43. package/templates/codex/rules/README.md +2 -2
  44. package/templates/cursor/commands/michi/spec-tasks.md +117 -0
  45. package/templates/michi/cc-sdd-overrides/settings/rules/design-review-michi.md +1 -1
  46. package/docs/context.md +0 -59
  47. package/docs/michi-development/contributing/development.md +0 -341
  48. package/docs/michi-development/contributing/release.md +0 -365
  49. package/docs/michi-development/design/config-unification.md +0 -733
  50. package/docs/michi-development/design/design-config-current-state.md +0 -330
  51. package/docs/michi-development/design/design-config-implementation.md +0 -628
  52. package/docs/michi-development/design/design-config-migration.md +0 -952
  53. package/docs/michi-development/design/design-config-security.md +0 -771
  54. package/docs/michi-development/design/design-config-solution.md +0 -583
  55. package/docs/michi-development/design/design-config-testing.md +0 -892
  56. package/docs/michi-development/testing/manual-verification-flow.md +0 -871
  57. package/docs/michi-development/testing/manual-verification-other-tools.md +0 -1279
  58. package/docs/michi-development/testing/manual-verification-troubleshooting.md +0 -122
  59. package/docs/michi-development/testing/pre-publish-checklist.md +0 -560
  60. package/docs/michi-development/testing-strategy.md +0 -87
  61. package/docs/plan.md +0 -275
  62. package/docs/user-guide/getting-started/github-token-setup.md +0 -510
  63. package/docs/user-guide/getting-started/new-repository-setup.md +0 -704
  64. package/docs/user-guide/getting-started/quick-start.md +0 -212
  65. package/docs/user-guide/getting-started/setup.md +0 -819
  66. package/docs/user-guide/guides/agent-skills-integration.md +0 -222
  67. package/docs/user-guide/guides/customization.md +0 -537
  68. package/docs/user-guide/guides/internationalization.md +0 -540
  69. package/docs/user-guide/guides/migration-guide.md +0 -138
  70. package/docs/user-guide/guides/multi-project.md +0 -368
  71. package/docs/user-guide/guides/multi-repo-guide.md +0 -1590
  72. package/docs/user-guide/guides/phase-automation.md +0 -419
  73. package/docs/user-guide/guides/workflow.md +0 -574
  74. package/docs/user-guide/hands-on/README.md +0 -142
  75. package/docs/user-guide/hands-on/claude-agent-setup.md +0 -597
  76. package/docs/user-guide/hands-on/claude-setup.md +0 -452
  77. package/docs/user-guide/hands-on/cursor-setup.md +0 -353
  78. package/docs/user-guide/hands-on/troubleshooting.md +0 -964
  79. package/docs/user-guide/hands-on/verification-checklist.md +0 -439
  80. package/docs/user-guide/hands-on/workflow-walkthrough.md +0 -1078
  81. package/docs/user-guide/reference/config.md +0 -589
  82. package/docs/user-guide/reference/multi-repo-api.md +0 -771
  83. package/docs/user-guide/reference/quick-reference.md +0 -297
  84. package/docs/user-guide/reference/security-test-payloads.md +0 -50
  85. package/docs/user-guide/reference/tasks-template.md +0 -550
  86. package/docs/user-guide/release/ci-setup-java.md +0 -114
  87. package/docs/user-guide/release/ci-setup-nodejs.md +0 -94
  88. package/docs/user-guide/release/ci-setup-php.md +0 -102
  89. package/docs/user-guide/release/ci-setup-troubleshooting.md +0 -94
  90. package/docs/user-guide/release/ci-setup.md +0 -188
  91. package/docs/user-guide/release/release-flow.md +0 -476
  92. package/docs/user-guide/templates/test-specs/README.md +0 -173
  93. package/docs/user-guide/templates/test-specs/e2e-test-spec-template.md +0 -553
  94. package/docs/user-guide/templates/test-specs/integration-test-spec-template.md +0 -435
  95. package/docs/user-guide/templates/test-specs/performance-test-spec-template.md +0 -454
  96. package/docs/user-guide/templates/test-specs/security-test-spec-template.md +0 -625
  97. package/docs/user-guide/templates/test-specs/unit-test-spec-template.md +0 -328
  98. package/docs/user-guide/testing/integration-tests.md +0 -312
  99. package/docs/user-guide/testing/tdd-cycle.md +0 -349
  100. package/docs/user-guide/testing/test-execution-flow.md +0 -396
  101. package/docs/user-guide/testing/test-failure-handling.md +0 -521
  102. package/docs/user-guide/testing/test-planning-flow.md +0 -185
  103. package/docs/user-guide/testing-strategy.md +0 -185
  104. 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
-