opencode-metis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.md +140 -0
  2. package/dist/cli.cjs +63 -0
  3. package/dist/mcp-server.cjs +51 -0
  4. package/dist/plugin.cjs +4 -0
  5. package/dist/worker.cjs +224 -0
  6. package/opencode/agent/the-analyst/feature-prioritization.md +66 -0
  7. package/opencode/agent/the-analyst/market-research.md +77 -0
  8. package/opencode/agent/the-analyst/project-coordination.md +81 -0
  9. package/opencode/agent/the-analyst/requirements-analysis.md +77 -0
  10. package/opencode/agent/the-architect/compatibility-review.md +138 -0
  11. package/opencode/agent/the-architect/complexity-review.md +137 -0
  12. package/opencode/agent/the-architect/quality-review.md +67 -0
  13. package/opencode/agent/the-architect/security-review.md +127 -0
  14. package/opencode/agent/the-architect/system-architecture.md +119 -0
  15. package/opencode/agent/the-architect/system-documentation.md +83 -0
  16. package/opencode/agent/the-architect/technology-research.md +85 -0
  17. package/opencode/agent/the-chief.md +79 -0
  18. package/opencode/agent/the-designer/accessibility-implementation.md +101 -0
  19. package/opencode/agent/the-designer/design-foundation.md +74 -0
  20. package/opencode/agent/the-designer/interaction-architecture.md +75 -0
  21. package/opencode/agent/the-designer/user-research.md +70 -0
  22. package/opencode/agent/the-meta-agent.md +155 -0
  23. package/opencode/agent/the-platform-engineer/ci-cd-pipelines.md +109 -0
  24. package/opencode/agent/the-platform-engineer/containerization.md +106 -0
  25. package/opencode/agent/the-platform-engineer/data-architecture.md +81 -0
  26. package/opencode/agent/the-platform-engineer/dependency-review.md +144 -0
  27. package/opencode/agent/the-platform-engineer/deployment-automation.md +81 -0
  28. package/opencode/agent/the-platform-engineer/infrastructure-as-code.md +107 -0
  29. package/opencode/agent/the-platform-engineer/performance-tuning.md +82 -0
  30. package/opencode/agent/the-platform-engineer/pipeline-engineering.md +81 -0
  31. package/opencode/agent/the-platform-engineer/production-monitoring.md +105 -0
  32. package/opencode/agent/the-qa-engineer/exploratory-testing.md +66 -0
  33. package/opencode/agent/the-qa-engineer/performance-testing.md +81 -0
  34. package/opencode/agent/the-qa-engineer/quality-assurance.md +77 -0
  35. package/opencode/agent/the-qa-engineer/test-execution.md +66 -0
  36. package/opencode/agent/the-software-engineer/api-development.md +78 -0
  37. package/opencode/agent/the-software-engineer/component-development.md +79 -0
  38. package/opencode/agent/the-software-engineer/concurrency-review.md +141 -0
  39. package/opencode/agent/the-software-engineer/domain-modeling.md +66 -0
  40. package/opencode/agent/the-software-engineer/performance-optimization.md +113 -0
  41. package/opencode/command/analyze.md +149 -0
  42. package/opencode/command/constitution.md +178 -0
  43. package/opencode/command/debug.md +194 -0
  44. package/opencode/command/document.md +178 -0
  45. package/opencode/command/implement.md +225 -0
  46. package/opencode/command/refactor.md +207 -0
  47. package/opencode/command/review.md +229 -0
  48. package/opencode/command/simplify.md +267 -0
  49. package/opencode/command/specify.md +191 -0
  50. package/opencode/command/validate.md +224 -0
  51. package/opencode/skill/accessibility-design/SKILL.md +566 -0
  52. package/opencode/skill/accessibility-design/checklists/wcag-checklist.md +435 -0
  53. package/opencode/skill/agent-coordination/SKILL.md +224 -0
  54. package/opencode/skill/api-contract-design/SKILL.md +550 -0
  55. package/opencode/skill/api-contract-design/templates/graphql-schema-template.md +818 -0
  56. package/opencode/skill/api-contract-design/templates/rest-api-template.md +417 -0
  57. package/opencode/skill/architecture-design/SKILL.md +160 -0
  58. package/opencode/skill/architecture-design/examples/architecture-examples.md +170 -0
  59. package/opencode/skill/architecture-design/template.md +749 -0
  60. package/opencode/skill/architecture-design/validation.md +99 -0
  61. package/opencode/skill/architecture-selection/SKILL.md +522 -0
  62. package/opencode/skill/architecture-selection/examples/adrs/001-example-adr.md +71 -0
  63. package/opencode/skill/architecture-selection/examples/architecture-patterns.md +239 -0
  64. package/opencode/skill/bug-diagnosis/SKILL.md +235 -0
  65. package/opencode/skill/code-quality-review/SKILL.md +337 -0
  66. package/opencode/skill/code-quality-review/examples/anti-patterns.md +629 -0
  67. package/opencode/skill/code-quality-review/reference.md +322 -0
  68. package/opencode/skill/code-review/SKILL.md +363 -0
  69. package/opencode/skill/code-review/reference.md +450 -0
  70. package/opencode/skill/codebase-analysis/SKILL.md +139 -0
  71. package/opencode/skill/codebase-navigation/SKILL.md +227 -0
  72. package/opencode/skill/codebase-navigation/examples/exploration-patterns.md +263 -0
  73. package/opencode/skill/coding-conventions/SKILL.md +178 -0
  74. package/opencode/skill/coding-conventions/checklists/accessibility-checklist.md +176 -0
  75. package/opencode/skill/coding-conventions/checklists/performance-checklist.md +154 -0
  76. package/opencode/skill/coding-conventions/checklists/security-checklist.md +127 -0
  77. package/opencode/skill/constitution-validation/SKILL.md +315 -0
  78. package/opencode/skill/constitution-validation/examples/CONSTITUTION.md +202 -0
  79. package/opencode/skill/constitution-validation/reference/rule-patterns.md +328 -0
  80. package/opencode/skill/constitution-validation/template.md +115 -0
  81. package/opencode/skill/context-preservation/SKILL.md +445 -0
  82. package/opencode/skill/data-modeling/SKILL.md +385 -0
  83. package/opencode/skill/data-modeling/templates/schema-design-template.md +268 -0
  84. package/opencode/skill/deployment-pipeline-design/SKILL.md +579 -0
  85. package/opencode/skill/deployment-pipeline-design/templates/pipeline-template.md +633 -0
  86. package/opencode/skill/documentation-extraction/SKILL.md +259 -0
  87. package/opencode/skill/documentation-sync/SKILL.md +431 -0
  88. package/opencode/skill/domain-driven-design/SKILL.md +509 -0
  89. package/opencode/skill/domain-driven-design/examples/ddd-patterns.md +688 -0
  90. package/opencode/skill/domain-driven-design/reference.md +465 -0
  91. package/opencode/skill/drift-detection/SKILL.md +383 -0
  92. package/opencode/skill/drift-detection/reference.md +340 -0
  93. package/opencode/skill/error-recovery/SKILL.md +162 -0
  94. package/opencode/skill/error-recovery/examples/error-patterns.md +484 -0
  95. package/opencode/skill/feature-prioritization/SKILL.md +419 -0
  96. package/opencode/skill/feature-prioritization/examples/rice-template.md +139 -0
  97. package/opencode/skill/feature-prioritization/reference.md +256 -0
  98. package/opencode/skill/git-workflow/SKILL.md +453 -0
  99. package/opencode/skill/implementation-planning/SKILL.md +215 -0
  100. package/opencode/skill/implementation-planning/examples/phase-examples.md +217 -0
  101. package/opencode/skill/implementation-planning/template.md +220 -0
  102. package/opencode/skill/implementation-planning/validation.md +88 -0
  103. package/opencode/skill/implementation-verification/SKILL.md +272 -0
  104. package/opencode/skill/knowledge-capture/SKILL.md +265 -0
  105. package/opencode/skill/knowledge-capture/reference/knowledge-capture.md +402 -0
  106. package/opencode/skill/knowledge-capture/reference.md +444 -0
  107. package/opencode/skill/knowledge-capture/templates/domain-template.md +325 -0
  108. package/opencode/skill/knowledge-capture/templates/interface-template.md +255 -0
  109. package/opencode/skill/knowledge-capture/templates/pattern-template.md +144 -0
  110. package/opencode/skill/observability-design/SKILL.md +291 -0
  111. package/opencode/skill/observability-design/references/monitoring-patterns.md +461 -0
  112. package/opencode/skill/pattern-detection/SKILL.md +171 -0
  113. package/opencode/skill/pattern-detection/examples/common-patterns.md +359 -0
  114. package/opencode/skill/performance-analysis/SKILL.md +266 -0
  115. package/opencode/skill/performance-analysis/references/profiling-tools.md +499 -0
  116. package/opencode/skill/requirements-analysis/SKILL.md +139 -0
  117. package/opencode/skill/requirements-analysis/examples/good-prd.md +66 -0
  118. package/opencode/skill/requirements-analysis/template.md +177 -0
  119. package/opencode/skill/requirements-analysis/validation.md +69 -0
  120. package/opencode/skill/requirements-elicitation/SKILL.md +518 -0
  121. package/opencode/skill/requirements-elicitation/examples/interview-questions.md +226 -0
  122. package/opencode/skill/requirements-elicitation/examples/user-stories.md +414 -0
  123. package/opencode/skill/safe-refactoring/SKILL.md +312 -0
  124. package/opencode/skill/safe-refactoring/reference/code-smells.md +347 -0
  125. package/opencode/skill/security-assessment/SKILL.md +421 -0
  126. package/opencode/skill/security-assessment/checklists/security-review-checklist.md +285 -0
  127. package/opencode/skill/specification-management/SKILL.md +143 -0
  128. package/opencode/skill/specification-management/readme-template.md +32 -0
  129. package/opencode/skill/specification-management/reference.md +115 -0
  130. package/opencode/skill/specification-management/spec.py +229 -0
  131. package/opencode/skill/specification-validation/SKILL.md +397 -0
  132. package/opencode/skill/specification-validation/reference/3cs-framework.md +306 -0
  133. package/opencode/skill/specification-validation/reference/ambiguity-detection.md +132 -0
  134. package/opencode/skill/specification-validation/reference/constitution-validation.md +301 -0
  135. package/opencode/skill/specification-validation/reference/drift-detection.md +383 -0
  136. package/opencode/skill/task-delegation/SKILL.md +607 -0
  137. package/opencode/skill/task-delegation/examples/file-coordination.md +495 -0
  138. package/opencode/skill/task-delegation/examples/parallel-research.md +337 -0
  139. package/opencode/skill/task-delegation/examples/sequential-build.md +504 -0
  140. package/opencode/skill/task-delegation/reference.md +825 -0
  141. package/opencode/skill/tech-stack-detection/SKILL.md +89 -0
  142. package/opencode/skill/tech-stack-detection/references/framework-signatures.md +598 -0
  143. package/opencode/skill/technical-writing/SKILL.md +190 -0
  144. package/opencode/skill/technical-writing/templates/adr-template.md +205 -0
  145. package/opencode/skill/technical-writing/templates/system-doc-template.md +380 -0
  146. package/opencode/skill/test-design/SKILL.md +464 -0
  147. package/opencode/skill/test-design/examples/test-pyramid.md +724 -0
  148. package/opencode/skill/testing/SKILL.md +213 -0
  149. package/opencode/skill/testing/examples/test-pyramid.md +724 -0
  150. package/opencode/skill/user-insight-synthesis/SKILL.md +576 -0
  151. package/opencode/skill/user-insight-synthesis/templates/research-plan-template.md +217 -0
  152. package/opencode/skill/user-research/SKILL.md +508 -0
  153. package/opencode/skill/user-research/examples/interview-questions.md +265 -0
  154. package/opencode/skill/user-research/examples/personas.md +267 -0
  155. package/opencode/skill/vibe-security/SKILL.md +654 -0
  156. package/package.json +45 -0
