@einja/dev-cli 0.1.6

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 (243) hide show
  1. package/README.md +179 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +49 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/init.d.ts +3 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +243 -0
  9. package/dist/commands/init.js.map +1 -0
  10. package/dist/commands/list.d.ts +2 -0
  11. package/dist/commands/list.d.ts.map +1 -0
  12. package/dist/commands/list.js +23 -0
  13. package/dist/commands/list.js.map +1 -0
  14. package/dist/commands/sync.d.ts +7 -0
  15. package/dist/commands/sync.d.ts.map +1 -0
  16. package/dist/commands/sync.js +294 -0
  17. package/dist/commands/sync.js.map +1 -0
  18. package/dist/commands/sync.test.d.ts +2 -0
  19. package/dist/commands/sync.test.d.ts.map +1 -0
  20. package/dist/commands/sync.test.js +593 -0
  21. package/dist/commands/sync.test.js.map +1 -0
  22. package/dist/commands/task-loop.d.ts +11 -0
  23. package/dist/commands/task-loop.d.ts.map +1 -0
  24. package/dist/commands/task-loop.js +81 -0
  25. package/dist/commands/task-loop.js.map +1 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +3 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lib/file-system.d.ts +39 -0
  31. package/dist/lib/file-system.d.ts.map +1 -0
  32. package/dist/lib/file-system.js +79 -0
  33. package/dist/lib/file-system.js.map +1 -0
  34. package/dist/lib/mcp-config.d.ts +43 -0
  35. package/dist/lib/mcp-config.d.ts.map +1 -0
  36. package/dist/lib/mcp-config.js +109 -0
  37. package/dist/lib/mcp-config.js.map +1 -0
  38. package/dist/lib/mcp-config.test.d.ts +2 -0
  39. package/dist/lib/mcp-config.test.d.ts.map +1 -0
  40. package/dist/lib/mcp-config.test.js +285 -0
  41. package/dist/lib/mcp-config.test.js.map +1 -0
  42. package/dist/lib/merger.d.ts +41 -0
  43. package/dist/lib/merger.d.ts.map +1 -0
  44. package/dist/lib/merger.js +164 -0
  45. package/dist/lib/merger.js.map +1 -0
  46. package/dist/lib/preset-update/cli-repo-detector.d.ts +35 -0
  47. package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -0
  48. package/dist/lib/preset-update/cli-repo-detector.js +83 -0
  49. package/dist/lib/preset-update/cli-repo-detector.js.map +1 -0
  50. package/dist/lib/preset-update/cli-repo-detector.test.d.ts +2 -0
  51. package/dist/lib/preset-update/cli-repo-detector.test.d.ts.map +1 -0
  52. package/dist/lib/preset-update/cli-repo-detector.test.js +120 -0
  53. package/dist/lib/preset-update/cli-repo-detector.test.js.map +1 -0
  54. package/dist/lib/preset-update/file-copier.d.ts +59 -0
  55. package/dist/lib/preset-update/file-copier.d.ts.map +1 -0
  56. package/dist/lib/preset-update/file-copier.js +220 -0
  57. package/dist/lib/preset-update/file-copier.js.map +1 -0
  58. package/dist/lib/preset-update/file-copier.test.d.ts +2 -0
  59. package/dist/lib/preset-update/file-copier.test.d.ts.map +1 -0
  60. package/dist/lib/preset-update/file-copier.test.js +297 -0
  61. package/dist/lib/preset-update/file-copier.test.js.map +1 -0
  62. package/dist/lib/preset-update/preset-finder.d.ts +39 -0
  63. package/dist/lib/preset-update/preset-finder.d.ts.map +1 -0
  64. package/dist/lib/preset-update/preset-finder.js +92 -0
  65. package/dist/lib/preset-update/preset-finder.js.map +1 -0
  66. package/dist/lib/preset-update/preset-finder.test.d.ts +2 -0
  67. package/dist/lib/preset-update/preset-finder.test.d.ts.map +1 -0
  68. package/dist/lib/preset-update/preset-finder.test.js +128 -0
  69. package/dist/lib/preset-update/preset-finder.test.js.map +1 -0
  70. package/dist/lib/preset.d.ts +14 -0
  71. package/dist/lib/preset.d.ts.map +1 -0
  72. package/dist/lib/preset.js +52 -0
  73. package/dist/lib/preset.js.map +1 -0
  74. package/dist/lib/sync/backup-manager.d.ts +50 -0
  75. package/dist/lib/sync/backup-manager.d.ts.map +1 -0
  76. package/dist/lib/sync/backup-manager.js +117 -0
  77. package/dist/lib/sync/backup-manager.js.map +1 -0
  78. package/dist/lib/sync/backup-manager.test.d.ts +2 -0
  79. package/dist/lib/sync/backup-manager.test.d.ts.map +1 -0
  80. package/dist/lib/sync/backup-manager.test.js +155 -0
  81. package/dist/lib/sync/backup-manager.test.js.map +1 -0
  82. package/dist/lib/sync/batch-processor.d.ts +27 -0
  83. package/dist/lib/sync/batch-processor.d.ts.map +1 -0
  84. package/dist/lib/sync/batch-processor.js +46 -0
  85. package/dist/lib/sync/batch-processor.js.map +1 -0
  86. package/dist/lib/sync/batch-processor.test.d.ts +2 -0
  87. package/dist/lib/sync/batch-processor.test.d.ts.map +1 -0
  88. package/dist/lib/sync/batch-processor.test.js +110 -0
  89. package/dist/lib/sync/batch-processor.test.js.map +1 -0
  90. package/dist/lib/sync/category-validator.d.ts +36 -0
  91. package/dist/lib/sync/category-validator.d.ts.map +1 -0
  92. package/dist/lib/sync/category-validator.js +46 -0
  93. package/dist/lib/sync/category-validator.js.map +1 -0
  94. package/dist/lib/sync/category-validator.test.d.ts +2 -0
  95. package/dist/lib/sync/category-validator.test.d.ts.map +1 -0
  96. package/dist/lib/sync/category-validator.test.js +89 -0
  97. package/dist/lib/sync/category-validator.test.js.map +1 -0
  98. package/dist/lib/sync/conflict-reporter.d.ts +57 -0
  99. package/dist/lib/sync/conflict-reporter.d.ts.map +1 -0
  100. package/dist/lib/sync/conflict-reporter.js +81 -0
  101. package/dist/lib/sync/conflict-reporter.js.map +1 -0
  102. package/dist/lib/sync/conflict-reporter.test.d.ts +2 -0
  103. package/dist/lib/sync/conflict-reporter.test.d.ts.map +1 -0
  104. package/dist/lib/sync/conflict-reporter.test.js +132 -0
  105. package/dist/lib/sync/conflict-reporter.test.js.map +1 -0
  106. package/dist/lib/sync/diff-engine.d.ts +28 -0
  107. package/dist/lib/sync/diff-engine.d.ts.map +1 -0
  108. package/dist/lib/sync/diff-engine.js +118 -0
  109. package/dist/lib/sync/diff-engine.js.map +1 -0
  110. package/dist/lib/sync/diff-engine.test.d.ts +2 -0
  111. package/dist/lib/sync/diff-engine.test.d.ts.map +1 -0
  112. package/dist/lib/sync/diff-engine.test.js +133 -0
  113. package/dist/lib/sync/diff-engine.test.js.map +1 -0
  114. package/dist/lib/sync/file-filter.d.ts +40 -0
  115. package/dist/lib/sync/file-filter.d.ts.map +1 -0
  116. package/dist/lib/sync/file-filter.js +171 -0
  117. package/dist/lib/sync/file-filter.js.map +1 -0
  118. package/dist/lib/sync/file-filter.test.d.ts +2 -0
  119. package/dist/lib/sync/file-filter.test.d.ts.map +1 -0
  120. package/dist/lib/sync/file-filter.test.js +179 -0
  121. package/dist/lib/sync/file-filter.test.js.map +1 -0
  122. package/dist/lib/sync/hash-cache.d.ts +34 -0
  123. package/dist/lib/sync/hash-cache.d.ts.map +1 -0
  124. package/dist/lib/sync/hash-cache.js +51 -0
  125. package/dist/lib/sync/hash-cache.js.map +1 -0
  126. package/dist/lib/sync/hash-cache.test.d.ts +2 -0
  127. package/dist/lib/sync/hash-cache.test.d.ts.map +1 -0
  128. package/dist/lib/sync/hash-cache.test.js +110 -0
  129. package/dist/lib/sync/hash-cache.test.js.map +1 -0
  130. package/dist/lib/sync/integration.test.d.ts +2 -0
  131. package/dist/lib/sync/integration.test.d.ts.map +1 -0
  132. package/dist/lib/sync/integration.test.js +317 -0
  133. package/dist/lib/sync/integration.test.js.map +1 -0
  134. package/dist/lib/sync/marker-processor.d.ts +54 -0
  135. package/dist/lib/sync/marker-processor.d.ts.map +1 -0
  136. package/dist/lib/sync/marker-processor.js +208 -0
  137. package/dist/lib/sync/marker-processor.js.map +1 -0
  138. package/dist/lib/sync/marker-processor.test.d.ts +2 -0
  139. package/dist/lib/sync/marker-processor.test.d.ts.map +1 -0
  140. package/dist/lib/sync/marker-processor.test.js +245 -0
  141. package/dist/lib/sync/marker-processor.test.js.map +1 -0
  142. package/dist/lib/sync/metadata-manager.d.ts +46 -0
  143. package/dist/lib/sync/metadata-manager.d.ts.map +1 -0
  144. package/dist/lib/sync/metadata-manager.js +129 -0
  145. package/dist/lib/sync/metadata-manager.js.map +1 -0
  146. package/dist/lib/sync/metadata-manager.test.d.ts +2 -0
  147. package/dist/lib/sync/metadata-manager.test.d.ts.map +1 -0
  148. package/dist/lib/sync/metadata-manager.test.js +137 -0
  149. package/dist/lib/sync/metadata-manager.test.js.map +1 -0
  150. package/dist/lib/sync/performance.test.d.ts +2 -0
  151. package/dist/lib/sync/performance.test.d.ts.map +1 -0
  152. package/dist/lib/sync/performance.test.js +126 -0
  153. package/dist/lib/sync/performance.test.js.map +1 -0
  154. package/dist/types/index.d.ts +59 -0
  155. package/dist/types/index.d.ts.map +1 -0
  156. package/dist/types/index.js +2 -0
  157. package/dist/types/index.js.map +1 -0
  158. package/dist/types/preset-update.d.ts +106 -0
  159. package/dist/types/preset-update.d.ts.map +1 -0
  160. package/dist/types/preset-update.js +5 -0
  161. package/dist/types/preset-update.js.map +1 -0
  162. package/dist/types/sync.d.ts +169 -0
  163. package/dist/types/sync.d.ts.map +1 -0
  164. package/dist/types/sync.js +19 -0
  165. package/dist/types/sync.js.map +1 -0
  166. package/package.json +72 -0
  167. package/presets/minimal/.claude/agents/einja/docs/docs-updater.md +161 -0
  168. package/presets/minimal/.claude/agents/einja/frontend/design-engineer.md +685 -0
  169. package/presets/minimal/.claude/agents/einja/frontend/frontend-architect.md +747 -0
  170. package/presets/minimal/.claude/agents/einja/frontend/frontend-coder.md +441 -0
  171. package/presets/minimal/.claude/agents/einja/git/conflict-resolver.md +148 -0
  172. package/presets/minimal/.claude/agents/einja/specs/spec-design-generator.md +462 -0
  173. package/presets/minimal/.claude/agents/einja/specs/spec-qa-generator.md +466 -0
  174. package/presets/minimal/.claude/agents/einja/specs/spec-requirements-generator.md +416 -0
  175. package/presets/minimal/.claude/agents/einja/specs/spec-tasks-generator.md +608 -0
  176. package/presets/minimal/.claude/agents/einja/task/task-committer.md +82 -0
  177. package/presets/minimal/.claude/agents/einja/task/task-executer.md +352 -0
  178. package/presets/minimal/.claude/agents/einja/task/task-modification-analyzer.md +369 -0
  179. package/presets/minimal/.claude/agents/einja/task/task-qa.md +74 -0
  180. package/presets/minimal/.claude/agents/einja/task/task-reviewer.md +169 -0
  181. package/presets/minimal/.claude/commands/einja/frontend-implement.md +322 -0
  182. package/presets/minimal/.claude/commands/einja/spec-create.md +254 -0
  183. package/presets/minimal/.claude/commands/einja/start-dev.md +98 -0
  184. package/presets/minimal/.claude/commands/einja/sync-cursor-commands.md +203 -0
  185. package/presets/minimal/.claude/commands/einja/task-exec.md +390 -0
  186. package/presets/minimal/.claude/commands/einja/update-docs-by-task-specs.md +448 -0
  187. package/presets/minimal/.claude/hooks/einja/biome-format.sh +49 -0
  188. package/presets/minimal/.claude/hooks/einja/design-doc-check.sh +61 -0
  189. package/presets/minimal/.claude/hooks/einja/detect-secrets.sh +62 -0
  190. package/presets/minimal/.claude/hooks/einja/large-file-warning.sh +42 -0
  191. package/presets/minimal/.claude/hooks/einja/playwright-resize.sh +36 -0
  192. package/presets/minimal/.claude/hooks/einja/typecheck.sh +37 -0
  193. package/presets/minimal/.claude/hooks/einja/unset-volta-recursion.sh +32 -0
  194. package/presets/minimal/.claude/hooks/einja/validate-git-commit.sh +239 -0
  195. package/presets/minimal/.claude/hooks/einja/warn-index-ts.sh +34 -0
  196. package/presets/minimal/.claude/hooks/einja/warn-relative-import.sh +48 -0
  197. package/presets/minimal/.claude/settings.json +174 -0
  198. package/presets/minimal/.claude/skills/einja/api-development/SKILL.md +14 -0
  199. package/presets/minimal/.claude/skills/einja/backend-architecture/SKILL.md +14 -0
  200. package/presets/minimal/.claude/skills/einja/coding-standards/SKILL.md +120 -0
  201. package/presets/minimal/.claude/skills/einja/coding-standards/reference/naming-conventions.md +107 -0
  202. package/presets/minimal/.claude/skills/einja/coding-standards/reference/prohibited-patterns.md +169 -0
  203. package/presets/minimal/.claude/skills/einja/coding-standards/reference/typescript-rules.md +247 -0
  204. package/presets/minimal/.claude/skills/einja/component-design/SKILL.md +109 -0
  205. package/presets/minimal/.claude/skills/einja/component-design/reference/directory-structure.md +117 -0
  206. package/presets/minimal/.claude/skills/einja/component-design/reference/props-patterns.md +159 -0
  207. package/presets/minimal/.claude/skills/einja/component-design/reference/styling-guide.md +200 -0
  208. package/presets/minimal/.claude/skills/einja/conflict-resolver/SKILL.md +190 -0
  209. package/presets/minimal/.claude/skills/einja/frontend-development/SKILL.md +14 -0
  210. package/presets/minimal/.claude/skills/einja/general-context-loader/SKILL.md +254 -0
  211. package/presets/minimal/.claude/skills/einja/output-format/SKILL.md +137 -0
  212. package/presets/minimal/.claude/skills/einja/spec-context-loader/SKILL.md +177 -0
  213. package/presets/minimal/.claude/skills/einja/task-commit/SKILL.md +269 -0
  214. package/presets/minimal/.claude/skills/einja/task-qa/SKILL.md +306 -0
  215. package/presets/minimal/.claude/skills/einja/task-qa/reference/failure-patterns.md +69 -0
  216. package/presets/minimal/.claude/skills/einja/task-qa/reference/troubleshooting.md +65 -0
  217. package/presets/minimal/.claude/skills/einja/task-qa/reference/usage-patterns.md +52 -0
  218. package/presets/minimal/.claude/skills/einja/task-qa/templates/qa-test-template.md +128 -0
  219. package/presets/minimal/preset.yaml +111 -0
  220. package/presets/minimal/symlinks.json +45 -0
  221. package/scaffolds/.mcp.json +45 -0
  222. package/scaffolds/CLAUDE.md.template +318 -0
  223. package/scaffolds/steering/README.md +170 -0
  224. package/scaffolds/steering/acceptance-criteria-and-qa-guide.md +415 -0
  225. package/scaffolds/steering/architecture.md +481 -0
  226. package/scaffolds/steering/branch-strategy.md +362 -0
  227. package/scaffolds/steering/commit-rules.md +217 -0
  228. package/scaffolds/steering/db-schema-design.md +609 -0
  229. package/scaffolds/steering/development/api-development.md +783 -0
  230. package/scaffolds/steering/development/backend-architecture.md +731 -0
  231. package/scaffolds/steering/development/frontend-development.md +1537 -0
  232. package/scaffolds/steering/development/review-guidelines.md +365 -0
  233. package/scaffolds/steering/development/testing-strategy.md +819 -0
  234. package/scaffolds/steering/development-workflow.md +429 -0
  235. package/scaffolds/steering/infrastructure/deployment.md +277 -0
  236. package/scaffolds/steering/infrastructure/environment-variables.md +298 -0
  237. package/scaffolds/steering/product.md +540 -0
  238. package/scaffolds/steering/task-management.md +367 -0
  239. package/templates/README.md +159 -0
  240. package/templates/design-simple.md.template +172 -0
  241. package/templates/design.md.template +327 -0
  242. package/templates/qa-test.md.template +125 -0
  243. package/templates/requirements.md.template +254 -0
