📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -0,0 +1,420 @@
|
||||
# TDD Session: Bug Fix
|
||||
|
||||
TDD workflow for fixing a production bug with test reproduction first.
|
||||
|
||||
## Bug Report
|
||||
|
||||
**Issue**: Division by zero crashes calculator service
|
||||
**Reporter**: User reported 500 error when calculating percentage with zero total
|
||||
**Stack Trace**:
|
||||
|
||||
```
|
||||
Error: Division by zero
|
||||
at calculatePercentage (calculator.ts:45)
|
||||
at handler (api/stats.ts:23)
|
||||
```
|
||||
|
||||
**Priority**: High — causes service crash
|
||||
|
||||
## TDD Workflow
|
||||
|
||||
### Task Setup
|
||||
|
||||
```text
|
||||
1. Write failing test reproducing bug [in_progress]
|
||||
2. Fix bug with minimal code [pending]
|
||||
3. Refactor if needed [pending]
|
||||
4. Verify fix [pending]
|
||||
```
|
||||
|
||||
## RED Phase: Reproduce Bug (4 min)
|
||||
|
||||
Confidence: `▓▓░░░` → Can reproduce, know the fix
|
||||
|
||||
First, understand the current implementation:
|
||||
|
||||
**Read**: `src/calculator/percentage.ts`
|
||||
|
||||
```typescript
|
||||
export function calculatePercentage(value: number, total: number): number {
|
||||
return (value / total) * 100
|
||||
}
|
||||
```
|
||||
|
||||
Issue clear: No validation for zero total.
|
||||
|
||||
**Write failing test**: `src/calculator/percentage.test.ts`
|
||||
|
||||
```typescript
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { calculatePercentage } from './percentage'
|
||||
|
||||
describe('calculatePercentage', () => {
|
||||
// Existing tests
|
||||
test('calculates percentage correctly', () => {
|
||||
expect(calculatePercentage(25, 100)).toBe(25)
|
||||
expect(calculatePercentage(50, 200)).toBe(25)
|
||||
})
|
||||
|
||||
// NEW: Bug reproduction test
|
||||
test('handles division by zero gracefully', () => {
|
||||
const result = calculatePercentage(10, 0)
|
||||
|
||||
expect(result).toBe(0)
|
||||
// OR throw error approach:
|
||||
// expect(() => calculatePercentage(10, 0)).toThrow('Total cannot be zero')
|
||||
})
|
||||
|
||||
test('handles zero value with valid total', () => {
|
||||
expect(calculatePercentage(0, 100)).toBe(0)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Run test**: `bun test percentage.test.ts`
|
||||
|
||||
```
|
||||
calculatePercentage
|
||||
✓ calculates percentage correctly
|
||||
✗ handles division by zero gracefully
|
||||
Expected: 0
|
||||
Received: Infinity
|
||||
✓ handles zero value with valid total
|
||||
|
||||
1 failing
|
||||
```
|
||||
|
||||
Bug reproduced successfully — test fails as expected.
|
||||
|
||||
**Commit**: `test: add failing test for division by zero bug`
|
||||
|
||||
**Task Update**:
|
||||
|
||||
```text
|
||||
1. Write failing test reproducing bug [completed]
|
||||
2. Fix bug with minimal code [in_progress]
|
||||
3. Refactor if needed [pending]
|
||||
4. Verify fix [pending]
|
||||
```
|
||||
|
||||
## GREEN Phase: Fix Bug (3 min)
|
||||
|
||||
Confidence: `▓▓▓▓░` → Know exact fix needed
|
||||
|
||||
**Update**: `src/calculator/percentage.ts`
|
||||
|
||||
```typescript
|
||||
export function calculatePercentage(value: number, total: number): number {
|
||||
// Fix: Handle division by zero
|
||||
if (total === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return (value / total) * 100
|
||||
}
|
||||
```
|
||||
|
||||
**Run tests**: `bun test percentage.test.ts`
|
||||
|
||||
```
|
||||
calculatePercentage
|
||||
✓ calculates percentage correctly
|
||||
✓ handles division by zero gracefully
|
||||
✓ handles zero value with valid total
|
||||
|
||||
3 passing
|
||||
```
|
||||
|
||||
All tests pass — bug fixed.
|
||||
|
||||
**Verify no regressions**: `bun test`
|
||||
|
||||
```
|
||||
All test suites passing (23 tests total)
|
||||
```
|
||||
|
||||
No other tests broken — safe fix.
|
||||
|
||||
**Commit**: `fix: handle division by zero in percentage calculation`
|
||||
|
||||
**Task Update**:
|
||||
|
||||
```text
|
||||
1. Write failing test reproducing bug [completed]
|
||||
2. Fix bug with minimal code [completed]
|
||||
3. Refactor if needed [in_progress]
|
||||
4. Verify fix [pending]
|
||||
```
|
||||
|
||||
## REFACTOR Phase: Consider Improvements (5 min)
|
||||
|
||||
Current fix works but could be more robust. Consider:
|
||||
|
||||
1. Should negative totals be handled?
|
||||
2. Should we return error instead of 0?
|
||||
3. Type safety improvements?
|
||||
|
||||
**Decision**: Add input validation, use Result type for better error handling.
|
||||
|
||||
**Refactored**: `src/calculator/percentage.ts`
|
||||
|
||||
```typescript
|
||||
type PercentageResult =
|
||||
| { type: 'success'; value: number }
|
||||
| { type: 'error'; code: 'ZERO_TOTAL' | 'NEGATIVE_TOTAL' }
|
||||
|
||||
export function calculatePercentage(
|
||||
value: number,
|
||||
total: number
|
||||
): PercentageResult {
|
||||
// Validate total
|
||||
if (total === 0) {
|
||||
return { type: 'error', code: 'ZERO_TOTAL' }
|
||||
}
|
||||
|
||||
if (total < 0) {
|
||||
return { type: 'error', code: 'NEGATIVE_TOTAL' }
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'success',
|
||||
value: (value / total) * 100,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Update tests** to match new signature:
|
||||
|
||||
```typescript
|
||||
describe('calculatePercentage', () => {
|
||||
test('calculates percentage correctly', () => {
|
||||
const result1 = calculatePercentage(25, 100)
|
||||
const result2 = calculatePercentage(50, 200)
|
||||
|
||||
expect(result1).toEqual({ type: 'success', value: 25 })
|
||||
expect(result2).toEqual({ type: 'success', value: 25 })
|
||||
})
|
||||
|
||||
test('returns error for division by zero', () => {
|
||||
const result = calculatePercentage(10, 0)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'error',
|
||||
code: 'ZERO_TOTAL',
|
||||
})
|
||||
})
|
||||
|
||||
test('returns error for negative total', () => {
|
||||
const result = calculatePercentage(10, -100)
|
||||
|
||||
expect(result).toEqual({
|
||||
type: 'error',
|
||||
code: 'NEGATIVE_TOTAL',
|
||||
})
|
||||
})
|
||||
|
||||
test('handles zero value with valid total', () => {
|
||||
const result = calculatePercentage(0, 100)
|
||||
|
||||
expect(result).toEqual({ type: 'success', value: 0 })
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Run tests**: `bun test percentage.test.ts`
|
||||
|
||||
```
|
||||
calculatePercentage
|
||||
✓ calculates percentage correctly
|
||||
✓ returns error for division by zero
|
||||
✓ returns error for negative total
|
||||
✓ handles zero value with valid total
|
||||
|
||||
4 passing
|
||||
```
|
||||
|
||||
**Update usage** in `api/stats.ts`:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
const percentage = calculatePercentage(completed, total)
|
||||
return { percentage }
|
||||
|
||||
// After
|
||||
const result = calculatePercentage(completed, total)
|
||||
if (result.type === 'error') {
|
||||
return { error: 'Invalid calculation', code: result.code }
|
||||
}
|
||||
return { percentage: result.value }
|
||||
```
|
||||
|
||||
**Run all tests**: `bun test`
|
||||
|
||||
```
|
||||
All tests passing (24 tests total)
|
||||
```
|
||||
|
||||
No regressions — refactor successful.
|
||||
|
||||
**Commit**: `refactor: use Result type for percentage calculation errors`
|
||||
|
||||
**Task Update**:
|
||||
|
||||
```text
|
||||
1. Write failing test reproducing bug [completed]
|
||||
2. Fix bug with minimal code [completed]
|
||||
3. Refactor if needed [completed]
|
||||
4. Verify fix [in_progress]
|
||||
```
|
||||
|
||||
## VERIFY Phase: Comprehensive Validation (4 min)
|
||||
|
||||
Run full test suite with coverage:
|
||||
|
||||
```bash
|
||||
bun test --coverage
|
||||
```
|
||||
|
||||
```
|
||||
Coverage Summary:
|
||||
percentage.ts: 100% statements, 100% branches
|
||||
|
||||
All tests passing (24 total)
|
||||
```
|
||||
|
||||
Run mutation testing on updated code:
|
||||
|
||||
```bash
|
||||
bun x stryker run --mutate "src/calculator/percentage.ts"
|
||||
```
|
||||
|
||||
```
|
||||
Mutation Score: 100%
|
||||
All mutants killed
|
||||
|
||||
Mutations tested:
|
||||
✓ === → !== (killed by "returns error for division by zero")
|
||||
✓ < → <= (killed by "returns error for negative total")
|
||||
✓ / → * (killed by "calculates percentage correctly")
|
||||
✓ 0 → 1 (killed by "handles zero value with valid total")
|
||||
```
|
||||
|
||||
Perfect mutation score — tests thoroughly verify behavior.
|
||||
|
||||
**Integration test** to verify API fix:
|
||||
|
||||
```typescript
|
||||
describe('GET /api/stats', () => {
|
||||
test('handles zero total gracefully', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/stats')
|
||||
.query({ completed: 10, total: 0 })
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.body).toMatchObject({
|
||||
error: 'Invalid calculation',
|
||||
code: 'ZERO_TOTAL',
|
||||
})
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
**Run integration tests**: `bun test tests/integration/`
|
||||
|
||||
```
|
||||
API Integration Tests
|
||||
✓ handles zero total gracefully
|
||||
|
||||
All integration tests passing
|
||||
```
|
||||
|
||||
**Task Update**:
|
||||
|
||||
```text
|
||||
1. Write failing test reproducing bug [completed]
|
||||
2. Fix bug with minimal code [completed]
|
||||
3. Refactor if needed [completed]
|
||||
4. Verify fix [completed]
|
||||
```
|
||||
|
||||
## Session Summary
|
||||
|
||||
**Duration**: 16 minutes total
|
||||
- RED: 4 min
|
||||
- GREEN: 3 min
|
||||
- REFACTOR: 5 min
|
||||
- VERIFY: 4 min
|
||||
|
||||
**Bug**: Division by zero crash
|
||||
**Fix**: Added validation with Result type
|
||||
**Tests**: 4 new tests + 1 integration test
|
||||
**Coverage**: 100% on changed code
|
||||
**Mutation Score**: 100%
|
||||
|
||||
**Improvements beyond minimal fix**:
|
||||
- Used discriminated union for error handling
|
||||
- Added negative total validation
|
||||
- Updated API to handle error results
|
||||
- Added integration test
|
||||
|
||||
**Production deployment**:
|
||||
- All tests passing
|
||||
- No regressions detected
|
||||
- Error handling verified
|
||||
- Ready to deploy
|
||||
|
||||
## Key TDD Bug Fix Principles
|
||||
|
||||
1. **RED first**: Always reproduce bug with failing test before fixing
|
||||
2. **Minimal GREEN**: Fix the immediate issue first
|
||||
3. **Refactor for robustness**: Improve error handling and edge cases
|
||||
4. **Verify thoroughly**: Run full suite + mutation tests + integration tests
|
||||
5. **Document in test**: Test name describes the bug being fixed
|
||||
|
||||
## Anti-patterns Avoided
|
||||
|
||||
Avoided jumping straight to fix without test:
|
||||
|
||||
```typescript
|
||||
// ❌ Wrong approach
|
||||
// 1. See bug report
|
||||
// 2. Add if (total === 0) return 0
|
||||
// 3. Deploy and hope
|
||||
|
||||
// ✓ Correct TDD approach
|
||||
// 1. Write failing test reproducing bug
|
||||
// 2. Verify test fails
|
||||
// 3. Add minimal fix
|
||||
// 4. Verify test passes
|
||||
// 5. Refactor for robustness
|
||||
// 6. Verify with mutation testing
|
||||
```
|
||||
|
||||
Avoided over-engineering initial fix:
|
||||
|
||||
```typescript
|
||||
// ❌ Too complex for first fix
|
||||
if (total === 0 || total < 0 || !isFinite(total) || isNaN(total)) {
|
||||
throw new ValidationError(...)
|
||||
}
|
||||
|
||||
// ✓ Minimal fix first (GREEN phase)
|
||||
if (total === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// ✓ Then refactor with proper error handling (REFACTOR phase)
|
||||
if (total === 0) {
|
||||
return { type: 'error', code: 'ZERO_TOTAL' }
|
||||
}
|
||||
```
|
||||
|
||||
## Commit History
|
||||
|
||||
```
|
||||
test: add failing test for division by zero bug
|
||||
fix: handle division by zero in percentage calculation
|
||||
refactor: use Result type for percentage calculation errors
|
||||
```
|
||||
|
||||
Clean, focused commits showing TDD progression.
|
||||
Reference in New Issue
Block a user