📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-05-29 08:33:53 +00:00
parent fdb52f1e96
commit 06e0d13d57
1615 changed files with 232858 additions and 0 deletions
@@ -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.
@@ -0,0 +1,686 @@
# TDD Session: Feature Implementation
Complete TDD session implementing user authentication feature from scratch.
## Session Setup
**Feature**: User authentication with email/password
**Tech Stack**: TypeScript, Bun, discriminated unions for results
**Starting Point**: No existing code
**Duration**: ~45 minutes (3 RED-GREEN-REFACTOR cycles)
## Task State Tracking
Initial todos:
```text
1. Write failing test for user authentication [in_progress]
2. Implement authentication to pass tests [pending]
3. Refactor authentication code [pending]
4. Verify implementation [pending]
```
## Cycle 1: Basic Authentication
### RED Phase (5 min)
Starting confidence: `▓░░░░` → Writing tests to define interface
**Created**: `src/auth/authenticate.test.ts`
```typescript
import { describe, test, expect } from 'bun:test'
import { authenticate } from './authenticate'
describe('authenticate', () => {
const validCreds = {
email: 'user@example.com',
password: 'ValidPass123!',
} as const
test('returns success result with valid credentials', async () => {
const result = await authenticate(validCreds)
expect(result.type).toBe('success')
if (result.type === 'success') {
expect(result.user.email).toBe(validCreds.email)
}
})
test('returns error result with invalid credentials', async () => {
const result = await authenticate({
email: 'wrong@example.com',
password: 'wrong',
})
expect(result.type).toBe('error')
if (result.type === 'error') {
expect(result.code).toBe('INVALID_CREDENTIALS')
}
})
test('returns error result with empty password', async () => {
const result = await authenticate({
email: 'user@example.com',
password: '',
})
expect(result.type).toBe('error')
if (result.type === 'error') {
expect(result.code).toBe('MISSING_PASSWORD')
}
})
})
```
**Run tests**: `bun test`
```
authenticate
✗ returns success result with valid credentials
Error: Cannot find module "./authenticate"
✗ returns error result with invalid credentials
✗ returns error result with empty password
3 failing
```
Tests fail as expected — no implementation exists yet.
**Commit**: `test: add failing tests for user authentication`
**Task Update**:
```text
1. Write failing test for user authentication [completed]
2. Implement authentication to pass tests [in_progress]
3. Refactor authentication code [pending]
4. Verify implementation [pending]
```
### GREEN Phase (8 min)
Confidence: `▓▓░░░` → Implementing minimal solution
**Created**: `src/auth/authenticate.ts`
```typescript
type User = {
id: string
email: string
passwordHash: string
}
type AuthSuccess = {
type: 'success'
user: User
}
type AuthError = {
type: 'error'
code: 'INVALID_CREDENTIALS' | 'MISSING_PASSWORD'
}
type AuthResult = AuthSuccess | AuthError
// Minimal mock database
const users: User[] = [
{
id: '1',
email: 'user@example.com',
passwordHash: '$2a$10$hashedValidPass123!',
},
]
async function findUserByEmail(email: string): Promise<User | undefined> {
return users.find(u => u.email === email)
}
async function comparePassword(password: string, hash: string): Promise<boolean> {
// Simplified for testing - in production use bcrypt
return password === 'ValidPass123!' && hash === '$2a$10$hashedValidPass123!'
}
export async function authenticate(credentials: {
email: string
password: string
}): Promise<AuthResult> {
// Check password not empty
if (!credentials.password) {
return { type: 'error', code: 'MISSING_PASSWORD' }
}
// Find user
const user = await findUserByEmail(credentials.email)
if (!user) {
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
// Verify password
const passwordMatch = await comparePassword(credentials.password, user.passwordHash)
if (!passwordMatch) {
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
return { type: 'success', user }
}
```
**Run tests**: `bun test`
```
authenticate
✓ returns success result with valid credentials
✓ returns error result with invalid credentials
✓ returns error result with empty password
3 passing
```
All tests pass — implementation complete.
**Commit**: `feat: implement user authentication to pass tests`
**Task Update**:
```text
1. Write failing test for user authentication [completed]
2. Implement authentication to pass tests [completed]
3. Refactor authentication code [in_progress]
4. Verify implementation [pending]
```
### REFACTOR Phase (7 min)
Confidence: `▓▓▓░░` → Improving structure and types
Observations:
- Types scattered throughout file
- Password comparison hardcoded
- No clear separation of concerns
**Refactored**: `src/auth/authenticate.ts`
```typescript
// Extract types to top
type User = {
id: string
email: string
passwordHash: string
}
type AuthSuccess = {
type: 'success'
user: User
}
type AuthError = {
type: 'error'
code: 'INVALID_CREDENTIALS' | 'MISSING_PASSWORD' | 'INVALID_EMAIL'
}
type AuthResult = AuthSuccess | AuthError
// Extract validation
function validateCredentials(credentials: {
email: string
password: string
}): AuthError | null {
if (!credentials.password) {
return { type: 'error', code: 'MISSING_PASSWORD' }
}
if (!credentials.email.includes('@')) {
return { type: 'error', code: 'INVALID_EMAIL' }
}
return null
}
// Mock database (unchanged)
const users: User[] = [
{
id: '1',
email: 'user@example.com',
passwordHash: '$2a$10$hashedValidPass123!',
},
]
async function findUserByEmail(email: string): Promise<User | undefined> {
return users.find(u => u.email === email)
}
// Extract password verification
async function verifyPassword(password: string, hash: string): Promise<boolean> {
// Simplified for testing - in production use bcrypt
return password === 'ValidPass123!' && hash === '$2a$10$hashedValidPass123!'
}
// Cleaner main function
export async function authenticate(credentials: {
email: string
password: string
}): Promise<AuthResult> {
// Validate input
const validationError = validateCredentials(credentials)
if (validationError) {
return validationError
}
// Find user
const user = await findUserByEmail(credentials.email)
if (!user) {
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
// Verify password
const isValid = await verifyPassword(credentials.password, user.passwordHash)
if (!isValid) {
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
return { type: 'success', user }
}
```
**Run tests**: `bun test`
```
authenticate
✓ returns success result with valid credentials
✓ returns error result with invalid credentials
✓ returns error result with empty password
3 passing
```
Tests still pass — refactoring successful.
**Commit**: `refactor: extract validation and password verification`
**Task Update**:
```text
1. Write failing test for user authentication [completed]
2. Implement authentication to pass tests [completed]
3. Refactor authentication code [completed]
4. Verify implementation [in_progress]
```
### VERIFY Phase (3 min)
Run full test suite with coverage:
```bash
bun test --coverage
```
```
authenticate
✓ returns success result with valid credentials
✓ returns error result with invalid credentials
✓ returns error result with empty password
Coverage:
File | % Stmts | % Branch | % Funcs | % Lines
------------------|---------|----------|---------|--------
authenticate.ts | 95.45 | 100 | 100 | 95.45
3 passing
```
Coverage ≥80% — quality standards met.
**Task Update**:
```text
1. Write failing test for user authentication [completed]
2. Implement authentication to pass tests [completed]
3. Refactor authentication code [completed]
4. Verify implementation [completed]
```
## Cycle 2: Email Validation
Starting new cycle for email validation edge cases.
**Task Update**:
```text
1. Write failing test for email validation [in_progress]
2. Implement email validation to pass tests [pending]
3. Refactor email validation [pending]
4. Verify implementation [pending]
```
### RED Phase (4 min)
Add tests for email validation edge cases:
```typescript
describe('authenticate - email validation', () => {
test('returns error for invalid email format', async () => {
const result = await authenticate({
email: 'not-an-email',
password: 'ValidPass123!',
})
expect(result.type).toBe('error')
if (result.type === 'error') {
expect(result.code).toBe('INVALID_EMAIL')
}
})
test('returns error for empty email', async () => {
const result = await authenticate({
email: '',
password: 'ValidPass123!',
})
expect(result.type).toBe('error')
if (result.type === 'error') {
expect(result.code).toBe('INVALID_EMAIL')
}
})
})
```
**Run tests**: `bun test`
```
authenticate - email validation
✓ returns error for invalid email format # Already passes!
✗ returns error for empty email
Expected code: 'INVALID_EMAIL'
Received code: 'MISSING_PASSWORD'
1 failing
```
One test passes (basic email check exists), one fails.
**Commit**: `test: add email validation edge case tests`
### GREEN Phase (3 min)
Update validation to handle empty email:
```typescript
function validateCredentials(credentials: {
email: string
password: string
}): AuthError | null {
if (!credentials.email) {
return { type: 'error', code: 'INVALID_EMAIL' }
}
if (!credentials.password) {
return { type: 'error', code: 'MISSING_PASSWORD' }
}
if (!credentials.email.includes('@')) {
return { type: 'error', code: 'INVALID_EMAIL' }
}
return null
}
```
**Run tests**: `bun test`
```
authenticate - email validation
✓ returns error for invalid email format
✓ returns error for empty email
All tests passing (5 total)
```
**Commit**: `feat: validate empty email addresses`
### REFACTOR Phase (4 min)
Extract email validation to dedicated function:
```typescript
function isValidEmail(email: string): boolean {
return email.length > 0 && email.includes('@')
}
function validateCredentials(credentials: {
email: string
password: string
}): AuthError | null {
if (!isValidEmail(credentials.email)) {
return { type: 'error', code: 'INVALID_EMAIL' }
}
if (!credentials.password) {
return { type: 'error', code: 'MISSING_PASSWORD' }
}
return null
}
```
**Run tests**: `bun test` — All passing
**Commit**: `refactor: extract email validation function`
### VERIFY Phase (2 min)
```bash
bun test --coverage
```
Coverage: 96.2% — excellent.
## Cycle 3: Rate Limiting
Implementing rate limiting for failed authentication attempts.
### RED Phase (6 min)
Add tests for rate limiting:
```typescript
describe('authenticate - rate limiting', () => {
test('allows authentication after successful login', async () => {
const validCreds = {
email: 'user@example.com',
password: 'ValidPass123!',
}
const result1 = await authenticate(validCreds)
const result2 = await authenticate(validCreds)
expect(result1.type).toBe('success')
expect(result2.type).toBe('success')
})
test('blocks authentication after 3 failed attempts', async () => {
const invalidCreds = {
email: 'user@example.com',
password: 'wrong',
}
// 3 failed attempts
await authenticate(invalidCreds)
await authenticate(invalidCreds)
await authenticate(invalidCreds)
// 4th attempt should be rate limited
const result = await authenticate(invalidCreds)
expect(result.type).toBe('error')
if (result.type === 'error') {
expect(result.code).toBe('RATE_LIMITED')
}
})
})
```
**Run tests**: `bun test` — Rate limit tests fail as expected
**Commit**: `test: add rate limiting tests`
### GREEN Phase (10 min)
Implement basic rate limiting:
```typescript
type AuthError = {
type: 'error'
code: 'INVALID_CREDENTIALS' | 'MISSING_PASSWORD' | 'INVALID_EMAIL' | 'RATE_LIMITED'
}
// Track failed attempts
const failedAttempts = new Map<string, number>()
function incrementFailedAttempts(email: string): void {
const current = failedAttempts.get(email) || 0
failedAttempts.set(email, current + 1)
}
function resetFailedAttempts(email: string): void {
failedAttempts.delete(email)
}
function isRateLimited(email: string): boolean {
const attempts = failedAttempts.get(email) || 0
return attempts >= 3
}
export async function authenticate(credentials: {
email: string
password: string
}): Promise<AuthResult> {
// Check rate limiting first
if (isRateLimited(credentials.email)) {
return { type: 'error', code: 'RATE_LIMITED' }
}
// Validate input
const validationError = validateCredentials(credentials)
if (validationError) {
return validationError
}
// Find user
const user = await findUserByEmail(credentials.email)
if (!user) {
incrementFailedAttempts(credentials.email)
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
// Verify password
const isValid = await verifyPassword(credentials.password, user.passwordHash)
if (!isValid) {
incrementFailedAttempts(credentials.email)
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
// Reset on success
resetFailedAttempts(credentials.email)
return { type: 'success', user }
}
```
**Run tests**: `bun test` — All 7 tests passing
**Commit**: `feat: implement rate limiting for failed authentication`
### REFACTOR Phase (6 min)
Extract rate limiting to separate module for testability:
**Created**: `src/auth/rate-limiter.ts`
```typescript
export class RateLimiter {
private attempts = new Map<string, number>()
constructor(private maxAttempts: number = 3) {}
increment(key: string): void {
const current = this.attempts.get(key) || 0
this.attempts.set(key, current + 1)
}
reset(key: string): void {
this.attempts.delete(key)
}
isLimited(key: string): boolean {
const attempts = this.attempts.get(key) || 0
return attempts >= this.maxAttempts
}
}
```
Update `authenticate.ts` to use class:
```typescript
import { RateLimiter } from './rate-limiter'
const rateLimiter = new RateLimiter(3)
export async function authenticate(credentials: {
email: string
password: string
}): Promise<AuthResult> {
// Check rate limiting first
if (rateLimiter.isLimited(credentials.email)) {
return { type: 'error', code: 'RATE_LIMITED' }
}
// ... rest unchanged ...
// On failure
if (!isValid) {
rateLimiter.increment(credentials.email)
return { type: 'error', code: 'INVALID_CREDENTIALS' }
}
// On success
rateLimiter.reset(credentials.email)
return { type: 'success', user }
}
```
**Run tests**: `bun test` — All passing
**Commit**: `refactor: extract rate limiter to separate class`
### VERIFY Phase (5 min)
Final verification with mutation testing:
```bash
bun test --coverage
bun x stryker run
```
Results:
- Coverage: 94.8%
- Mutation score: 78.3%
- All tests passing
**Task**: All completed
## Session Summary
Duration: 45 minutes
Cycles: 3 complete RED-GREEN-REFACTOR cycles
Tests: 7 tests, all passing
Coverage: 94.8% line coverage
Mutation: 78.3% mutation score
Features implemented:
1. Basic authentication with email/password
2. Email validation
3. Rate limiting for failed attempts
Code quality:
- All types explicit
- Functions single-purpose
- Tests cover happy path and edge cases
- Mutation testing verifies test quality
Next steps:
- Add integration tests with real database
- Implement actual bcrypt password hashing
- Add time-based rate limit expiration