@@ -0,0 +1,819 @@
1
+ # テスト戦略
2
+
3
+ ## 概要
4
+
5
+ このドキュメントでは、ユニットテスト、統合テスト、E2Eテスト(Playwrightコード)の戦略と実装ガイドラインを説明します。
6
+
7
+ テストカバレッジ目標を達成し、高品質なコードを維持するためのベストプラクティスを提供します。
8
+
9
+ > **Note**: このドキュメントは開発者向けの自動テスト戦略を扱います。QAエージェント(task-qa)が実行するBrowserテスト(Playwright MCP)については、`docs/einja/steering/terminology.md` および `docs/einja/steering/acceptance-criteria-and-qa-guide.md` を参照してください。
10
+
11
+ ---
12
+
13
+ ## 目次
14
+
15
+ 1. [テスト戦略の全体像](#1-テスト戦略の全体像)
16
+ 2. [ユニットテスト](#2-ユニットテスト)
17
+ 3. [統合テスト](#3-統合テスト)
18
+ 4. [E2Eテスト](#4-e2eテスト)
19
+ 5. [テストカバレッジ](#5-テストカバレッジ)
20
+ 6. [テストツール](#6-テストツール)
21
+ 7. [テストの実行](#7-テストの実行)
22
+ 8. [モックとスタブ](#8-モックとスタブ)
23
+
24
+ ---
25
+
26
+ ## 1. テスト戦略の全体像
27
+
28
+ ### テストピラミッド
29
+
30
+ ```mermaid
31
+ graph TB
32
+ subgraph "テストピラミッド"
33
+ E2E[E2Eテスト<br/>完全なユーザーフロー<br/>少ない]
34
+ Integration[統合テスト<br/>パッケージ間連携<br/>中程度]
35
+ Unit[ユニットテスト<br/>個別の関数・クラス<br/>多い]
36
+ end
37
+
38
+ E2E --> Integration
39
+ Integration --> Unit
40
+
41
+ style E2E fill:#ffebee
42
+ style Integration fill:#fff3e0
43
+ style Unit fill:#e8f5e9
44
+ ```
45
+
46
+ ### テスト種別と目的
47
+
48
+ | テスト種別 | 目的 | 対象 | 実行頻度 |
49
+ |-----------|------|------|---------|
50
+ | ユニットテスト | 個別の関数・クラスの動作確認 | Repository, UseCase, Entity, Validator | 毎コミット |
51
+ | 統合テスト | パッケージ間の連携確認 | @repo/server-core ⇔ apps/* | 毎PR |
52
+ | E2Eテスト | 完全なユーザーフローの確認 | Web, Admin, Cron Worker | デプロイ前 |
53
+
54
+ ### TDDワークフロー(Red-Green-Refactor)
55
+
56
+ TDD採用が推奨される場合(ビジネスロジック、データ変換、外部API連携、金銭・認証処理など)、以下のサイクルを実施:
57
+
58
+ 1. **Red**: 失敗するテストを書く(Given-When-Then形式で記述)
59
+ - 期待する振る舞いをテストとして記述
60
+ - テストを実行し、失敗することを確認
61
+
62
+ 2. **Green**: テストを通す最小限の実装
63
+ - テストが通る最小限のコードを書く
64
+ - この段階では美しさより動作を優先
65
+
66
+ 3. **Refactor**: コードを改善
67
+ - テストが通ったまま、コードを改善
68
+ - 重複削除、命名改善、構造改善
69
+
70
+ **補足**: Given-When-ThenとRed-Green-Refactorは**補完関係**:
71
+ - **Given-When-Then**: テストの記述形式(どう書くか)
72
+ - **Red-Green-Refactor**: TDDの実施サイクル(どう進めるか)
73
+ - 組み合わせ: 「Red-Green-RefactorサイクルでGiven-When-Then形式のテストを書く」
74
+
75
+ ---
76
+
77
+ ## 2. ユニットテスト
78
+
79
+ ### 対象コンポーネント
80
+
81
+ 1. **Repository**(Infrastructure層)
82
+ 2. **UseCase**(Application層)
83
+ 3. **Entity**(Domain層)
84
+ 4. **Validator**(Domain層)
85
+ 5. **Mapper**(Infrastructure層)
86
+
87
+ ### 正常系テストケース
88
+
89
+ #### Repositoryのテスト
90
+
91
+ **Given-When-Then形式**:
92
+
93
+ ```typescript
94
+ // packages/server-core/src/infrastructure/database/repositories/__tests__/UserRepository.test.ts
95
+ import { userRepository } from '../UserRepository'
96
+ import { prisma } from '../../client'
97
+
98
+ describe('UserRepository', () => {
99
+ beforeEach(async () => {
100
+ // テストデータのクリーンアップ
101
+ await prisma.user.deleteMany()
102
+ })
103
+
104
+ describe('create', () => {
105
+ it('有効なユーザーデータを渡すと、データベースにユーザーが作成され、ドメインエンティティが返る', async () => {
106
+ // Given: 有効なユーザーデータ
107
+ const userData = {
108
+ email: 'test@example.com',
109
+ name: 'Test User',
110
+ }
111
+
112
+ // When: createメソッドを呼び出す
113
+ const result = await userRepository.create(userData)
114
+
115
+ // Then: 成功結果が返り、ユーザーが作成される
116
+ expect(result.isSuccess).toBe(true)
117
+ if (result.isSuccess) {
118
+ expect(result.value.email).toBe('test@example.com')
119
+ expect(result.value.name).toBe('Test User')
120
+ expect(result.value.id).toBeDefined()
121
+ }
122
+
123
+ // データベースに保存されているか確認
124
+ const savedUser = await prisma.user.findUnique({
125
+ where: { email: 'test@example.com' }
126
+ })
127
+ expect(savedUser).toBeDefined()
128
+ })
129
+ })
130
+
131
+ describe('find', () => {
132
+ it('存在するユーザーのメールアドレスで検索すると、ユーザーが返る', async () => {
133
+ // Given: ユーザーが存在する
134
+ await prisma.user.create({
135
+ data: {
136
+ email: 'existing@example.com',
137
+ name: 'Existing User',
138
+ }
139
+ })
140
+
141
+ // When: メールアドレスで検索
142
+ const result = await userRepository.find({ email: 'existing@example.com' })
143
+
144
+ // Then: ユーザーが見つかる
145
+ expect(result.isSuccess).toBe(true)
146
+ if (result.isSuccess) {
147
+ expect(result.value).not.toBeNull()
148
+ expect(result.value?.email).toBe('existing@example.com')
149
+ }
150
+ })
151
+ })
152
+ })
153
+ ```
154
+
155
+ #### バリデーションのテスト
156
+
157
+ ```typescript
158
+ // packages/server-core/src/domain/validators/__tests__/user.test.ts
159
+ import { createUserSchema, updateUserSchema } from '../user'
160
+
161
+ describe('User Validators', () => {
162
+ describe('createUserSchema', () => {
163
+ it('有効なメールアドレスと名前が渡されると、バリデーションが成功する', () => {
164
+ // Given: 有効なユーザーデータ
165
+ const validData = {
166
+ email: 'test@example.com',
167
+ name: 'Test User',
168
+ }
169
+
170
+ // When: バリデーションを実行
171
+ const result = createUserSchema.safeParse(validData)
172
+
173
+ // Then: バリデーション成功
174
+ expect(result.success).toBe(true)
175
+ if (result.success) {
176
+ expect(result.data.email).toBe('test@example.com')
177
+ expect(result.data.name).toBe('Test User')
178
+ }
179
+ })
180
+
181
+ it('不正な形式のメールアドレスが渡されると、バリデーションが失敗する', () => {
182
+ // Given: 不正なメールアドレス
183
+ const invalidData = {
184
+ email: 'invalid-email',
185
+ name: 'Test User',
186
+ }
187
+
188
+ // When: バリデーションを実行
189
+ const result = createUserSchema.safeParse(invalidData)
190
+
191
+ // Then: バリデーション失敗
192
+ expect(result.success).toBe(false)
193
+ if (!result.success) {
194
+ expect(result.error.issues[0].path).toContain('email')
195
+ }
196
+ })
197
+ })
198
+ })
199
+ ```
200
+
201
+ #### エンティティのテスト
202
+
203
+ ```typescript
204
+ // packages/server-core/src/domain/entities/__tests__/Post.test.ts
205
+ import { Post } from '../Post'
206
+
207
+ describe('Post Entity', () => {
208
+ describe('publish', () => {
209
+ it('下書き状態の投稿をpublishすると、公開状態になり、公開日時が設定される', () => {
210
+ // Given: 下書き状態の投稿
211
+ const post = new Post({
212
+ id: '1',
213
+ userId: 'user1',
214
+ title: 'Test Post',
215
+ content: 'Content',
216
+ status: 'draft',
217
+ publishedAt: null,
218
+ createdAt: new Date(),
219
+ updatedAt: new Date(),
220
+ })
221
+
222
+ // When: publish メソッドを呼び出す
223
+ post.publish()
224
+
225
+ // Then: 公開状態になる
226
+ expect(post.status).toBe('published')
227
+ expect(post.publishedAt).toBeInstanceOf(Date)
228
+ expect(post.isPublished()).toBe(true)
229
+ })
230
+ })
231
+
232
+ describe('isOwnedBy', () => {
233
+ it('投稿の所有者IDと一致するユーザーIDを渡すと、trueが返る', () => {
234
+ // Given: ユーザー'user1'が作成した投稿
235
+ const post = new Post({
236
+ id: '1',
237
+ userId: 'user1',
238
+ title: 'Test Post',
239
+ content: 'Content',
240
+ status: 'draft',
241
+ publishedAt: null,
242
+ createdAt: new Date(),
243
+ updatedAt: new Date(),
244
+ })
245
+
246
+ // When: 所有者チェック
247
+ const isOwned = post.isOwnedBy('user1')
248
+
249
+ // Then: trueが返る
250
+ expect(isOwned).toBe(true)
251
+ })
252
+
253
+ it('投稿の所有者IDと異なるユーザーIDを渡すと、falseが返る', () => {
254
+ // Given: ユーザー'user1'が作成した投稿
255
+ const post = new Post({
256
+ id: '1',
257
+ userId: 'user1',
258
+ title: 'Test Post',
259
+ content: 'Content',
260
+ status: 'draft',
261
+ publishedAt: null,
262
+ createdAt: new Date(),
263
+ updatedAt: new Date(),
264
+ })
265
+
266
+ // When: 別のユーザーIDで所有者チェック
267
+ const isOwned = post.isOwnedBy('user2')
268
+
269
+ // Then: falseが返る
270
+ expect(isOwned).toBe(false)
271
+ })
272
+ })
273
+ })
274
+ ```
275
+
276
+ ### 異常系テストケース
277
+
278
+ #### 無効な入力値のテスト
279
+
280
+ ```typescript
281
+ describe('createUserSchema - 異常系', () => {
282
+ it('メールアドレスが空文字の場合、バリデーションエラーが発生する', () => {
283
+ // Given: 空のメールアドレス
284
+ const invalidData = {
285
+ email: '',
286
+ name: 'Test User',
287
+ }
288
+
289
+ // When: バリデーション実行
290
+ const result = createUserSchema.safeParse(invalidData)
291
+
292
+ // Then: エラーが返る
293
+ expect(result.success).toBe(false)
294
+ if (!result.success) {
295
+ expect(result.error.issues[0].message).toContain('email')
296
+ }
297
+ })
298
+
299
+ it('名前が100文字を超える場合、バリデーションエラーが発生する', () => {
300
+ // Given: 100文字を超える名前
301
+ const invalidData = {
302
+ email: 'test@example.com',
303
+ name: 'a'.repeat(101),
304
+ }
305
+
306
+ // When: バリデーション実行
307
+ const result = createUserSchema.safeParse(invalidData)
308
+
309
+ // Then: エラーが返る
310
+ expect(result.success).toBe(false)
311
+ })
312
+ })
313
+ ```
314
+
315
+ #### データベースエラーのテスト
316
+
317
+ ```typescript
318
+ describe('UserRepository - データベースエラー', () => {
319
+ it('重複したメールアドレスでユーザーを作成すると、エラーが返る', async () => {
320
+ // Given: 既に存在するメールアドレス
321
+ await prisma.user.create({
322
+ data: {
323
+ email: 'duplicate@example.com',
324
+ name: 'Existing User',
325
+ }
326
+ })
327
+
328
+ // When: 同じメールアドレスでユーザー作成
329
+ const result = await userRepository.create({
330
+ email: 'duplicate@example.com',
331
+ name: 'New User',
332
+ })
333
+
334
+ // Then: エラーが返る
335
+ expect(result.isSuccess).toBe(false)
336
+ if (!result.isSuccess) {
337
+ expect(result.error.code).toBe('CONFLICT')
338
+ }
339
+ })
340
+ })
341
+ ```
342
+
343
+ ---
344
+
345
+ ## 3. 統合テスト
346
+
347
+ ### パッケージ間の連携テスト
348
+
349
+ #### webから@repo/server-coreの使用
350
+
351
+ ```typescript
352
+ // apps/web/__tests__/integration/post-api.test.ts
353
+ import { apiClient } from '@/lib/api-client'
354
+ import { prisma } from '@repo/server-core/infrastructure/database/client'
355
+
356
+ describe('Post API統合テスト', () => {
357
+ beforeEach(async () => {
358
+ // テストデータのクリーンアップ
359
+ await prisma.post.deleteMany()
360
+ await prisma.user.deleteMany()
361
+ })
362
+
363
+ it('投稿一覧APIを呼び出すと、@repo/server-coreのRepositoryが使用され、データが取得できる', async () => {
364
+ // Given: ユーザーと投稿が存在する
365
+ const user = await prisma.user.create({
366
+ data: {
367
+ email: 'test@example.com',
368
+ name: 'Test User',
369
+ }
370
+ })
371
+
372
+ await prisma.post.create({
373
+ data: {
374
+ userId: user.id,
375
+ title: 'Test Post',
376
+ content: 'Content',
377
+ status: 'published',
378
+ }
379
+ })
380
+
381
+ // When: API呼び出し
382
+ const response = await apiClient.posts.$get({
383
+ query: { page: '1', limit: '10' }
384
+ })
385
+
386
+ // Then: データが取得できる
387
+ expect(response.ok).toBe(true)
388
+ const data = await response.json()
389
+ expect(data.posts).toHaveLength(1)
390
+ expect(data.posts[0].title).toBe('Test Post')
391
+ })
392
+
393
+ it('Repositoryパターンが正しく動作し、型定義が適切に共有される', async () => {
394
+ // Given: ユーザーが存在する
395
+ const user = await prisma.user.create({
396
+ data: {
397
+ email: 'test@example.com',
398
+ name: 'Test User',
399
+ }
400
+ })
401
+
402
+ // When: 投稿作成APIを呼び出し
403
+ const response = await apiClient.posts.$post({
404
+ json: {
405
+ title: 'New Post',
406
+ content: 'Content',
407
+ status: 'draft',
408
+ }
409
+ })
410
+
411
+ // Then: 作成成功
412
+ expect(response.ok).toBe(true)
413
+ const data = await response.json()
414
+ expect(data.post.title).toBe('New Post')
415
+ expect(data.post.status).toBe('draft')
416
+
417
+ // 型推論が正しく機能しているか確認(TypeScriptコンパイルエラーが出ないこと)
418
+ const title: string = data.post.title
419
+ const status: 'draft' | 'published' | 'archived' = data.post.status
420
+ expect(title).toBeDefined()
421
+ expect(status).toBeDefined()
422
+ })
423
+ })
424
+ ```
425
+
426
+ ### ビルドパイプラインのテスト
427
+
428
+ ```typescript
429
+ // __tests__/integration/build.test.ts
430
+ import { execSync } from 'child_process'
431
+
432
+ describe('ビルドパイプライン統合テスト', () => {
433
+ it('turbo run buildで全アプリケーションがビルド成功する', () => {
434
+ // When: ビルド実行
435
+ expect(() => {
436
+ execSync('pnpm turbo run build', { stdio: 'inherit' })
437
+ }).not.toThrow()
438
+ })
439
+
440
+ it('依存関係の順序が正しく解決される', () => {
441
+ // Given: @repo/server-coreがwebより先にビルドされる必要がある
442
+ // When: ビルド実行
443
+ const output = execSync('pnpm turbo run build --dry-run', { encoding: 'utf-8' })
444
+
445
+ // Then: 依存関係の順序が正しい
446
+ expect(output).toContain('@repo/server-core')
447
+ expect(output.indexOf('@repo/server-core')).toBeLessThan(output.indexOf('web'))
448
+ })
449
+ })
450
+ ```
451
+
452
+ ---
453
+
454
+ ## 4. E2Eテスト(Playwrightコード)
455
+
456
+ > **用語の明確化**: このセクションで説明する「E2Eテスト」は、`pnpm test:e2e` で実行されるPlaywrightコードベースの自動テストを指します。QAエージェントがPlaywright MCPを使用して実行する「Browserテスト」とは異なります。詳細は `docs/einja/steering/terminology.md` を参照してください。
457
+
458
+ ### Playwright設定
459
+
460
+ **playwright.config.ts**:
461
+
462
+ ```typescript
463
+ import { defineConfig, devices } from '@playwright/test'
464
+
465
+ export default defineConfig({
466
+ testDir: './e2e',
467
+ fullyParallel: true,
468
+ forbidOnly: !!process.env.CI,
469
+ retries: process.env.CI ? 2 : 0,
470
+ workers: process.env.CI ? 1 : undefined,
471
+ reporter: 'html',
472
+ use: {
473
+ baseURL: 'http://localhost:3000',
474
+ trace: 'on-first-retry',
475
+ },
476
+
477
+ projects: [
478
+ {
479
+ name: 'chromium',
480
+ use: { ...devices['Desktop Chrome'] },
481
+ },
482
+ {
483
+ name: 'firefox',
484
+ use: { ...devices['Desktop Firefox'] },
485
+ },
486
+ ],
487
+
488
+ webServer: {
489
+ command: 'pnpm dev',
490
+ url: 'http://localhost:3000',
491
+ reuseExistingServer: !process.env.CI,
492
+ },
493
+ })
494
+ ```
495
+
496
+ ### 完全なユーザーフロー
497
+
498
+ ```typescript
499
+ // e2e/post-creation.spec.ts
500
+ import { test, expect } from '@playwright/test'
501
+
502
+ test.describe('投稿作成フロー', () => {
503
+ test('ユーザーがwebにアクセスし、ログインして投稿を作成すると、データベースに保存され、一覧ページに表示される', async ({ page }) => {
504
+ // Given: ユーザーがログインページにアクセス
505
+ await page.goto('/auth/login')
506
+
507
+ // When: ログイン
508
+ await page.fill('[data-testid="email-input"]', 'test@example.com')
509
+ await page.fill('[data-testid="password-input"]', 'password123')
510
+ await page.click('[data-testid="login-button"]')
511
+
512
+ // Then: ダッシュボードにリダイレクト
513
+ await expect(page).toHaveURL('/dashboard')
514
+
515
+ // When: 投稿作成ページに移動
516
+ await page.click('[data-testid="create-post-button"]')
517
+ await expect(page).toHaveURL('/posts/new')
518
+
519
+ // When: 投稿フォームを入力
520
+ await page.fill('[data-testid="title-input"]', 'E2E Test Post')
521
+ await page.fill('[data-testid="content-input"]', 'This is E2E test content')
522
+ await page.selectOption('[data-testid="status-select"]', 'published')
523
+ await page.click('[data-testid="submit-button"]')
524
+
525
+ // Then: 投稿一覧ページにリダイレクト
526
+ await expect(page).toHaveURL('/posts')
527
+
528
+ // Then: 作成した投稿が一覧に表示される
529
+ await expect(page.locator('text=E2E Test Post')).toBeVisible()
530
+ })
531
+ })
532
+ ```
533
+
534
+ ### 管理画面フロー
535
+
536
+ ```typescript
537
+ // e2e/admin-user-management.spec.ts
538
+ import { test, expect } from '@playwright/test'
539
+
540
+ test.describe('管理画面ユーザー管理フロー', () => {
541
+ test('管理者がadminにログインし、ユーザー一覧を表示し、ユーザーを削除すると、データベースから削除され、webに反映される', async ({ page, context }) => {
542
+ // Given: 管理者がログイン
543
+ await page.goto('/admin/login')
544
+ await page.fill('[data-testid="email-input"]', 'admin@example.com')
545
+ await page.fill('[data-testid="password-input"]', 'admin123')
546
+ await page.click('[data-testid="login-button"]')
547
+
548
+ // Then: 管理画面ダッシュボードにリダイレクト
549
+ await expect(page).toHaveURL('/admin/dashboard')
550
+
551
+ // When: ユーザー一覧ページに移動
552
+ await page.click('[data-testid="users-link"]')
553
+ await expect(page).toHaveURL('/admin/users')
554
+
555
+ // Then: ユーザー一覧が表示される
556
+ await expect(page.locator('table[data-testid="users-table"]')).toBeVisible()
557
+
558
+ // When: ユーザーを削除
559
+ await page.click('[data-testid="delete-user-button-1"]')
560
+ await page.click('[data-testid="confirm-delete-button"]')
561
+
562
+ // Then: ユーザーが一覧から削除される
563
+ await expect(page.locator('[data-testid="user-row-1"]')).not.toBeVisible()
564
+
565
+ // When: webアプリで確認(別タブ)
566
+ const webPage = await context.newPage()
567
+ await webPage.goto('http://localhost:3000/users')
568
+
569
+ // Then: 削除されたユーザーが表示されない
570
+ await expect(webPage.locator('[data-testid="user-1"]')).not.toBeVisible()
571
+ })
572
+ })
573
+ ```
574
+
575
+ ### Cronジョブフロー
576
+
577
+ ```typescript
578
+ // e2e/cron-cleanup.spec.ts
579
+ import { test, expect } from '@playwright/test'
580
+ import { execSync } from 'child_process'
581
+ import { prisma } from '@repo/server-core/infrastructure/database/client'
582
+
583
+ test.describe('Cronジョブフロー', () => {
584
+ test('クリーンアップジョブが実行されると、期限切れセッションが削除される', async () => {
585
+ // Given: 期限切れセッションが存在する
586
+ const expiredSession = await prisma.session.create({
587
+ data: {
588
+ userId: 'user1',
589
+ token: 'expired-token',
590
+ expiresAt: new Date(Date.now() - 1000 * 60 * 60), // 1時間前
591
+ }
592
+ })
593
+
594
+ // When: クリーンアップジョブを実行
595
+ execSync('pnpm job:cleanup', { stdio: 'inherit' })
596
+
597
+ // Then: 期限切れセッションが削除される
598
+ const deletedSession = await prisma.session.findUnique({
599
+ where: { id: expiredSession.id }
600
+ })
601
+ expect(deletedSession).toBeNull()
602
+ })
603
+ })
604
+ ```
605
+
606
+ ---
607
+
608
+ ## 5. テストカバレッジ
609
+
610
+ ### カバレッジ目標
611
+
612
+ | 対象 | カバレッジ目標 | 優先度 |
613
+ |------|--------------|--------|
614
+ | 共有パッケージ (@repo/server-core) | 80%以上 | 最優先 |
615
+ | アプリケーション (web, admin, cron-worker) | 70%以上 | 高 |
616
+ | 統合テスト | 主要フロー100% | 高 |
617
+ | E2Eテスト | クリティカルパス100% | 中 |
618
+
619
+ ### カバレッジレポートの生成
620
+
621
+ ```bash
622
+ # カバレッジレポート生成
623
+ pnpm test:coverage
624
+
625
+ # カバレッジレポートの確認
626
+ open coverage/index.html
627
+ ```
628
+
629
+ ### カバレッジ確認コマンド
630
+
631
+ ```json
632
+ {
633
+ "scripts": {
634
+ "test": "vitest",
635
+ "test:coverage": "vitest run --coverage",
636
+ "test:watch": "vitest watch"
637
+ }
638
+ }
639
+ ```
640
+
641
+ ---
642
+
643
+ ## 6. テストツール
644
+
645
+ ### テストフレームワーク
646
+
647
+ | ツール | 用途 | 対象 |
648
+ |--------|------|------|
649
+ | Vitest | ユニットテスト、統合テスト | @repo/server-core, apps/* |
650
+ | Playwright (コード) | E2Eテスト(`pnpm test:e2e`で実行) | web, admin |
651
+ | Playwright MCP | Browserテスト(task-qaが実行) | 画面フロー確認 |
652
+ | Testing Library | Reactコンポーネントテスト | web, admin |
653
+
654
+ ### Vitest設定
655
+
656
+ **vitest.config.ts**:
657
+
658
+ ```typescript
659
+ import { defineConfig } from 'vitest/config'
660
+
661
+ export default defineConfig({
662
+ test: {
663
+ globals: true,
664
+ environment: 'node',
665
+ setupFiles: ['./vitest.setup.ts'],
666
+ coverage: {
667
+ provider: 'v8',
668
+ reporter: ['text', 'html', 'json'],
669
+ exclude: [
670
+ 'node_modules/',
671
+ '**/*.test.ts',
672
+ '**/*.spec.ts',
673
+ '**/dist/**',
674
+ '**/.next/**',
675
+ ],
676
+ },
677
+ },
678
+ })
679
+ ```
680
+
681
+ **vitest.setup.ts**:
682
+
683
+ ```typescript
684
+ import { beforeEach } from 'vitest'
685
+ import { prisma } from '@repo/server-core/infrastructure/database/client'
686
+
687
+ // 各テスト前にデータベースをクリーンアップ
688
+ beforeEach(async () => {
689
+ await prisma.post.deleteMany()
690
+ await prisma.session.deleteMany()
691
+ await prisma.account.deleteMany()
692
+ await prisma.user.deleteMany()
693
+ })
694
+ ```
695
+
696
+ ---
697
+
698
+ ## 7. テストの実行
699
+
700
+ ### ローカルでのテスト実行
701
+
702
+ ```bash
703
+ # すべてのユニットテストを実行
704
+ pnpm test
705
+
706
+ # 特定のファイルのみテスト
707
+ pnpm test UserRepository.test.ts
708
+
709
+ # ウォッチモード
710
+ pnpm test:watch
711
+
712
+ # カバレッジ付きでテスト
713
+ pnpm test:coverage
714
+
715
+ # E2Eテスト実行
716
+ pnpm test:e2e
717
+
718
+ # E2Eテスト(UI付き)
719
+ pnpm test:e2e:ui
720
+ ```
721
+
722
+ ### CI/CDでのテスト実行
723
+
724
+ ```yaml
725
+ # .github/workflows/ci.yml
726
+ - name: Run tests
727
+ run: pnpm turbo run test
728
+ env:
729
+ DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
730
+
731
+ - name: Run E2E tests
732
+ run: pnpm test:e2e
733
+ env:
734
+ DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}
735
+
736
+ - name: Upload coverage
737
+ uses: codecov/codecov-action@v3
738
+ with:
739
+ files: ./coverage/coverage-final.json
740
+ ```
741
+
742
+ ---
743
+
744
+ ## 8. モックとスタブ
745
+
746
+ ### Repositoryのモック
747
+
748
+ ```typescript
749
+ // __tests__/mocks/userRepository.mock.ts
750
+ import type { IUserRepository } from '@repo/server-core/domain/repository-interfaces/IUserRepository'
751
+ import type { User } from '@repo/server-core/domain/entities/User'
752
+
753
+ export const createMockUserRepository = (): IUserRepository => ({
754
+ find: vi.fn().mockResolvedValue({
755
+ isSuccess: true,
756
+ value: {
757
+ id: '1',
758
+ email: 'test@example.com',
759
+ name: 'Test User',
760
+ createdAt: new Date(),
761
+ updatedAt: new Date(),
762
+ } as User,
763
+ }),
764
+
765
+ create: vi.fn().mockResolvedValue({
766
+ isSuccess: true,
767
+ value: {
768
+ id: '1',
769
+ email: 'test@example.com',
770
+ name: 'Test User',
771
+ createdAt: new Date(),
772
+ updatedAt: new Date(),
773
+ } as User,
774
+ }),
775
+
776
+ // 他のメソッドも同様にモック
777
+ })
778
+ ```
779
+
780
+ ### UseCaseでのモック使用
781
+
782
+ ```typescript
783
+ // __tests__/use-cases/UserUseCase.test.ts
784
+ import { createMockUserRepository } from '../mocks/userRepository.mock'
785
+ import { userUseCase } from '@repo/server-core/application/use-cases/UserUseCase'
786
+
787
+ describe('UserUseCase', () => {
788
+ it('createメソッドが正しく動作する', async () => {
789
+ // Given: モックRepository
790
+ const mockRepo = createMockUserRepository()
791
+
792
+ // When: UseCase実行(依存性注入)
793
+ const result = await userUseCase.create({
794
+ email: 'test@example.com',
795
+ name: 'Test User',
796
+ })
797
+
798
+ // Then: Repository呼び出しを確認
799
+ expect(mockRepo.create).toHaveBeenCalledWith({
800
+ email: 'test@example.com',
801
+ name: 'Test User',
802
+ })
803
+ })
804
+ })
805
+ ```
806
+
807
+ ---
808
+
809
+ ## まとめ
810
+
811
+ このテスト戦略に従うことで、以下を実現できます:
812
+
813
+ 1. **高品質**: カバレッジ目標達成により、バグの早期発見
814
+ 2. **自動化**: CI/CDパイプラインでの自動テスト実行
815
+ 3. **保守性**: モックとスタブによるテストの独立性確保
816
+ 4. **信頼性**: E2Eテストによる完全なユーザーフロー検証
817
+ 5. **効率性**: Vitestによる高速なテスト実行
818
+
819
+ すべてのテストは、このガイドラインに従って実装してください。