codecruise 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 (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/bin/codecruise.js +68 -0
  4. package/config/CLAUDE.md +107 -0
  5. package/config/agents/analyst.md +48 -0
  6. package/config/agents/architect-reviewer.md +161 -0
  7. package/config/agents/architect.md +119 -0
  8. package/config/agents/critic.md +63 -0
  9. package/config/agents/developer.md +96 -0
  10. package/config/agents/devops.md +81 -0
  11. package/config/agents/orchestrator.md +91 -0
  12. package/config/agents/planner.md +139 -0
  13. package/config/agents/retro.md +52 -0
  14. package/config/agents/reviewer.md +101 -0
  15. package/config/agents/security-reviewer.md +57 -0
  16. package/config/agents/stack/expo/AGENT.md +473 -0
  17. package/config/agents/stack/expo/rules/critical.md +427 -0
  18. package/config/agents/stack/expo/rules/native.md +455 -0
  19. package/config/agents/stack/expo/rules/navigation.md +445 -0
  20. package/config/agents/stack/expo/rules/performance.md +415 -0
  21. package/config/agents/stack/fastify/AGENT.md +397 -0
  22. package/config/agents/stack/fastify/rules/api-design.md +283 -0
  23. package/config/agents/stack/fastify/rules/critical.md +232 -0
  24. package/config/agents/stack/fastify/rules/queues.md +303 -0
  25. package/config/agents/stack/fastify/rules/security.md +384 -0
  26. package/config/agents/stack/index.yaml +48 -0
  27. package/config/agents/stack/nextjs/AGENT.md +421 -0
  28. package/config/agents/stack/nextjs/rules/components.md +413 -0
  29. package/config/agents/stack/nextjs/rules/critical.md +391 -0
  30. package/config/agents/stack/nextjs/rules/performance.md +403 -0
  31. package/config/agents/stack/nextjs/rules/styling.md +334 -0
  32. package/config/agents/stack/shared-ts/AGENT.md +384 -0
  33. package/config/agents/stack/shared-ts/rules/critical.md +315 -0
  34. package/config/agents/stack/shared-ts/rules/patterns.md +384 -0
  35. package/config/agents/stack/shared-ts/rules/zod.md +427 -0
  36. package/config/agents/tester.md +79 -0
  37. package/config/commands/architect-discuss.md +366 -0
  38. package/config/commands/architect-list.md +160 -0
  39. package/config/commands/architect-review.md +111 -0
  40. package/config/commands/architect.md +118 -0
  41. package/config/commands/compact.md +118 -0
  42. package/config/commands/companion.md +279 -0
  43. package/config/commands/dashboard.md +152 -0
  44. package/config/commands/doctor.md +227 -0
  45. package/config/commands/dogfood-report.md +101 -0
  46. package/config/commands/flags/run-autonomous.md +110 -0
  47. package/config/commands/flags/run-pause.md +80 -0
  48. package/config/commands/ingest.md +173 -0
  49. package/config/commands/init.md +128 -0
  50. package/config/commands/metrics.md +87 -0
  51. package/config/commands/parallel.md +320 -0
  52. package/config/commands/pause.md +55 -0
  53. package/config/commands/plan-review.md +130 -0
  54. package/config/commands/plan.md +216 -0
  55. package/config/commands/production-check.md +308 -0
  56. package/config/commands/refine.md +323 -0
  57. package/config/commands/resume.md +72 -0
  58. package/config/commands/retro.md +121 -0
  59. package/config/commands/retry.md +75 -0
  60. package/config/commands/role.md +310 -0
  61. package/config/commands/run.md +417 -0
  62. package/config/commands/scope.md +85 -0
  63. package/config/commands/setup-permissions.md +104 -0
  64. package/config/commands/skip.md +75 -0
  65. package/config/commands/spec-forge.md +213 -0
  66. package/config/commands/spec-help.md +194 -0
  67. package/config/commands/spec-patch.md +342 -0
  68. package/config/commands/spec-resolve.md +110 -0
  69. package/config/commands/spec-review.md +153 -0
  70. package/config/commands/status.md +114 -0
  71. package/config/commands/sync.md +131 -0
  72. package/config/commands/task.md +138 -0
  73. package/config/commands/verify.md +124 -0
  74. package/config/hooks/README.md +632 -0
  75. package/config/hooks/activity-log.sh +187 -0
  76. package/config/hooks/anti-rationalize.sh +52 -0
  77. package/config/hooks/capture-verification.sh +112 -0
  78. package/config/hooks/collect-metrics.sh +135 -0
  79. package/config/hooks/enforce-file-scope.sh +75 -0
  80. package/config/hooks/enforce-state-machine.sh +161 -0
  81. package/config/hooks/enforce-tdd.sh +180 -0
  82. package/config/hooks/format.sh +40 -0
  83. package/config/hooks/lib/activity-helpers.sh +162 -0
  84. package/config/hooks/lib/read-settings.sh +71 -0
  85. package/config/hooks/load-context-skills.sh +95 -0
  86. package/config/hooks/notify.sh +81 -0
  87. package/config/hooks/pre-commit.sample +35 -0
  88. package/config/hooks/protect-files.sh +63 -0
  89. package/config/hooks/track-agents.sh +41 -0
  90. package/config/hooks/track-commands.sh +37 -0
  91. package/config/hooks/track-enforcement.sh +44 -0
  92. package/config/hooks/track-ooda.sh +77 -0
  93. package/config/hooks/validate-commit-msg.sh +35 -0
  94. package/config/hooks/validate-plan.sh +213 -0
  95. package/config/hooks/verify-criteria.sh +46 -0
  96. package/config/hooks/verify-todo-completion.sh +140 -0
  97. package/config/rules/comments.md +25 -0
  98. package/config/rules/decision-rules.md +308 -0
  99. package/config/rules/hygiene.md +247 -0
  100. package/config/rules/pattern-detection.md +372 -0
  101. package/config/rules/profiles.md +193 -0
  102. package/config/rules/recovery.md +83 -0
  103. package/config/rules/scope-detection.md +213 -0
  104. package/config/rules/standards.md +127 -0
  105. package/config/rules/workflow.md +121 -0
  106. package/config/schemas.md +767 -0
  107. package/config/settings.json +195 -0
  108. package/config/skills/backend/SKILL.md +734 -0
  109. package/config/skills/database/SKILL.md +426 -0
  110. package/config/skills/frontend/SKILL.md +434 -0
  111. package/config/skills/git/SKILL.md +396 -0
  112. package/config/skills/index.yaml +36 -0
  113. package/config/skills/observability/SKILL.md +430 -0
  114. package/config/skills/package-dev/SKILL.md +498 -0
  115. package/config/skills/performance/SKILL.md +378 -0
  116. package/config/skills/resilience/SKILL.md +573 -0
  117. package/config/skills/testing/SKILL.md +398 -0
  118. package/config/skills/testing-patterns/SKILL.md +276 -0
  119. package/config/skills/typescript/SKILL.md +152 -0
  120. package/config/templates/CLAUDE.md +70 -0
  121. package/config/templates/README.md +117 -0
  122. package/config/templates/steering/adr-template.md +102 -0
  123. package/config/templates/steering/product.md +60 -0
  124. package/config/templates/steering/rfc-template.md +159 -0
  125. package/config/templates/steering/structure.md +146 -0
  126. package/config/templates/steering/tech.md +85 -0
  127. package/package.json +40 -0
  128. package/src/install.js +163 -0
  129. package/src/report.js +310 -0
@@ -0,0 +1,398 @@
1
+ ---
2
+ name: testing
3
+ description: TDD workflow, unit, integration, and E2E testing
4
+ version: 1.0.0
5
+ triggers:
6
+ - test
7
+ - TDD
8
+ - vitest
9
+ - jest
10
+ - playwright
11
+ - spec
12
+ - mock
13
+ ---
14
+
15
+ # Testing Skill
16
+
17
+ TDD workflow and comprehensive testing patterns.
18
+
19
+ ## Quick Reference
20
+
21
+ ### TDD Workflow
22
+
23
+ | ID | Rule | Priority |
24
+ |----|------|----------|
25
+ | tdd-1 | Write failing test BEFORE implementation | CRITICAL |
26
+ | tdd-2 | Write minimal code to pass the test | CRITICAL |
27
+ | tdd-3 | Refactor only after tests pass | CRITICAL |
28
+ | tdd-4 | One logical assertion per test | HIGH |
29
+ | tdd-5 | Test behavior, not implementation | HIGH |
30
+ | tdd-6 | Name tests as behavior descriptions | HIGH |
31
+ | tdd-7 | Use Arrange-Act-Assert pattern | HIGH |
32
+ | tdd-8 | Keep tests deterministic (no randomness) | CRITICAL |
33
+
34
+ ### Unit Testing
35
+
36
+ | ID | Rule | Priority |
37
+ |----|------|----------|
38
+ | unit-1 | Test pure functions first | HIGH |
39
+ | unit-2 | Mock external dependencies | HIGH |
40
+ | unit-3 | Use test.each for parameterized tests | MEDIUM |
41
+ | unit-4 | Test edge cases explicitly | HIGH |
42
+ | unit-5 | Test error conditions | HIGH |
43
+ | unit-6 | Keep tests under 20 lines | MEDIUM |
44
+ | unit-7 | No logic in tests (no if/loops) | HIGH |
45
+
46
+ ### Component Testing
47
+
48
+ | ID | Rule | Priority |
49
+ |----|------|----------|
50
+ | comp-1 | Query by role, label, or text | CRITICAL |
51
+ | comp-2 | Use userEvent, not fireEvent | HIGH |
52
+ | comp-3 | Test user interactions, not state | HIGH |
53
+ | comp-4 | Test accessibility (roles, labels) | HIGH |
54
+ | comp-5 | Test loading and error states | HIGH |
55
+ | comp-6 | Avoid snapshot tests | MEDIUM |
56
+ | comp-7 | Use findBy for async content | HIGH |
57
+
58
+ ### E2E Testing
59
+
60
+ | ID | Rule | Priority |
61
+ |----|------|----------|
62
+ | e2e-1 | Test critical user journeys | HIGH |
63
+ | e2e-2 | Use Page Object Model | HIGH |
64
+ | e2e-3 | Keep E2E tests minimal and focused | HIGH |
65
+ | e2e-4 | Run E2E in CI with retries | MEDIUM |
66
+ | e2e-5 | Use stable selectors (data-testid) | MEDIUM |
67
+
68
+ ---
69
+
70
+ ## TDD Cycle
71
+
72
+ ### tdd-1, tdd-2, tdd-3: RED → GREEN → REFACTOR
73
+
74
+ ```typescript
75
+ // 1. RED: Write failing test
76
+ describe('formatCurrency', () => {
77
+ it('should format positive numbers with $ symbol', () => {
78
+ expect(formatCurrency(1234.56)).toBe('$1,234.56');
79
+ });
80
+ });
81
+
82
+ // Run test → FAILS (function doesn't exist)
83
+
84
+ // 2. GREEN: Minimal implementation
85
+ function formatCurrency(amount: number): string {
86
+ return `$${amount.toLocaleString('en-US', {
87
+ minimumFractionDigits: 2
88
+ })}`;
89
+ }
90
+
91
+ // Run test → PASSES
92
+
93
+ // 3. REFACTOR: Clean up (tests still pass)
94
+ const formatter = new Intl.NumberFormat('en-US', {
95
+ style: 'currency',
96
+ currency: 'USD',
97
+ });
98
+
99
+ function formatCurrency(amount: number): string {
100
+ return formatter.format(amount);
101
+ }
102
+
103
+ // Run test → STILL PASSES
104
+ ```
105
+
106
+ ### tdd-7: Arrange-Act-Assert
107
+
108
+ ```typescript
109
+ it('should create user with hashed password', async () => {
110
+ // Arrange
111
+ const input = { email: 'test@example.com', password: 'secret123' };
112
+
113
+ // Act
114
+ const user = await createUser(input);
115
+
116
+ // Assert
117
+ expect(user.email).toBe('test@example.com');
118
+ expect(user.password).not.toBe('secret123');
119
+ expect(user.password).toHaveLength(60); // bcrypt hash
120
+ });
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Unit Testing
126
+
127
+ ### unit-3: Parameterized Tests
128
+
129
+ ```typescript
130
+ describe('validateEmail', () => {
131
+ it.each([
132
+ ['user@example.com', true],
133
+ ['user+tag@example.com', true],
134
+ ['invalid', false],
135
+ ['@example.com', false],
136
+ ['user@', false],
137
+ ])('validateEmail(%s) should return %s', (email, expected) => {
138
+ expect(validateEmail(email)).toBe(expected);
139
+ });
140
+ });
141
+ ```
142
+
143
+ ### Mocking
144
+
145
+ ```typescript
146
+ // Mock module
147
+ vi.mock('@/lib/database', () => ({
148
+ db: {
149
+ user: {
150
+ findUnique: vi.fn(),
151
+ create: vi.fn(),
152
+ },
153
+ },
154
+ }));
155
+
156
+ import { db } from '@/lib/database';
157
+
158
+ describe('getUser', () => {
159
+ beforeEach(() => {
160
+ vi.clearAllMocks();
161
+ });
162
+
163
+ it('should return user from database', async () => {
164
+ const mockUser = { id: '1', name: 'John' };
165
+ vi.mocked(db.user.findUnique).mockResolvedValue(mockUser);
166
+
167
+ const user = await getUser('1');
168
+
169
+ expect(user).toEqual(mockUser);
170
+ expect(db.user.findUnique).toHaveBeenCalledWith({
171
+ where: { id: '1' },
172
+ });
173
+ });
174
+ });
175
+ ```
176
+
177
+ ### Async Testing
178
+
179
+ ```typescript
180
+ it('should throw on invalid id', async () => {
181
+ await expect(fetchUser('invalid')).rejects.toThrow('User not found');
182
+ });
183
+
184
+ it('should resolve with user data', async () => {
185
+ const user = await fetchUser('123');
186
+ expect(user).toMatchObject({
187
+ id: '123',
188
+ name: expect.any(String),
189
+ });
190
+ });
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Component Testing
196
+
197
+ ### comp-1: Query by Role/Label
198
+
199
+ ```typescript
200
+ // GOOD: Accessible queries
201
+ screen.getByRole('button', { name: /submit/i });
202
+ screen.getByLabelText(/email/i);
203
+ screen.getByText(/welcome/i);
204
+
205
+ // BAD: Implementation details
206
+ screen.getByTestId('submit-btn'); // Only as last resort
207
+ screen.getByClassName('btn-primary'); // Never
208
+ ```
209
+
210
+ ### comp-2: userEvent Over fireEvent
211
+
212
+ ```typescript
213
+ import userEvent from '@testing-library/user-event';
214
+
215
+ it('should submit form on enter', async () => {
216
+ const user = userEvent.setup();
217
+ const onSubmit = vi.fn();
218
+
219
+ render(<SearchForm onSubmit={onSubmit} />);
220
+
221
+ await user.type(screen.getByRole('textbox'), 'search query');
222
+ await user.keyboard('{Enter}');
223
+
224
+ expect(onSubmit).toHaveBeenCalledWith('search query');
225
+ });
226
+ ```
227
+
228
+ ### comp-5: Loading and Error States
229
+
230
+ ```typescript
231
+ it('should show loading state', () => {
232
+ render(<UserProfile isLoading />);
233
+
234
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
235
+ expect(screen.queryByText(/john/i)).not.toBeInTheDocument();
236
+ });
237
+
238
+ it('should show error state', () => {
239
+ render(<UserProfile error="Failed to load" />);
240
+
241
+ expect(screen.getByRole('alert')).toHaveTextContent('Failed to load');
242
+ });
243
+ ```
244
+
245
+ ### comp-7: Async Content
246
+
247
+ ```typescript
248
+ it('should load and display user', async () => {
249
+ render(<UserProfile userId="123" />);
250
+
251
+ // findBy waits for element
252
+ const name = await screen.findByText(/john doe/i);
253
+ expect(name).toBeInTheDocument();
254
+
255
+ // Loading should be gone
256
+ expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
257
+ });
258
+ ```
259
+
260
+ ### Testing Hooks
261
+
262
+ ```typescript
263
+ import { renderHook, act } from '@testing-library/react';
264
+
265
+ describe('useCounter', () => {
266
+ it('should increment count', () => {
267
+ const { result } = renderHook(() => useCounter());
268
+
269
+ act(() => {
270
+ result.current.increment();
271
+ });
272
+
273
+ expect(result.current.count).toBe(1);
274
+ });
275
+ });
276
+ ```
277
+
278
+ ---
279
+
280
+ ## E2E Testing (Playwright)
281
+
282
+ ### e2e-2: Page Object Model
283
+
284
+ ```typescript
285
+ // e2e/pages/login.page.ts
286
+ export class LoginPage {
287
+ constructor(private page: Page) {}
288
+
289
+ readonly emailInput = () => this.page.getByLabel('Email');
290
+ readonly passwordInput = () => this.page.getByLabel('Password');
291
+ readonly submitButton = () => this.page.getByRole('button', { name: 'Sign in' });
292
+ readonly errorMessage = () => this.page.getByRole('alert');
293
+
294
+ async goto() {
295
+ await this.page.goto('/login');
296
+ }
297
+
298
+ async login(email: string, password: string) {
299
+ await this.emailInput().fill(email);
300
+ await this.passwordInput().fill(password);
301
+ await this.submitButton().click();
302
+ }
303
+ }
304
+ ```
305
+
306
+ ### E2E Test
307
+
308
+ ```typescript
309
+ import { test, expect } from '@playwright/test';
310
+ import { LoginPage } from './pages/login.page';
311
+
312
+ test('should login successfully', async ({ page }) => {
313
+ const loginPage = new LoginPage(page);
314
+
315
+ await loginPage.goto();
316
+ await loginPage.login('user@example.com', 'password123');
317
+
318
+ await expect(page).toHaveURL('/dashboard');
319
+ await expect(page.getByText('Welcome back')).toBeVisible();
320
+ });
321
+
322
+ test('should show error with invalid credentials', async ({ page }) => {
323
+ const loginPage = new LoginPage(page);
324
+
325
+ await loginPage.goto();
326
+ await loginPage.login('user@example.com', 'wrong');
327
+
328
+ await expect(loginPage.errorMessage()).toContainText('Invalid credentials');
329
+ });
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Test Data Factories
335
+
336
+ ```typescript
337
+ import { faker } from '@faker-js/faker';
338
+
339
+ export function createUser(overrides: Partial<User> = {}): User {
340
+ return {
341
+ id: faker.string.uuid(),
342
+ email: faker.internet.email(),
343
+ name: faker.person.fullName(),
344
+ role: 'user',
345
+ createdAt: faker.date.past(),
346
+ ...overrides,
347
+ };
348
+ }
349
+
350
+ // Usage
351
+ const users = Array.from({ length: 5 }, () => createUser());
352
+ const admin = createUser({ role: 'admin' });
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Configuration
358
+
359
+ ### Vitest
360
+
361
+ ```typescript
362
+ // vitest.config.ts
363
+ export default defineConfig({
364
+ test: {
365
+ environment: 'jsdom',
366
+ globals: true,
367
+ setupFiles: ['./tests/setup.ts'],
368
+ coverage: {
369
+ provider: 'v8',
370
+ reporter: ['text', 'html'],
371
+ exclude: ['node_modules/', '**/*.d.ts'],
372
+ },
373
+ },
374
+ });
375
+ ```
376
+
377
+ ### Test Setup
378
+
379
+ ```typescript
380
+ // tests/setup.ts
381
+ import '@testing-library/jest-dom/vitest';
382
+ import { cleanup } from '@testing-library/react';
383
+ import { afterEach } from 'vitest';
384
+
385
+ afterEach(() => {
386
+ cleanup();
387
+ });
388
+ ```
389
+
390
+ ---
391
+
392
+ ## Coverage Goals
393
+
394
+ | Type | Target | Focus |
395
+ |------|--------|-------|
396
+ | Unit | 80%+ | Utils, hooks, pure functions |
397
+ | Integration | 70%+ | Components, API routes |
398
+ | E2E | Critical paths | Auth, checkout, core flows |
@@ -0,0 +1,276 @@
1
+ ---
2
+ name: testing-patterns
3
+ description: Testing pyramid, mocking, coverage, TDD, integration tests, E2E
4
+ keywords: [testing, vitest, playwright, mock, coverage, tdd, integration, e2e, test pyramid]
5
+ ---
6
+
7
+ # Testing Standards
8
+
9
+ Testing patterns and requirements for production-grade software.
10
+
11
+ ## Test Pyramid
12
+
13
+ ```
14
+ ╱╲ E2E (5%) - Critical flows only
15
+ ╱──╲ Contract (10%) - API schemas
16
+ ╱────╲ Integration (25%) - Real services
17
+ ╱──────╲ Unit (60%) - Business logic
18
+ ```
19
+
20
+ - Never skip unit tests for integration
21
+ - E2E for critical money/auth flows only
22
+ - Integration tests catch real DB/cache bugs
23
+
24
+ ## Coverage Requirements
25
+
26
+ | Layer | Minimum | Target |
27
+ |-------|---------|--------|
28
+ | Infrastructure packages | 85% | 95% |
29
+ | Business logic packages | 70% | 85% |
30
+ | Applications (unit) | 60% | 80% |
31
+
32
+ Block merge on coverage drop >2%.
33
+
34
+ ## When to Write Tests
35
+
36
+ ### TDD (Test First)
37
+ - Business logic services
38
+ - Data transformations
39
+ - Validation rules
40
+ - Error handling
41
+
42
+ ### Test After
43
+ - UI components
44
+ - Styling changes
45
+ - Configuration
46
+
47
+ ### Always Test
48
+ - Happy path
49
+ - 2+ error cases per function
50
+ - Edge cases (null, empty, max, boundary)
51
+ - Security-sensitive code (auth, payments)
52
+
53
+ ## Mock Strategy
54
+
55
+ | Test Type | Database | Redis | External APIs |
56
+ |-----------|----------|-------|---------------|
57
+ | Unit | Mock | Mock | Mock |
58
+ | Integration | Real (Testcontainers) | Real | Mock |
59
+ | E2E | Real (staging) | Real | Real or sandbox |
60
+
61
+ ### Mock Utilities
62
+
63
+ ```typescript
64
+ // Database mocking (@figu/db pattern)
65
+ import { createMockRawDrizzleDb, mockSelectChain } from '@figu/db/test-utils';
66
+
67
+ const { mockDb, mockQuery } = createMockRawDrizzleDb();
68
+ mockSelectChain(mockQuery, [{ id: 'user_123' }]);
69
+ ```
70
+
71
+ ### Real Services (Testcontainers)
72
+
73
+ ```typescript
74
+ // Integration tests with real PostgreSQL
75
+ import { PostgreSqlContainer } from '@testcontainers/postgresql';
76
+
77
+ let container: StartedPostgreSqlContainer;
78
+
79
+ beforeAll(async () => {
80
+ container = await new PostgreSqlContainer().start();
81
+ process.env.DATABASE_URL = container.getConnectionUri();
82
+ });
83
+
84
+ afterAll(async () => {
85
+ await container.stop();
86
+ });
87
+ ```
88
+
89
+ ## Test Structure
90
+
91
+ ### File Organization
92
+
93
+ ```
94
+ src/
95
+ ├── service.ts
96
+ ├── service.test.ts # Unit tests (co-located)
97
+ └── __tests__/
98
+ └── service.int.ts # Integration tests (separate)
99
+ ```
100
+
101
+ ### Naming Convention
102
+
103
+ ```typescript
104
+ // Pattern: should_expectedBehavior_when_condition
105
+ it('should return user profile when authenticated', async () => {});
106
+ it('should throw UnauthorizedError when token expired', async () => {});
107
+ it('should reject when wallet limit exceeded', async () => {});
108
+ ```
109
+
110
+ ### AAA Pattern
111
+
112
+ ```typescript
113
+ it('should calculate balance from transactions', async () => {
114
+ // Arrange
115
+ const wallet = createTestWallet({ id: 'wallet-1' });
116
+ const transactions = [
117
+ createTestTransaction({ amount: -100 }),
118
+ createTestTransaction({ amount: -50 }),
119
+ ];
120
+
121
+ // Act
122
+ const balance = calculateBalance(wallet, transactions);
123
+
124
+ // Assert
125
+ expect(balance).toBe(-150);
126
+ });
127
+ ```
128
+
129
+ ## Test Data
130
+
131
+ ### Factories (Recommended)
132
+
133
+ ```typescript
134
+ // packages/testing/src/factories/user.ts
135
+ export function createTestUser(overrides: Partial<User> = {}): User {
136
+ return {
137
+ id: `user_${cuid()}`,
138
+ email: `test-${Date.now()}@example.com`,
139
+ firstName: 'Test',
140
+ lastName: 'User',
141
+ createdAt: new Date(),
142
+ ...overrides,
143
+ };
144
+ }
145
+ ```
146
+
147
+ ### Rules
148
+ - Never use production data
149
+ - Generate unique IDs per test
150
+ - Clean up in `afterEach`
151
+ - Use realistic but fake data
152
+
153
+ ## Integration Tests
154
+
155
+ ### Skip When Services Unavailable
156
+
157
+ ```typescript
158
+ describe.skipIf(!process.env.DATABASE_URL)('Database Integration', () => {
159
+ // Tests requiring real database
160
+ });
161
+ ```
162
+
163
+ ### Clean Up Test Data
164
+
165
+ ```typescript
166
+ afterEach(async () => {
167
+ await db.delete(users).where(like(users.email, 'test-%'));
168
+ });
169
+ ```
170
+
171
+ ## E2E Tests
172
+
173
+ ### Critical Paths Only
174
+
175
+ Test these flows E2E:
176
+ - Authentication (login, logout, refresh)
177
+ - Money movement (create transaction, transfer)
178
+ - Data integrity (export, deletion)
179
+
180
+ ### Page Object Pattern
181
+
182
+ ```typescript
183
+ // e2e/pages/login.page.ts
184
+ export class LoginPage {
185
+ constructor(private page: Page) {}
186
+
187
+ async login(email: string, password: string) {
188
+ await this.page.fill('[data-testid="email"]', email);
189
+ await this.page.fill('[data-testid="password"]', password);
190
+ await this.page.click('[data-testid="submit"]');
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Timeouts
196
+
197
+ - Max 30s per test
198
+ - Record video on failure
199
+ - Screenshot on assertion failure
200
+
201
+ ## Security Testing
202
+
203
+ ### SAST (Every PR)
204
+ - CodeQL for JavaScript/TypeScript
205
+ - npm audit for dependencies
206
+ - Block merge on critical findings
207
+
208
+ ### DAST (Weekly)
209
+ - OWASP ZAP scan
210
+ - API fuzzing
211
+ - Authentication bypass checks
212
+
213
+ ## Performance Testing
214
+
215
+ ### Baselines
216
+
217
+ | Metric | Target | Maximum |
218
+ |--------|--------|---------|
219
+ | API p95 latency | < 200ms | < 500ms |
220
+ | API p99 latency | < 500ms | < 2s |
221
+ | DB query p95 | < 50ms | < 200ms |
222
+
223
+ ### Regression Detection
224
+
225
+ ```bash
226
+ # Compare current run to baseline
227
+ k6 run --out json=results.json tests/load/api.js
228
+ # Fail if p95 > baseline + 20%
229
+ ```
230
+
231
+ ## Flaky Tests
232
+
233
+ ### Prevention
234
+ - Use `vi.useFakeTimers()` for time-dependent tests
235
+ - Avoid `sleep()` - use `waitFor()` or polling
236
+ - Isolate test state (no shared mutable state)
237
+
238
+ ### Handling
239
+ - Max 3 retries in CI
240
+ - Quarantine after 3 consecutive failures
241
+ - Fix within 48 hours or delete
242
+ - Track flaky test metrics
243
+
244
+ ## CI Integration
245
+
246
+ ### Quality Gate
247
+
248
+ ```bash
249
+ pnpm quality # typecheck → lint → test
250
+ ```
251
+
252
+ Must pass before:
253
+ - Merge to main
254
+ - Release tags
255
+ - Deployment
256
+
257
+ ### Coverage Gate
258
+
259
+ ```yaml
260
+ # .github/workflows/ci.yml
261
+ - name: Check coverage
262
+ run: |
263
+ pnpm test:coverage
264
+ # Fail if below threshold
265
+ ```
266
+
267
+ ## Checklist Before PR
268
+
269
+ - [ ] Unit tests for new business logic
270
+ - [ ] Error cases tested
271
+ - [ ] Edge cases tested
272
+ - [ ] Integration test if touching DB/Redis
273
+ - [ ] E2E test if critical user flow
274
+ - [ ] No `console.log` in test files
275
+ - [ ] No `.only` or `.skip` committed
276
+ - [ ] Coverage not decreased