@champpaba/claude-agent-kit 1.1.1 → 1.4.1
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.
- package/.claude/CLAUDE.md +52 -444
- package/.claude/agents/01-integration.md +47 -193
- package/.claude/agents/02-uxui-frontend.md +151 -188
- package/.claude/agents/03-test-debug.md +43 -186
- package/.claude/agents/04-frontend.md +50 -478
- package/.claude/agents/05-backend.md +49 -499
- package/.claude/agents/06-database.md +36 -188
- package/.claude/commands/cdev.md +86 -1
- package/.claude/commands/csetup.md +319 -6
- package/.claude/commands/designsetup.md +360 -29
- package/.claude/commands/pageplan.md +409 -19
- package/.claude/contexts/design/box-thinking.md +358 -0
- package/.claude/contexts/patterns/animation-patterns.md +768 -0
- package/.claude/contexts/patterns/performance-optimization.md +421 -0
- package/.claude/contexts/patterns/ui-component-consistency.md +49 -2
- package/.claude/lib/README.md +46 -4
- package/.claude/lib/agent-executor.md +69 -10
- package/.claude/lib/context-loading-protocol.md +462 -0
- package/.claude/lib/detailed-guides/agent-system.md +237 -0
- package/.claude/lib/detailed-guides/best-practices-system.md +150 -0
- package/.claude/lib/detailed-guides/context-optimization.md +118 -0
- package/.claude/lib/detailed-guides/design-system.md +98 -0
- package/.claude/lib/detailed-guides/page-planning.md +147 -0
- package/.claude/lib/detailed-guides/taskmaster-analysis.md +263 -0
- package/.claude/lib/document-loader.md +353 -0
- package/.claude/lib/handoff-protocol.md +665 -0
- package/.claude/lib/task-analyzer.md +694 -0
- package/.claude/lib/tdd-workflow.md +891 -0
- package/.claude/templates/design-context-template.md +220 -0
- package/.claude/templates/page-plan-example.md +131 -0
- package/README.md +290 -0
- package/bin/cli.js +0 -2
- package/lib/helpers.js +108 -0
- package/lib/init.js +18 -13
- package/lib/update.js +48 -31
- package/package.json +1 -1
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
# TDD Workflow (Red-Green-Refactor)
|
|
2
|
+
|
|
3
|
+
> **Test-Driven Development methodology for complex/critical tasks**
|
|
4
|
+
> **Version:** 1.4.0
|
|
5
|
+
> **Purpose:** Eliminate duplication across backend/frontend/database agents
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📋 Overview
|
|
10
|
+
|
|
11
|
+
This document defines the TDD (Test-Driven Development) workflow using the Red-Green-Refactor cycle.
|
|
12
|
+
|
|
13
|
+
**Why this exists:**
|
|
14
|
+
- ✅ Single source of truth for TDD methodology
|
|
15
|
+
- ✅ Prevents errors in critical code (auth, payment, migrations)
|
|
16
|
+
- ✅ Ensures testability (write tests first)
|
|
17
|
+
- ✅ Consistent TDD practice across agents
|
|
18
|
+
|
|
19
|
+
**Who uses this:**
|
|
20
|
+
- **backend agent:** When `tdd_required: true` (auth, payment, security)
|
|
21
|
+
- **frontend agent:** When `tdd_required: true` (complex state, multi-step forms)
|
|
22
|
+
- **database agent:** When `tdd_required: true` (migrations, schema changes)
|
|
23
|
+
|
|
24
|
+
**When to use TDD:**
|
|
25
|
+
- ✅ Authentication/authorization logic
|
|
26
|
+
- ✅ Payment processing
|
|
27
|
+
- ✅ Security-critical features
|
|
28
|
+
- ✅ Complex business logic
|
|
29
|
+
- ✅ Database migrations
|
|
30
|
+
- ✅ Multi-step workflows
|
|
31
|
+
- ✅ Any task with `tdd_required: true` flag
|
|
32
|
+
|
|
33
|
+
**When NOT to use TDD (use Test-Alongside):**
|
|
34
|
+
- ❌ Simple CRUD endpoints
|
|
35
|
+
- ❌ Read-only queries
|
|
36
|
+
- ❌ Presentational UI components
|
|
37
|
+
- ❌ Static pages
|
|
38
|
+
- ❌ Any task with `tdd_required: false` flag
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🔄 The Red-Green-Refactor Cycle
|
|
43
|
+
|
|
44
|
+
### Overview:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
1. 🔴 RED Phase: Write Test First
|
|
48
|
+
├─→ Test MUST fail (feature doesn't exist yet)
|
|
49
|
+
└─→ Defines expected behavior
|
|
50
|
+
|
|
51
|
+
2. 🟢 GREEN Phase: Minimal Implementation
|
|
52
|
+
├─→ Write just enough code to pass tests
|
|
53
|
+
└─→ Focus on making it work (not perfect)
|
|
54
|
+
|
|
55
|
+
3. 🔵 REFACTOR Phase: Improve Code Quality
|
|
56
|
+
├─→ Add error handling, logging, validation
|
|
57
|
+
├─→ Improve structure and readability
|
|
58
|
+
└─→ Tests MUST still pass
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Key Principle:** Tests drive the implementation, not the other way around.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 🔴 RED Phase: Write Test First
|
|
66
|
+
|
|
67
|
+
### Purpose:
|
|
68
|
+
|
|
69
|
+
- Define expected behavior BEFORE writing code
|
|
70
|
+
- Ensure feature is testable from the start
|
|
71
|
+
- Document requirements as executable tests
|
|
72
|
+
|
|
73
|
+
### Protocol:
|
|
74
|
+
|
|
75
|
+
**Step 1: Write failing tests**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
✅ DO:
|
|
79
|
+
- Write comprehensive test cases
|
|
80
|
+
- Cover success cases
|
|
81
|
+
- Cover error cases
|
|
82
|
+
- Cover edge cases
|
|
83
|
+
- Use descriptive test names
|
|
84
|
+
|
|
85
|
+
❌ DON'T:
|
|
86
|
+
- Write implementation code yet
|
|
87
|
+
- Skip error cases
|
|
88
|
+
- Use vague test names
|
|
89
|
+
- Assume implementation details
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Step 2: Run tests**
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Expected result: ❌ FAILED
|
|
96
|
+
# This is CORRECT! Tests should fail in RED phase.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Step 3: Verify test failure**
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
✅ Test failed for the right reason:
|
|
103
|
+
- Feature doesn't exist (404, Connection refused)
|
|
104
|
+
- Endpoint not found
|
|
105
|
+
- Function not defined
|
|
106
|
+
|
|
107
|
+
❌ Test failed for wrong reason:
|
|
108
|
+
- Syntax error in test
|
|
109
|
+
- Wrong import
|
|
110
|
+
- Test configuration issue
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 🟢 GREEN Phase: Minimal Implementation
|
|
116
|
+
|
|
117
|
+
### Purpose:
|
|
118
|
+
|
|
119
|
+
- Make tests pass with simplest possible code
|
|
120
|
+
- Don't worry about perfect code yet
|
|
121
|
+
- Focus on correctness, not elegance
|
|
122
|
+
|
|
123
|
+
### Protocol:
|
|
124
|
+
|
|
125
|
+
**Step 1: Write minimal code**
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
✅ DO:
|
|
129
|
+
- Write just enough to make tests pass
|
|
130
|
+
- Use hardcoded values (will refactor later)
|
|
131
|
+
- Keep it simple and straightforward
|
|
132
|
+
|
|
133
|
+
❌ DON'T:
|
|
134
|
+
- Over-engineer
|
|
135
|
+
- Add unnecessary features
|
|
136
|
+
- Optimize prematurely
|
|
137
|
+
- Add complex error handling (save for REFACTOR)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Step 2: Run tests**
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Expected result: ✅ PASSED
|
|
144
|
+
# All (or most) tests should pass now.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Step 3: Verify all tests pass**
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
✅ If all tests pass:
|
|
151
|
+
→ Move to REFACTOR phase
|
|
152
|
+
|
|
153
|
+
⚠️ If some tests still fail:
|
|
154
|
+
→ Fix implementation (stay in GREEN phase)
|
|
155
|
+
|
|
156
|
+
❌ If tests pass but you added extra features:
|
|
157
|
+
→ Remove extra features (YAGNI principle)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🔵 REFACTOR Phase: Production Quality
|
|
163
|
+
|
|
164
|
+
### Purpose:
|
|
165
|
+
|
|
166
|
+
- Improve code quality while keeping tests green
|
|
167
|
+
- Add error handling, logging, validation
|
|
168
|
+
- Optimize and clean up
|
|
169
|
+
|
|
170
|
+
### Protocol:
|
|
171
|
+
|
|
172
|
+
**Step 1: Improve code quality**
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
✅ DO:
|
|
176
|
+
- Add error handling (try/catch, validation)
|
|
177
|
+
- Add structured logging (entry, success, error)
|
|
178
|
+
- Add type hints / TypeScript types
|
|
179
|
+
- Add docstrings / JSDoc comments
|
|
180
|
+
- Extract reusable functions
|
|
181
|
+
- Follow coding standards
|
|
182
|
+
- Improve variable names
|
|
183
|
+
|
|
184
|
+
❌ DON'T:
|
|
185
|
+
- Change test expectations (tests are requirements)
|
|
186
|
+
- Break existing functionality
|
|
187
|
+
- Add untested features
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Step 2: Run tests after EACH change**
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# After every refactor:
|
|
194
|
+
# Expected result: ✅ PASSED (tests still pass!)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Step 3: Verify tests still pass**
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
✅ If tests pass:
|
|
201
|
+
→ Continue refactoring
|
|
202
|
+
→ When satisfied, mark task complete
|
|
203
|
+
|
|
204
|
+
❌ If tests fail:
|
|
205
|
+
→ Undo last change
|
|
206
|
+
→ Fix refactoring
|
|
207
|
+
→ Run tests again
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 📚 Language-Specific Examples
|
|
213
|
+
|
|
214
|
+
### Python (FastAPI + Pytest)
|
|
215
|
+
|
|
216
|
+
#### 🔴 RED Phase:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
# tests/test_auth.py (WRITE THIS FIRST!)
|
|
220
|
+
import pytest
|
|
221
|
+
from httpx import AsyncClient
|
|
222
|
+
|
|
223
|
+
@pytest.mark.asyncio
|
|
224
|
+
async def test_login_success(client: AsyncClient):
|
|
225
|
+
"""Test successful login with valid credentials"""
|
|
226
|
+
response = await client.post("/api/auth/login", json={
|
|
227
|
+
"email": "test@example.com",
|
|
228
|
+
"password": "password123"
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
assert response.status_code == 200
|
|
232
|
+
data = response.json()
|
|
233
|
+
assert "token" in data
|
|
234
|
+
assert data["user"]["email"] == "test@example.com"
|
|
235
|
+
|
|
236
|
+
@pytest.mark.asyncio
|
|
237
|
+
async def test_login_invalid_credentials(client: AsyncClient):
|
|
238
|
+
"""Test invalid credentials return 401"""
|
|
239
|
+
response = await client.post("/api/auth/login", json={
|
|
240
|
+
"email": "wrong@example.com",
|
|
241
|
+
"password": "wrongpass"
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
assert response.status_code == 401
|
|
245
|
+
assert "invalid" in response.json()["detail"].lower()
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_login_validation_error(client: AsyncClient):
|
|
249
|
+
"""Test validation on missing fields"""
|
|
250
|
+
response = await client.post("/api/auth/login", json={
|
|
251
|
+
"email": "not-an-email"
|
|
252
|
+
# Missing password
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
assert response.status_code == 422 # Validation error
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Run tests:**
|
|
259
|
+
```bash
|
|
260
|
+
pytest tests/test_auth.py -v
|
|
261
|
+
|
|
262
|
+
# Expected: ❌ FAILED (endpoint doesn't exist)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### 🟢 GREEN Phase:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# app/api/auth.py (NOW write implementation)
|
|
269
|
+
from fastapi import APIRouter, HTTPException
|
|
270
|
+
|
|
271
|
+
router = APIRouter()
|
|
272
|
+
|
|
273
|
+
@router.post("/api/auth/login")
|
|
274
|
+
async def login(email: str, password: str):
|
|
275
|
+
"""Minimal implementation - just make tests pass"""
|
|
276
|
+
|
|
277
|
+
# Hardcoded for now (will refactor later)
|
|
278
|
+
if email == "test@example.com" and password == "password123":
|
|
279
|
+
return {
|
|
280
|
+
"token": "fake-token-123",
|
|
281
|
+
"user": {"email": email}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Invalid credentials
|
|
285
|
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Run tests:**
|
|
289
|
+
```bash
|
|
290
|
+
pytest tests/test_auth.py -v
|
|
291
|
+
|
|
292
|
+
# Expected: ✅ PASSED (2-3 tests passing)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
#### 🔵 REFACTOR Phase:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
# app/api/auth.py (Refactor with production quality)
|
|
299
|
+
from fastapi import APIRouter, HTTPException, Depends
|
|
300
|
+
from pydantic import BaseModel, EmailStr
|
|
301
|
+
from sqlalchemy import select
|
|
302
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
303
|
+
from app.lib.logger import logger
|
|
304
|
+
from app.lib.auth import verify_password, create_jwt_token
|
|
305
|
+
from app.db import get_db
|
|
306
|
+
from app.models.user import User
|
|
307
|
+
|
|
308
|
+
router = APIRouter()
|
|
309
|
+
|
|
310
|
+
class LoginRequest(BaseModel):
|
|
311
|
+
"""Login request validation schema"""
|
|
312
|
+
email: EmailStr
|
|
313
|
+
password: str
|
|
314
|
+
|
|
315
|
+
class LoginResponse(BaseModel):
|
|
316
|
+
"""Login response schema"""
|
|
317
|
+
token: str
|
|
318
|
+
user: dict
|
|
319
|
+
|
|
320
|
+
@router.post("/api/auth/login", response_model=LoginResponse)
|
|
321
|
+
async def login(
|
|
322
|
+
data: LoginRequest,
|
|
323
|
+
db: AsyncSession = Depends(get_db)
|
|
324
|
+
):
|
|
325
|
+
"""
|
|
326
|
+
Authenticate user and return JWT token.
|
|
327
|
+
|
|
328
|
+
Raises:
|
|
329
|
+
HTTPException 401: Invalid credentials
|
|
330
|
+
HTTPException 500: Server error
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
# Log entry
|
|
334
|
+
logger.info("api_route_entry", extra={
|
|
335
|
+
"route": "/api/auth/login",
|
|
336
|
+
"method": "POST",
|
|
337
|
+
"email": data.email
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
# Query database
|
|
342
|
+
result = await db.execute(
|
|
343
|
+
select(User).where(User.email == data.email)
|
|
344
|
+
)
|
|
345
|
+
user = result.scalar_one_or_none()
|
|
346
|
+
|
|
347
|
+
# Verify credentials
|
|
348
|
+
if not user or not verify_password(data.password, user.hashed_password):
|
|
349
|
+
logger.warning("login_failed", extra={
|
|
350
|
+
"email": data.email,
|
|
351
|
+
"reason": "invalid_credentials"
|
|
352
|
+
})
|
|
353
|
+
raise HTTPException(
|
|
354
|
+
status_code=401,
|
|
355
|
+
detail="Invalid credentials"
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Generate JWT token
|
|
359
|
+
token = create_jwt_token(user.id)
|
|
360
|
+
|
|
361
|
+
# Log success
|
|
362
|
+
logger.info("login_success", extra={
|
|
363
|
+
"user_id": user.id,
|
|
364
|
+
"email": data.email
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return LoginResponse(
|
|
368
|
+
token=token,
|
|
369
|
+
user={
|
|
370
|
+
"id": user.id,
|
|
371
|
+
"email": user.email,
|
|
372
|
+
"name": user.name
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
except HTTPException:
|
|
377
|
+
raise
|
|
378
|
+
except Exception as e:
|
|
379
|
+
logger.error("login_error", extra={
|
|
380
|
+
"error": str(e),
|
|
381
|
+
"email": data.email
|
|
382
|
+
})
|
|
383
|
+
raise HTTPException(
|
|
384
|
+
status_code=500,
|
|
385
|
+
detail="Internal server error"
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Run tests:**
|
|
390
|
+
```bash
|
|
391
|
+
pytest tests/test_auth.py -v
|
|
392
|
+
|
|
393
|
+
# Expected: ✅ PASSED (all tests still passing!)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
### TypeScript (Next.js + Vitest)
|
|
399
|
+
|
|
400
|
+
#### 🔴 RED Phase:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// __tests__/api/auth.test.ts (WRITE THIS FIRST!)
|
|
404
|
+
import { describe, it, expect } from 'vitest'
|
|
405
|
+
import { POST } from '@/app/api/auth/login/route'
|
|
406
|
+
|
|
407
|
+
describe('POST /api/auth/login', () => {
|
|
408
|
+
it('should return 200 and token for valid credentials', async () => {
|
|
409
|
+
const request = new Request('http://localhost/api/auth/login', {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
body: JSON.stringify({
|
|
412
|
+
email: 'test@example.com',
|
|
413
|
+
password: 'password123'
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
const response = await POST(request)
|
|
418
|
+
const data = await response.json()
|
|
419
|
+
|
|
420
|
+
expect(response.status).toBe(200)
|
|
421
|
+
expect(data).toHaveProperty('token')
|
|
422
|
+
expect(data.user.email).toBe('test@example.com')
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it('should return 401 for invalid credentials', async () => {
|
|
426
|
+
const request = new Request('http://localhost/api/auth/login', {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
body: JSON.stringify({
|
|
429
|
+
email: 'wrong@example.com',
|
|
430
|
+
password: 'wrongpass'
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
const response = await POST(request)
|
|
435
|
+
const data = await response.json()
|
|
436
|
+
|
|
437
|
+
expect(response.status).toBe(401)
|
|
438
|
+
expect(data.error).toMatch(/invalid/i)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should return 422 for missing email', async () => {
|
|
442
|
+
const request = new Request('http://localhost/api/auth/login', {
|
|
443
|
+
method: 'POST',
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
password: 'password123'
|
|
446
|
+
// Missing email
|
|
447
|
+
})
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
const response = await POST(request)
|
|
451
|
+
|
|
452
|
+
expect(response.status).toBe(422)
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Run tests:**
|
|
458
|
+
```bash
|
|
459
|
+
vitest run __tests__/api/auth.test.ts
|
|
460
|
+
|
|
461
|
+
# Expected: ❌ FAILED (route doesn't exist)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### 🟢 GREEN Phase:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// app/api/auth/login/route.ts (NOW write implementation)
|
|
468
|
+
import { NextResponse } from 'next/server'
|
|
469
|
+
|
|
470
|
+
export async function POST(request: Request) {
|
|
471
|
+
const body = await request.json()
|
|
472
|
+
const { email, password } = body
|
|
473
|
+
|
|
474
|
+
// Hardcoded for now (will refactor later)
|
|
475
|
+
if (email === 'test@example.com' && password === 'password123') {
|
|
476
|
+
return NextResponse.json({
|
|
477
|
+
token: 'fake-token-123',
|
|
478
|
+
user: { email }
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Invalid credentials
|
|
483
|
+
return NextResponse.json(
|
|
484
|
+
{ error: 'Invalid credentials' },
|
|
485
|
+
{ status: 401 }
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Run tests:**
|
|
491
|
+
```bash
|
|
492
|
+
vitest run __tests__/api/auth.test.ts
|
|
493
|
+
|
|
494
|
+
# Expected: ✅ PASSED (2-3 tests passing)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### 🔵 REFACTOR Phase:
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// app/api/auth/login/route.ts (Refactor with production quality)
|
|
501
|
+
import { NextResponse } from 'next/server'
|
|
502
|
+
import { z } from 'zod'
|
|
503
|
+
import { logger } from '@/lib/logger'
|
|
504
|
+
import { verifyPassword, createJwtToken } from '@/lib/auth'
|
|
505
|
+
import { prisma } from '@/lib/db'
|
|
506
|
+
|
|
507
|
+
const LoginSchema = z.object({
|
|
508
|
+
email: z.string().email('Invalid email format'),
|
|
509
|
+
password: z.string().min(8, 'Password must be at least 8 characters')
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
export async function POST(request: Request) {
|
|
513
|
+
// Log entry
|
|
514
|
+
logger.info('api_route_entry', {
|
|
515
|
+
route: '/api/auth/login',
|
|
516
|
+
method: 'POST'
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
// Parse and validate request
|
|
521
|
+
const body = await request.json()
|
|
522
|
+
const validation = LoginSchema.safeParse(body)
|
|
523
|
+
|
|
524
|
+
if (!validation.success) {
|
|
525
|
+
logger.warning('validation_error', {
|
|
526
|
+
errors: validation.error.errors
|
|
527
|
+
})
|
|
528
|
+
return NextResponse.json(
|
|
529
|
+
{ error: 'Validation failed', details: validation.error.errors },
|
|
530
|
+
{ status: 422 }
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const { email, password } = validation.data
|
|
535
|
+
|
|
536
|
+
// Query database
|
|
537
|
+
const user = await prisma.user.findUnique({
|
|
538
|
+
where: { email }
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
// Verify credentials
|
|
542
|
+
if (!user || !(await verifyPassword(password, user.hashedPassword))) {
|
|
543
|
+
logger.warning('login_failed', {
|
|
544
|
+
email,
|
|
545
|
+
reason: 'invalid_credentials'
|
|
546
|
+
})
|
|
547
|
+
return NextResponse.json(
|
|
548
|
+
{ error: 'Invalid credentials' },
|
|
549
|
+
{ status: 401 }
|
|
550
|
+
)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Generate JWT token
|
|
554
|
+
const token = createJwtToken(user.id)
|
|
555
|
+
|
|
556
|
+
// Log success
|
|
557
|
+
logger.info('login_success', {
|
|
558
|
+
userId: user.id,
|
|
559
|
+
email: user.email
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
return NextResponse.json({
|
|
563
|
+
token,
|
|
564
|
+
user: {
|
|
565
|
+
id: user.id,
|
|
566
|
+
email: user.email,
|
|
567
|
+
name: user.name
|
|
568
|
+
}
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
} catch (error) {
|
|
572
|
+
logger.error('login_error', {
|
|
573
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
574
|
+
})
|
|
575
|
+
return NextResponse.json(
|
|
576
|
+
{ error: 'Internal server error' },
|
|
577
|
+
{ status: 500 }
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
**Run tests:**
|
|
584
|
+
```bash
|
|
585
|
+
vitest run __tests__/api/auth.test.ts
|
|
586
|
+
|
|
587
|
+
# Expected: ✅ PASSED (all tests still passing!)
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
### JavaScript (Express + Jest)
|
|
593
|
+
|
|
594
|
+
#### 🔴 RED Phase:
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
// __tests__/auth.test.js (WRITE THIS FIRST!)
|
|
598
|
+
const request = require('supertest')
|
|
599
|
+
const app = require('../app')
|
|
600
|
+
|
|
601
|
+
describe('POST /api/auth/login', () => {
|
|
602
|
+
it('should return 200 and token for valid credentials', async () => {
|
|
603
|
+
const response = await request(app)
|
|
604
|
+
.post('/api/auth/login')
|
|
605
|
+
.send({
|
|
606
|
+
email: 'test@example.com',
|
|
607
|
+
password: 'password123'
|
|
608
|
+
})
|
|
609
|
+
|
|
610
|
+
expect(response.status).toBe(200)
|
|
611
|
+
expect(response.body).toHaveProperty('token')
|
|
612
|
+
expect(response.body.user.email).toBe('test@example.com')
|
|
613
|
+
})
|
|
614
|
+
|
|
615
|
+
it('should return 401 for invalid credentials', async () => {
|
|
616
|
+
const response = await request(app)
|
|
617
|
+
.post('/api/auth/login')
|
|
618
|
+
.send({
|
|
619
|
+
email: 'wrong@example.com',
|
|
620
|
+
password: 'wrongpass'
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
expect(response.status).toBe(401)
|
|
624
|
+
expect(response.body.error).toMatch(/invalid/i)
|
|
625
|
+
})
|
|
626
|
+
})
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**Run tests:**
|
|
630
|
+
```bash
|
|
631
|
+
npm test -- auth.test.js
|
|
632
|
+
|
|
633
|
+
# Expected: ❌ FAILED (route doesn't exist)
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
#### 🟢 GREEN Phase:
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
// routes/auth.js (NOW write implementation)
|
|
640
|
+
const express = require('express')
|
|
641
|
+
const router = express.Router()
|
|
642
|
+
|
|
643
|
+
router.post('/api/auth/login', (req, res) => {
|
|
644
|
+
const { email, password } = req.body
|
|
645
|
+
|
|
646
|
+
// Hardcoded for now (will refactor later)
|
|
647
|
+
if (email === 'test@example.com' && password === 'password123') {
|
|
648
|
+
return res.json({
|
|
649
|
+
token: 'fake-token-123',
|
|
650
|
+
user: { email }
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Invalid credentials
|
|
655
|
+
return res.status(401).json({ error: 'Invalid credentials' })
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
module.exports = router
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Run tests:**
|
|
662
|
+
```bash
|
|
663
|
+
npm test -- auth.test.js
|
|
664
|
+
|
|
665
|
+
# Expected: ✅ PASSED
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
#### 🔵 REFACTOR Phase:
|
|
669
|
+
|
|
670
|
+
```javascript
|
|
671
|
+
// routes/auth.js (Refactor with production quality)
|
|
672
|
+
const express = require('express')
|
|
673
|
+
const { z } = require('zod')
|
|
674
|
+
const logger = require('../lib/logger')
|
|
675
|
+
const { verifyPassword, createJwtToken } = require('../lib/auth')
|
|
676
|
+
const prisma = require('../lib/db')
|
|
677
|
+
|
|
678
|
+
const router = express.Router()
|
|
679
|
+
|
|
680
|
+
const LoginSchema = z.object({
|
|
681
|
+
email: z.string().email(),
|
|
682
|
+
password: z.string().min(8)
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
router.post('/api/auth/login', async (req, res) => {
|
|
686
|
+
// Log entry
|
|
687
|
+
logger.info('api_route_entry', {
|
|
688
|
+
route: '/api/auth/login',
|
|
689
|
+
method: 'POST'
|
|
690
|
+
})
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
// Validate request
|
|
694
|
+
const validation = LoginSchema.safeParse(req.body)
|
|
695
|
+
if (!validation.success) {
|
|
696
|
+
logger.warning('validation_error', { errors: validation.error.errors })
|
|
697
|
+
return res.status(422).json({
|
|
698
|
+
error: 'Validation failed',
|
|
699
|
+
details: validation.error.errors
|
|
700
|
+
})
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const { email, password } = validation.data
|
|
704
|
+
|
|
705
|
+
// Query database
|
|
706
|
+
const user = await prisma.user.findUnique({ where: { email } })
|
|
707
|
+
|
|
708
|
+
// Verify credentials
|
|
709
|
+
if (!user || !(await verifyPassword(password, user.hashedPassword))) {
|
|
710
|
+
logger.warning('login_failed', { email, reason: 'invalid_credentials' })
|
|
711
|
+
return res.status(401).json({ error: 'Invalid credentials' })
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Generate JWT token
|
|
715
|
+
const token = createJwtToken(user.id)
|
|
716
|
+
|
|
717
|
+
// Log success
|
|
718
|
+
logger.info('login_success', { userId: user.id, email: user.email })
|
|
719
|
+
|
|
720
|
+
return res.json({
|
|
721
|
+
token,
|
|
722
|
+
user: {
|
|
723
|
+
id: user.id,
|
|
724
|
+
email: user.email,
|
|
725
|
+
name: user.name
|
|
726
|
+
}
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
} catch (error) {
|
|
730
|
+
logger.error('login_error', { error: error.message })
|
|
731
|
+
return res.status(500).json({ error: 'Internal server error' })
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
module.exports = router
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
**Run tests:**
|
|
739
|
+
```bash
|
|
740
|
+
npm test -- auth.test.js
|
|
741
|
+
|
|
742
|
+
# Expected: ✅ PASSED (all tests still passing!)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
---
|
|
746
|
+
|
|
747
|
+
## 🔀 TDD vs Test-Alongside
|
|
748
|
+
|
|
749
|
+
### When to Use TDD (Red-Green-Refactor):
|
|
750
|
+
|
|
751
|
+
**Use when `tdd_required: true`:**
|
|
752
|
+
|
|
753
|
+
| Scenario | Why TDD? |
|
|
754
|
+
|----------|----------|
|
|
755
|
+
| Authentication/authorization | Security-critical, complex logic |
|
|
756
|
+
| Payment processing | Financial data, zero tolerance for bugs |
|
|
757
|
+
| Database migrations | Schema changes affect entire system |
|
|
758
|
+
| Multi-step workflows | Complex state transitions |
|
|
759
|
+
| Business logic (calculations) | Need to verify correctness |
|
|
760
|
+
|
|
761
|
+
### When to Use Test-Alongside:
|
|
762
|
+
|
|
763
|
+
**Use when `tdd_required: false`:**
|
|
764
|
+
|
|
765
|
+
| Scenario | Why Test-Alongside? |
|
|
766
|
+
|----------|---------------------|
|
|
767
|
+
| Simple CRUD endpoints | Straightforward, low risk |
|
|
768
|
+
| Read-only queries | No side effects |
|
|
769
|
+
| Presentational UI | Visual, not logic-heavy |
|
|
770
|
+
| Static pages | No dynamic behavior |
|
|
771
|
+
|
|
772
|
+
### Test-Alongside Workflow:
|
|
773
|
+
|
|
774
|
+
```
|
|
775
|
+
1. Write implementation first
|
|
776
|
+
2. Write tests after
|
|
777
|
+
3. Run tests to verify
|
|
778
|
+
4. Refactor if needed
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
**Example:**
|
|
782
|
+
|
|
783
|
+
```python
|
|
784
|
+
# Step 1: Write implementation
|
|
785
|
+
@router.get("/api/users")
|
|
786
|
+
async def list_users(db: AsyncSession = Depends(get_db)):
|
|
787
|
+
result = await db.execute(select(User))
|
|
788
|
+
return {"users": [user.dict() for user in result.scalars().all()]}
|
|
789
|
+
|
|
790
|
+
# Step 2: Write tests
|
|
791
|
+
@pytest.mark.asyncio
|
|
792
|
+
async def test_list_users(client):
|
|
793
|
+
response = await client.get("/api/users")
|
|
794
|
+
assert response.status_code == 200
|
|
795
|
+
assert "users" in response.json()
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## 📊 Logging TDD Phases
|
|
801
|
+
|
|
802
|
+
### RED Phase Log:
|
|
803
|
+
|
|
804
|
+
```json
|
|
805
|
+
{
|
|
806
|
+
"event": "tdd_red_phase",
|
|
807
|
+
"task": "Implement POST /api/auth/login",
|
|
808
|
+
"test_file": "tests/test_auth.py",
|
|
809
|
+
"tests_written": 3,
|
|
810
|
+
"status": "fail",
|
|
811
|
+
"expected": "Tests should fail - endpoint not implemented yet"
|
|
812
|
+
}
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### GREEN Phase Log:
|
|
816
|
+
|
|
817
|
+
```json
|
|
818
|
+
{
|
|
819
|
+
"event": "tdd_green_phase",
|
|
820
|
+
"task": "Implement POST /api/auth/login",
|
|
821
|
+
"tests_passed": 2,
|
|
822
|
+
"tests_failed": 1,
|
|
823
|
+
"implementation": "app/api/auth.py",
|
|
824
|
+
"status": "partial_pass",
|
|
825
|
+
"note": "Minimal implementation complete, refactor needed"
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### REFACTOR Phase Log:
|
|
830
|
+
|
|
831
|
+
```json
|
|
832
|
+
{
|
|
833
|
+
"event": "tdd_refactor_phase",
|
|
834
|
+
"task": "Implement POST /api/auth/login",
|
|
835
|
+
"tests_passing": 3,
|
|
836
|
+
"improvements": [
|
|
837
|
+
"Added Pydantic validation schema",
|
|
838
|
+
"Added database integration",
|
|
839
|
+
"Added JWT token generation",
|
|
840
|
+
"Added structured logging",
|
|
841
|
+
"Added proper error handling"
|
|
842
|
+
],
|
|
843
|
+
"status": "complete"
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
---
|
|
848
|
+
|
|
849
|
+
## 🎯 Quick Reference
|
|
850
|
+
|
|
851
|
+
### TDD Checklist:
|
|
852
|
+
|
|
853
|
+
```markdown
|
|
854
|
+
Before starting TDD workflow:
|
|
855
|
+
|
|
856
|
+
[ ] Verify `tdd_required: true` flag
|
|
857
|
+
[ ] Choose appropriate testing framework (Pytest, Vitest, Jest)
|
|
858
|
+
[ ] Read existing test patterns (search for similar tests)
|
|
859
|
+
|
|
860
|
+
🔴 RED Phase:
|
|
861
|
+
[ ] Write comprehensive test cases (success + errors + edge)
|
|
862
|
+
[ ] Run tests → verify they FAIL for right reason
|
|
863
|
+
[ ] Log RED phase completion
|
|
864
|
+
|
|
865
|
+
🟢 GREEN Phase:
|
|
866
|
+
[ ] Write minimal implementation (hardcoded OK)
|
|
867
|
+
[ ] Run tests → verify they PASS
|
|
868
|
+
[ ] Log GREEN phase completion
|
|
869
|
+
|
|
870
|
+
🔵 REFACTOR Phase:
|
|
871
|
+
[ ] Add error handling
|
|
872
|
+
[ ] Add logging (entry, success, error)
|
|
873
|
+
[ ] Add validation
|
|
874
|
+
[ ] Add type hints/comments
|
|
875
|
+
[ ] Run tests after EACH change → verify still PASS
|
|
876
|
+
[ ] Log REFACTOR phase completion
|
|
877
|
+
|
|
878
|
+
✅ Final:
|
|
879
|
+
[ ] All tests passing
|
|
880
|
+
[ ] Code quality high (error handling, logging, types)
|
|
881
|
+
[ ] Ready for handoff
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
---
|
|
885
|
+
|
|
886
|
+
## 🔗 See Also
|
|
887
|
+
|
|
888
|
+
- `tdd-classifier.md` - Logic to determine when `tdd_required: true`
|
|
889
|
+
- `validation-framework.md` - Agent validation requirements
|
|
890
|
+
- `context-loading-protocol.md` - How agents load testing frameworks
|
|
891
|
+
- `contexts/patterns/testing.md` - General testing patterns
|