# Testing Strategy

**Status**: Placeholder
**Cross-Cutting Concern**: All Phases, Critical in Phase 5

---

## Overview

Comprehensive testing approach covering unit, integration, E2E, load, and security testing to ensure production-ready quality.

## Testing Pyramid

```
           /\
          /  \         E2E Tests (10%)
         /    \        - Full game flows
        /------\       - Multi-user scenarios
       /        \
      /  Integ.  \     Integration Tests (30%)
     /   Tests    \    - WebSocket flows
    /              \   - Database operations
   /----------------\
  /                  \ Unit Tests (60%)
 /   Unit Tests      \- Core logic
/______________________\- Pure functions
```

## Test Coverage Goals

- **Overall Coverage**: >80%
- **Core Game Logic**: >90%
- **WebSocket Handlers**: >85%
- **Database Operations**: >75%
- **Frontend Components**: >70%

## Testing Tools

### Backend
- **pytest**: Unit and integration tests
- **pytest-asyncio**: Async test support
- **pytest-cov**: Coverage reporting
- **httpx**: API client testing
- **python-socketio[client]**: WebSocket testing
- **factory_boy**: Test data generation
- **Faker**: Fake data generation

### Frontend
- **Vitest**: Unit testing (fast, Vite-native)
- **Testing Library**: Component testing
- **Cypress**: E2E testing
- **Playwright**: Alternative E2E testing
- **Lighthouse CI**: Performance testing
- **axe-core**: Accessibility testing

### Load Testing
- **Locust**: Distributed load testing
- **Artillery**: Alternative load testing

### Security Testing
- **Safety**: Dependency vulnerability scanning
- **Bandit**: Python security linting
- **OWASP ZAP**: Security scanning
- **npm audit**: Frontend dependency scanning

## Test Organization

### Backend Structure
```
backend/tests/
├── unit/
│   ├── test_game_engine.py
│   ├── test_state_manager.py
│   ├── test_play_resolver.py
│   ├── test_dice.py
│   └── test_validators.py
├── integration/
│   ├── test_websocket.py
│   ├── test_database.py
│   ├── test_api_client.py
│   └── test_full_turn.py
├── e2e/
│   ├── test_full_game.py
│   ├── test_reconnection.py
│   └── test_state_recovery.py
├── load/
│   └── locustfile.py
├── conftest.py          # Shared fixtures
└── factories.py         # Test data factories
```

### Frontend Structure
```
frontend-{league}/
├── tests/
│   ├── unit/
│   │   ├── composables/
│   │   └── utils/
│   └── components/
│       ├── Game/
│       ├── Decisions/
│       └── Display/
└── cypress/
    ├── e2e/
    │   ├── game-flow.cy.ts
    │   ├── auth.cy.ts
    │   └── spectator.cy.ts
    └── support/
```

## Key Test Scenarios

### Unit Tests
- Dice roll distribution (statistical validation)
- Play outcome resolution (all d20 results)
- State transitions
- Input validation
- Player model instantiation

### Integration Tests
- WebSocket event flow (connect → join → action → update)
- Database persistence and recovery
- API client responses
- Multi-turn game sequences

### E2E Tests
- Complete 9-inning game
- Game with substitutions
- AI opponent game
- Spectator joining active game
- Reconnection after disconnect
- Multiple concurrent games

### Load Tests
- 10 concurrent games
- 50 concurrent WebSocket connections
- Sustained load over 30 minutes
- Spike testing (sudden load increase)

### Security Tests
- SQL injection attempts
- XSS attempts
- CSRF protection
- Authorization bypass attempts
- Rate limit enforcement

## Example Test Cases

### Unit Test (Backend)
```python
# tests/unit/test_dice.py
import pytest
from app.core.dice import DiceRoller

def test_dice_roll_range():
    """Test that dice rolls are within valid range"""
    roller = DiceRoller()
    for _ in range(1000):
        roll = roller.roll_d20()
        assert 1 <= roll <= 20

def test_dice_distribution():
    """Test that dice rolls are reasonably distributed"""
    roller = DiceRoller()
    rolls = [roller.roll_d20() for _ in range(10000)]

    # Each number should appear roughly 500 times (±10%)
    for num in range(1, 21):
        count = rolls.count(num)
        assert 450 <= count <= 550
```

### Integration Test (Backend)
```python
# tests/integration/test_websocket.py
import pytest
import socketio

@pytest.mark.asyncio
async def test_game_action_flow(sio_client, test_game):
    """Test complete action flow through WebSocket"""

    # Connect
    await sio_client.connect('http://localhost:8000', auth={'token': 'valid-jwt'})

    # Join game
    await sio_client.emit('join_game', {'game_id': test_game.id, 'role': 'player'})

    # Wait for game state
    response = await sio_client.receive()
    assert response[0] == 'game_state_update'

    # Send defensive decision
    await sio_client.emit('set_defense', {
        'game_id': test_game.id,
        'positioning': 'standard'
    })

    # Verify decision recorded
    response = await sio_client.receive()
    assert response[0] == 'decision_recorded'
```

### Component Test (Frontend)
```typescript
// tests/components/Game/GameBoard.spec.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import GameBoard from '@/components/Game/GameBoard.vue'

describe('GameBoard', () => {
  it('displays runners on base correctly', () => {
    const runners = {
      first: null,
      second: 12345,
      third: null
    }

    const wrapper = mount(GameBoard, {
      props: { runners }
    })

    expect(wrapper.find('[data-base="second"]').exists()).toBe(true)
    expect(wrapper.find('[data-base="first"]').exists()).toBe(false)
  })
})
```

### E2E Test (Frontend)
```typescript
// cypress/e2e/game-flow.cy.ts
describe('Complete Game Flow', () => {
  it('can play through an at-bat', () => {
    cy.login() // Custom command
    cy.visit('/games/create')

    cy.get('[data-test="create-game"]').click()
    cy.get('[data-test="game-mode-live"]').click()
    cy.get('[data-test="start-game"]').click()

    // Wait for game to start
    cy.get('[data-test="game-board"]').should('be.visible')

    // Make defensive decision
    cy.get('[data-test="defense-standard"]').click()
    cy.get('[data-test="confirm-decision"]').click()

    // Verify decision recorded
    cy.get('[data-test="waiting-indicator"]').should('be.visible')
  })
})
```

## Continuous Integration

### GitHub Actions Example
```yaml
name: Test Suite

on: [push, pull_request]

jobs:
  backend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install -r backend/requirements-dev.txt
      - run: pytest backend/tests/ --cov --cov-report=xml
      - uses: codecov/codecov-action@v3

  frontend-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm run test:unit
      - run: npm run test:e2e
```

## Test Data Management

### Fixtures
- **Shared fixtures** in `conftest.py`
- **Factory pattern** for creating test objects
- **Database seeding** for integration tests
- **Mock data** for external APIs

### Test Isolation
- Each test should be independent
- Database rollback after each test
- Clean in-memory state between tests
- No shared mutable state

## Performance Testing

### Metrics to Track
- Response time (p50, p95, p99)
- Throughput (requests/second)
- Error rate
- CPU and memory usage
- Database query time

### Load Test Scenarios
1. **Normal Load**: 5 concurrent games
2. **Peak Load**: 10 concurrent games
3. **Stress Test**: 20 concurrent games (breaking point)
4. **Spike Test**: 2 → 10 games instantly

## Reference Documents

- [PRD Lines 1063-1101](../prd-web-scorecard-1.1.md) - Testing strategy
- [05-testing-launch.md](./05-testing-launch.md) - Phase 5 testing details

---

**Note**: This is a placeholder to be expanded with specific test implementations during development.