@@ -0,0 +1,724 @@
1
+ # Test Pyramid Implementation Guide
2
+
3
+ ## Context
4
+
5
+ This guide provides practical examples for implementing tests at each level of the test pyramid. Use these patterns when designing test suites to achieve optimal coverage with fast feedback loops.
6
+
7
+ ## Test Pyramid Overview
8
+
9
+ ```
10
+ /\
11
+ / \ E2E: 5-10%
12
+ / \ Validate critical user journeys
13
+ /------\ across the entire system
14
+ / \
15
+ / \ Integration: 20-30%
16
+ / \ Verify component interactions
17
+ /--------------\ and external dependencies
18
+ / \
19
+ / Unit: 60-70% \ Test isolated business logic
20
+ /==================\ Fast, deterministic, focused
21
+ ```
22
+
23
+ ### Why This Distribution?
24
+
25
+ | Level | Speed | Stability | Confidence | Cost |
26
+ |-------------|-----------|-----------|------------|---------|
27
+ | Unit | Very Fast | Very High | Low-Medium | Low |
28
+ | Integration | Medium | Medium | Medium | Medium |
29
+ | E2E | Slow | Low | High | High |
30
+
31
+ ## Unit Tests
32
+
33
+ ### Purpose
34
+
35
+ - Test single functions, methods, or classes in isolation
36
+ - Verify business logic correctness
37
+ - Provide fast feedback during development
38
+ - Enable refactoring with confidence
39
+
40
+ ### Characteristics
41
+
42
+ - Execute in under 100ms
43
+ - No external dependencies (database, network, filesystem)
44
+ - Deterministic (same input always produces same output)
45
+ - Can run in parallel without interference
46
+
47
+ ### Jest Example: Order Total Calculation
48
+
49
+ ```typescript
50
+ // src/domain/Order.ts
51
+ export class Order {
52
+ private items: OrderItem[] = [];
53
+
54
+ addItem(item: OrderItem): void {
55
+ this.items.push(item);
56
+ }
57
+
58
+ get subtotal(): number {
59
+ return this.items.reduce(
60
+ (sum, item) => sum + item.price * item.quantity,
61
+ 0
62
+ );
63
+ }
64
+
65
+ get discount(): number {
66
+ if (this.subtotal >= 100) return this.subtotal * 0.1;
67
+ if (this.subtotal >= 50) return this.subtotal * 0.05;
68
+ return 0;
69
+ }
70
+
71
+ get total(): number {
72
+ return this.subtotal - this.discount;
73
+ }
74
+ }
75
+
76
+ // src/domain/Order.test.ts
77
+ describe('Order', () => {
78
+ describe('subtotal', () => {
79
+ it('returns zero for empty order', () => {
80
+ const order = new Order();
81
+
82
+ expect(order.subtotal).toBe(0);
83
+ });
84
+
85
+ it('sums item prices multiplied by quantities', () => {
86
+ const order = new Order();
87
+ order.addItem({ sku: 'A', price: 10, quantity: 2 });
88
+ order.addItem({ sku: 'B', price: 25, quantity: 1 });
89
+
90
+ expect(order.subtotal).toBe(45);
91
+ });
92
+ });
93
+
94
+ describe('discount', () => {
95
+ it('returns zero when subtotal is under 50', () => {
96
+ const order = new Order();
97
+ order.addItem({ sku: 'A', price: 10, quantity: 4 });
98
+
99
+ expect(order.discount).toBe(0);
100
+ });
101
+
102
+ it('applies 5% discount when subtotal is 50 or more', () => {
103
+ const order = new Order();
104
+ order.addItem({ sku: 'A', price: 50, quantity: 1 });
105
+
106
+ expect(order.discount).toBe(2.5);
107
+ });
108
+
109
+ it('applies 10% discount when subtotal is 100 or more', () => {
110
+ const order = new Order();
111
+ order.addItem({ sku: 'A', price: 100, quantity: 1 });
112
+
113
+ expect(order.discount).toBe(10);
114
+ });
115
+ });
116
+
117
+ describe('total', () => {
118
+ it('returns subtotal minus discount', () => {
119
+ const order = new Order();
120
+ order.addItem({ sku: 'A', price: 50, quantity: 2 });
121
+
122
+ expect(order.total).toBe(90); // 100 - 10% discount
123
+ });
124
+ });
125
+ });
126
+ ```
127
+
128
+ ### Pytest Example: Password Validation
129
+
130
+ ```python
131
+ # src/domain/password_validator.py
132
+ import re
133
+ from dataclasses import dataclass
134
+
135
+ @dataclass
136
+ class ValidationResult:
137
+ valid: bool
138
+ errors: list[str]
139
+
140
+ class PasswordValidator:
141
+ MIN_LENGTH = 8
142
+ MAX_LENGTH = 128
143
+
144
+ def validate(self, password: str) -> ValidationResult:
145
+ errors = []
146
+
147
+ if len(password) < self.MIN_LENGTH:
148
+ errors.append(f"Password must be at least {self.MIN_LENGTH} characters")
149
+
150
+ if len(password) > self.MAX_LENGTH:
151
+ errors.append(f"Password must not exceed {self.MAX_LENGTH} characters")
152
+
153
+ if not re.search(r'[A-Z]', password):
154
+ errors.append("Password must contain at least one uppercase letter")
155
+
156
+ if not re.search(r'[a-z]', password):
157
+ errors.append("Password must contain at least one lowercase letter")
158
+
159
+ if not re.search(r'\d', password):
160
+ errors.append("Password must contain at least one digit")
161
+
162
+ if not re.search(r'[!@#$%^&*]', password):
163
+ errors.append("Password must contain at least one special character")
164
+
165
+ return ValidationResult(valid=len(errors) == 0, errors=errors)
166
+
167
+ # tests/domain/test_password_validator.py
168
+ import pytest
169
+ from src.domain.password_validator import PasswordValidator, ValidationResult
170
+
171
+ class TestPasswordValidator:
172
+ @pytest.fixture
173
+ def validator(self):
174
+ return PasswordValidator()
175
+
176
+ def test_accepts_valid_password(self, validator):
177
+ result = validator.validate("SecurePass1!")
178
+
179
+ assert result.valid is True
180
+ assert result.errors == []
181
+
182
+ @pytest.mark.parametrize("password,expected_error", [
183
+ ("Short1!", "Password must be at least 8 characters"),
184
+ ("a" * 129 + "A1!", "Password must not exceed 128 characters"),
185
+ ("lowercase1!", "Password must contain at least one uppercase letter"),
186
+ ("UPPERCASE1!", "Password must contain at least one lowercase letter"),
187
+ ("NoDigits!", "Password must contain at least one digit"),
188
+ ("NoSpecial1", "Password must contain at least one special character"),
189
+ ])
190
+ def test_rejects_invalid_passwords(self, validator, password, expected_error):
191
+ result = validator.validate(password)
192
+
193
+ assert result.valid is False
194
+ assert expected_error in result.errors
195
+
196
+ def test_collects_multiple_validation_errors(self, validator):
197
+ result = validator.validate("bad")
198
+
199
+ assert result.valid is False
200
+ assert len(result.errors) == 5 # Too short + missing uppercase + lowercase + digit + special
201
+ ```
202
+
203
+ ## Integration Tests
204
+
205
+ ### Purpose
206
+
207
+ - Verify interactions between components
208
+ - Test database operations and queries
209
+ - Validate API contracts and responses
210
+ - Ensure external service integrations work correctly
211
+
212
+ ### Characteristics
213
+
214
+ - Execute in 1-5 seconds
215
+ - May use real databases (often in-memory or containers)
216
+ - Test multiple units working together
217
+ - Verify data persistence and retrieval
218
+
219
+ ### Jest Example: User Repository with Database
220
+
221
+ ```typescript
222
+ // tests/integration/repositories/UserRepository.test.ts
223
+ import { UserRepository } from '@/infrastructure/UserRepository';
224
+ import { PrismaClient } from '@prisma/client';
225
+ import { execSync } from 'child_process';
226
+
227
+ describe('UserRepository', () => {
228
+ let prisma: PrismaClient;
229
+ let repository: UserRepository;
230
+
231
+ beforeAll(async () => {
232
+ // Use test database
233
+ process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test_db';
234
+ prisma = new PrismaClient();
235
+ await prisma.$connect();
236
+ });
237
+
238
+ afterAll(async () => {
239
+ await prisma.$disconnect();
240
+ });
241
+
242
+ beforeEach(async () => {
243
+ // Clean database before each test
244
+ await prisma.user.deleteMany();
245
+ repository = new UserRepository(prisma);
246
+ });
247
+
248
+ describe('create', () => {
249
+ it('persists user to database and returns with generated id', async () => {
250
+ const userData = {
251
+ email: 'test@example.com',
252
+ name: 'Test User',
253
+ hashedPassword: 'hashed123',
254
+ };
255
+
256
+ const created = await repository.create(userData);
257
+
258
+ expect(created.id).toBeDefined();
259
+ expect(created.email).toBe(userData.email);
260
+
261
+ // Verify persistence
262
+ const found = await prisma.user.findUnique({
263
+ where: { id: created.id },
264
+ });
265
+ expect(found).not.toBeNull();
266
+ expect(found?.email).toBe(userData.email);
267
+ });
268
+
269
+ it('throws ConflictError when email already exists', async () => {
270
+ const userData = {
271
+ email: 'duplicate@example.com',
272
+ name: 'First User',
273
+ hashedPassword: 'hashed123',
274
+ };
275
+ await repository.create(userData);
276
+
277
+ await expect(repository.create({
278
+ ...userData,
279
+ name: 'Second User',
280
+ })).rejects.toThrow('User with this email already exists');
281
+ });
282
+ });
283
+
284
+ describe('findByEmail', () => {
285
+ it('returns user when found', async () => {
286
+ const created = await repository.create({
287
+ email: 'findme@example.com',
288
+ name: 'Find Me',
289
+ hashedPassword: 'hashed123',
290
+ });
291
+
292
+ const found = await repository.findByEmail('findme@example.com');
293
+
294
+ expect(found).toEqual(created);
295
+ });
296
+
297
+ it('returns null when not found', async () => {
298
+ const found = await repository.findByEmail('nonexistent@example.com');
299
+
300
+ expect(found).toBeNull();
301
+ });
302
+ });
303
+ });
304
+ ```
305
+
306
+ ### Pytest Example: API Integration Tests
307
+
308
+ ```python
309
+ # tests/integration/api/test_users_api.py
310
+ import pytest
311
+ from fastapi.testclient import TestClient
312
+ from sqlalchemy import create_engine
313
+ from sqlalchemy.orm import sessionmaker
314
+
315
+ from src.main import app
316
+ from src.database import Base, get_db
317
+
318
+ # Test database setup
319
+ TEST_DATABASE_URL = "sqlite:///./test.db"
320
+ engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False})
321
+ TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
322
+
323
+ @pytest.fixture(scope="function")
324
+ def db_session():
325
+ Base.metadata.create_all(bind=engine)
326
+ session = TestingSessionLocal()
327
+ try:
328
+ yield session
329
+ finally:
330
+ session.close()
331
+ Base.metadata.drop_all(bind=engine)
332
+
333
+ @pytest.fixture(scope="function")
334
+ def client(db_session):
335
+ def override_get_db():
336
+ yield db_session
337
+
338
+ app.dependency_overrides[get_db] = override_get_db
339
+ with TestClient(app) as test_client:
340
+ yield test_client
341
+ app.dependency_overrides.clear()
342
+
343
+ class TestUsersAPI:
344
+ def test_create_user_returns_201_with_user_data(self, client):
345
+ response = client.post("/api/users", json={
346
+ "email": "new@user.com",
347
+ "password": "SecurePass1!",
348
+ "name": "New User"
349
+ })
350
+
351
+ assert response.status_code == 201
352
+ data = response.json()
353
+ assert data["email"] == "new@user.com"
354
+ assert data["name"] == "New User"
355
+ assert "id" in data
356
+ assert "password" not in data # Should not expose password
357
+
358
+ def test_create_user_returns_400_for_invalid_email(self, client):
359
+ response = client.post("/api/users", json={
360
+ "email": "invalid-email",
361
+ "password": "SecurePass1!",
362
+ "name": "New User"
363
+ })
364
+
365
+ assert response.status_code == 400
366
+ assert "email" in response.json()["detail"].lower()
367
+
368
+ def test_create_user_returns_409_for_duplicate_email(self, client):
369
+ user_data = {
370
+ "email": "duplicate@user.com",
371
+ "password": "SecurePass1!",
372
+ "name": "First User"
373
+ }
374
+ client.post("/api/users", json=user_data)
375
+
376
+ response = client.post("/api/users", json=user_data)
377
+
378
+ assert response.status_code == 409
379
+
380
+ def test_get_user_returns_200_when_found(self, client):
381
+ create_response = client.post("/api/users", json={
382
+ "email": "existing@user.com",
383
+ "password": "SecurePass1!",
384
+ "name": "Existing User"
385
+ })
386
+ user_id = create_response.json()["id"]
387
+
388
+ response = client.get(f"/api/users/{user_id}")
389
+
390
+ assert response.status_code == 200
391
+ assert response.json()["email"] == "existing@user.com"
392
+
393
+ def test_get_user_returns_404_when_not_found(self, client):
394
+ response = client.get("/api/users/nonexistent-id")
395
+
396
+ assert response.status_code == 404
397
+
398
+ def test_list_users_returns_paginated_results(self, client):
399
+ # Create 15 users
400
+ for i in range(15):
401
+ client.post("/api/users", json={
402
+ "email": f"user{i}@example.com",
403
+ "password": "SecurePass1!",
404
+ "name": f"User {i}"
405
+ })
406
+
407
+ response = client.get("/api/users?page=1&limit=10")
408
+
409
+ assert response.status_code == 200
410
+ data = response.json()
411
+ assert len(data["items"]) == 10
412
+ assert data["total"] == 15
413
+ assert data["page"] == 1
414
+ assert data["pages"] == 2
415
+ ```
416
+
417
+ ## E2E Tests
418
+
419
+ ### Purpose
420
+
421
+ - Validate critical user journeys through the entire system
422
+ - Ensure the application works from the user's perspective
423
+ - Catch integration issues that other tests miss
424
+ - Verify deployment and infrastructure configuration
425
+
426
+ ### Characteristics
427
+
428
+ - Execute in 10-60 seconds
429
+ - Run against deployed application (staging or local)
430
+ - Use real browser or API client
431
+ - Test complete user workflows
432
+
433
+ ### Playwright Example: User Registration Flow
434
+
435
+ ```typescript
436
+ // tests/e2e/user-registration.spec.ts
437
+ import { test, expect } from '@playwright/test';
438
+
439
+ test.describe('User Registration', () => {
440
+ test.beforeEach(async ({ page }) => {
441
+ await page.goto('/register');
442
+ });
443
+
444
+ test('allows new user to register and login', async ({ page }) => {
445
+ const uniqueEmail = `test-${Date.now()}@example.com`;
446
+
447
+ // Fill registration form
448
+ await page.getByLabel('Email').fill(uniqueEmail);
449
+ await page.getByLabel('Password').fill('SecurePass1!');
450
+ await page.getByLabel('Confirm Password').fill('SecurePass1!');
451
+ await page.getByLabel('Full Name').fill('Test User');
452
+ await page.getByRole('button', { name: 'Create Account' }).click();
453
+
454
+ // Verify redirect to login with success message
455
+ await expect(page).toHaveURL('/login');
456
+ await expect(page.getByText('Account created successfully')).toBeVisible();
457
+
458
+ // Login with new credentials
459
+ await page.getByLabel('Email').fill(uniqueEmail);
460
+ await page.getByLabel('Password').fill('SecurePass1!');
461
+ await page.getByRole('button', { name: 'Sign In' }).click();
462
+
463
+ // Verify logged in state
464
+ await expect(page).toHaveURL('/dashboard');
465
+ await expect(page.getByText('Welcome, Test User')).toBeVisible();
466
+ });
467
+
468
+ test('shows validation errors for invalid input', async ({ page }) => {
469
+ // Submit with empty form
470
+ await page.getByRole('button', { name: 'Create Account' }).click();
471
+
472
+ // Verify validation messages
473
+ await expect(page.getByText('Email is required')).toBeVisible();
474
+ await expect(page.getByText('Password is required')).toBeVisible();
475
+ });
476
+
477
+ test('prevents registration with existing email', async ({ page }) => {
478
+ // Attempt to register with known existing email
479
+ await page.getByLabel('Email').fill('existing@user.com');
480
+ await page.getByLabel('Password').fill('SecurePass1!');
481
+ await page.getByLabel('Confirm Password').fill('SecurePass1!');
482
+ await page.getByLabel('Full Name').fill('Another User');
483
+ await page.getByRole('button', { name: 'Create Account' }).click();
484
+
485
+ // Verify error message
486
+ await expect(page.getByText('An account with this email already exists')).toBeVisible();
487
+ });
488
+ });
489
+ ```
490
+
491
+ ### Cypress Example: E-commerce Checkout
492
+
493
+ ```typescript
494
+ // cypress/e2e/checkout.cy.ts
495
+ describe('Checkout Flow', () => {
496
+ beforeEach(() => {
497
+ cy.loginAs('test@customer.com');
498
+ cy.clearCart();
499
+ });
500
+
501
+ it('completes purchase with valid payment', () => {
502
+ // Add items to cart
503
+ cy.visit('/products/widget-pro');
504
+ cy.get('[data-testid="add-to-cart"]').click();
505
+ cy.get('[data-testid="cart-count"]').should('contain', '1');
506
+
507
+ // Proceed to checkout
508
+ cy.visit('/cart');
509
+ cy.get('[data-testid="checkout-button"]').click();
510
+
511
+ // Fill shipping address
512
+ cy.get('#shipping-address').type('123 Test Street');
513
+ cy.get('#shipping-city').type('Test City');
514
+ cy.get('#shipping-zip').type('12345');
515
+ cy.get('[data-testid="continue-to-payment"]').click();
516
+
517
+ // Fill payment (test card)
518
+ cy.getStripeElement('cardNumber').type('4242424242424242');
519
+ cy.getStripeElement('cardExpiry').type('1225');
520
+ cy.getStripeElement('cardCvc').type('123');
521
+ cy.get('[data-testid="place-order"]').click();
522
+
523
+ // Verify success
524
+ cy.url().should('include', '/order-confirmation');
525
+ cy.get('[data-testid="order-number"]').should('exist');
526
+ cy.get('[data-testid="order-total"]').should('contain', '$49.99');
527
+
528
+ // Verify email sent (via test mailbox API)
529
+ cy.task('getLastEmail', 'test@customer.com').then((email) => {
530
+ expect(email.subject).to.include('Order Confirmation');
531
+ });
532
+ });
533
+
534
+ it('handles payment failure gracefully', () => {
535
+ cy.visit('/products/widget-pro');
536
+ cy.get('[data-testid="add-to-cart"]').click();
537
+ cy.visit('/cart');
538
+ cy.get('[data-testid="checkout-button"]').click();
539
+
540
+ // Fill shipping
541
+ cy.get('#shipping-address').type('123 Test Street');
542
+ cy.get('#shipping-city').type('Test City');
543
+ cy.get('#shipping-zip').type('12345');
544
+ cy.get('[data-testid="continue-to-payment"]').click();
545
+
546
+ // Use declining test card
547
+ cy.getStripeElement('cardNumber').type('4000000000000002');
548
+ cy.getStripeElement('cardExpiry').type('1225');
549
+ cy.getStripeElement('cardCvc').type('123');
550
+ cy.get('[data-testid="place-order"]').click();
551
+
552
+ // Verify error handling
553
+ cy.get('[data-testid="payment-error"]')
554
+ .should('contain', 'Your card was declined');
555
+ cy.url().should('include', '/checkout/payment');
556
+ cy.get('[data-testid="cart-count"]').should('contain', '1');
557
+ });
558
+ });
559
+ ```
560
+
561
+ ## Test Selection Guidelines
562
+
563
+ ### When to Write Unit Tests
564
+
565
+ - Pure functions with business logic
566
+ - Data transformations and calculations
567
+ - Validation and parsing logic
568
+ - State machines and reducers
569
+ - Utility and helper functions
570
+
571
+ ### When to Write Integration Tests
572
+
573
+ - Database queries and transactions
574
+ - API endpoints and middleware
575
+ - Service-to-service communication
576
+ - Message queue producers/consumers
577
+ - Cache operations
578
+
579
+ ### When to Write E2E Tests
580
+
581
+ - User registration and authentication
582
+ - Payment and checkout flows
583
+ - Critical business workflows
584
+ - Multi-step forms and wizards
585
+ - Features involving multiple services
586
+
587
+ ## Variations
588
+
589
+ ### Contract Testing (Alternative to E2E)
590
+
591
+ For microservices, consider contract tests instead of E2E:
592
+
593
+ ```typescript
594
+ // consumer/tests/UserServiceContract.test.ts
595
+ import { Pact } from '@pact-foundation/pact';
596
+
597
+ const provider = new Pact({
598
+ consumer: 'OrderService',
599
+ provider: 'UserService',
600
+ });
601
+
602
+ describe('UserService Contract', () => {
603
+ it('returns user details for valid user id', async () => {
604
+ await provider.addInteraction({
605
+ state: 'user 123 exists',
606
+ uponReceiving: 'a request for user 123',
607
+ withRequest: {
608
+ method: 'GET',
609
+ path: '/users/123',
610
+ },
611
+ willRespondWith: {
612
+ status: 200,
613
+ body: {
614
+ id: '123',
615
+ email: Matchers.email(),
616
+ name: Matchers.string(),
617
+ },
618
+ },
619
+ });
620
+
621
+ const client = new UserServiceClient(provider.mockService.baseUrl);
622
+ const user = await client.getUser('123');
623
+
624
+ expect(user.id).toBe('123');
625
+ });
626
+ });
627
+ ```
628
+
629
+ ### Visual Regression Testing
630
+
631
+ For UI-heavy applications:
632
+
633
+ ```typescript
634
+ // tests/visual/Button.visual.test.ts
635
+ import { test, expect } from '@playwright/test';
636
+
637
+ test.describe('Button Visual Regression', () => {
638
+ test('primary button states', async ({ page }) => {
639
+ await page.goto('/storybook/button--primary');
640
+
641
+ // Default state
642
+ await expect(page.locator('.button')).toHaveScreenshot('button-default.png');
643
+
644
+ // Hover state
645
+ await page.locator('.button').hover();
646
+ await expect(page.locator('.button')).toHaveScreenshot('button-hover.png');
647
+
648
+ // Focus state
649
+ await page.locator('.button').focus();
650
+ await expect(page.locator('.button')).toHaveScreenshot('button-focus.png');
651
+ });
652
+ });
653
+ ```
654
+
655
+ ## Anti-Patterns
656
+
657
+ ### Testing at Wrong Level
658
+
659
+ ```typescript
660
+ // WRONG: Using E2E test for simple calculation
661
+ test('calculates discount correctly', async ({ page }) => {
662
+ await page.goto('/cart');
663
+ await page.fill('#quantity', '10');
664
+ await expect(page.locator('#discount')).toHaveText('10%');
665
+ });
666
+
667
+ // CORRECT: Unit test for calculation logic
668
+ test('applies 10% discount for orders of 10+ items', () => {
669
+ expect(calculateDiscount(10)).toBe(0.1);
670
+ });
671
+ ```
672
+
673
+ ### Missing Critical Paths
674
+
675
+ ```
676
+ // WRONG: Skipping E2E for checkout
677
+ Unit tests: 95% coverage
678
+ Integration tests: API endpoints tested
679
+ E2E tests: None for payment flow
680
+
681
+ // Payment bugs slip through because the complete flow
682
+ // (UI -> API -> Payment Provider -> Webhook -> DB) is never tested
683
+ ```
684
+
685
+ ### Excessive E2E Tests
686
+
687
+ ```
688
+ // WRONG: E2E test for every validation rule
689
+ e2e/
690
+ registration-empty-email.spec.ts
691
+ registration-invalid-email.spec.ts
692
+ registration-short-password.spec.ts
693
+ registration-no-uppercase.spec.ts
694
+ registration-no-number.spec.ts
695
+ ... 20 more files
696
+
697
+ // CORRECT: Unit tests for validation, single E2E for happy path
698
+ unit/
699
+ PasswordValidator.test.ts // All validation rules
700
+ e2e/
701
+ registration.spec.ts // One happy path + one error case
702
+ ```
703
+
704
+ ### Ignoring Test Pyramid Distribution
705
+
706
+ ```
707
+ // WRONG: Inverted pyramid
708
+ E2E Tests: 500 (slow, flaky)
709
+ Integration Tests: 50
710
+ Unit Tests: 100
711
+
712
+ // Build time: 45 minutes
713
+ // Flaky test rate: 15%
714
+ // Developer confidence: Low
715
+
716
+ // CORRECT: Following pyramid
717
+ Unit Tests: 2000 (fast, stable)
718
+ Integration Tests: 300
719
+ E2E Tests: 50
720
+
721
+ // Build time: 8 minutes
722
+ // Flaky test rate: 1%
723
+ // Developer confidence: High
724
+ ```