# Testing Guide

This document explains how to run tests for the Dune Weaver backend.

## Quick Start

```bash
# Install test dependencies
pip install -r requirements-dev.txt

# Run all unit tests (no hardware needed)
pytest tests/unit/ -v

# Run with coverage report
pytest tests/ --cov=modules --cov-report=term-missing
```

## Test Structure

```
tests/
├── conftest.py              # Shared fixtures
├── unit/                    # Unit tests (run in CI, no hardware)
│   ├── conftest.py
│   ├── test_api_patterns.py
│   ├── test_api_playlists.py
│   ├── test_api_status.py
│   ├── test_connection_manager.py
│   ├── test_pattern_manager.py
│   └── test_playlist_manager.py
├── integration/             # Integration tests (require hardware)
│   ├── conftest.py
│   ├── test_hardware.py
│   ├── test_playback_controls.py
│   └── test_playlist.py
└── fixtures/                # Test data files
```

## Unit Tests

Unit tests mock all hardware dependencies and run quickly. They're safe to run anywhere.

```bash
# Run all unit tests
pytest tests/unit/ -v

# Run specific test file
pytest tests/unit/test_pattern_manager.py -v

# Run specific test
pytest tests/unit/test_api_status.py::test_get_status -v

# Run tests matching a pattern
pytest tests/unit/ -v -k "playlist"
```

## Integration Tests

Integration tests require the sand table hardware to be connected. They are **skipped by default**.

```bash
# Run integration tests (with hardware connected)
pytest tests/integration/ --run-hardware -v

# Run specific integration test file
pytest tests/integration/test_playback_controls.py --run-hardware -v

# Run with output visible (helpful for debugging)
pytest tests/integration/ --run-hardware -v -s
```

### Integration Test Categories

| File | What it tests | Duration |
|------|---------------|----------|
| `test_hardware.py` | Serial connection, homing, movement, pattern execution | ~5-10 min |
| `test_playback_controls.py` | Pause, resume, stop, skip, speed control | ~5 min |
| `test_playlist.py` | Playlist modes, clear patterns, state updates | ~5 min |

### Safety Notes

- **Movement tests physically move the table** — ensure the ball path is clear
- **Homing tests run the homing sequence** — table will move to home position
- **Pattern tests execute real patterns** — star.thr runs end-to-end

## Coverage Reports

```bash
# Terminal report
pytest tests/ --cov=modules --cov-report=term-missing

# HTML report (creates htmlcov/ directory)
pytest tests/ --cov=modules --cov-report=html
open htmlcov/index.html

# XML report (for CI tools)
pytest tests/ --cov=modules --cov-report=xml
```

## CI Behavior

When `CI=true` environment variable is set:
- All `@pytest.mark.hardware` tests are automatically skipped
- Unit tests run normally

```bash
# Simulate CI environment locally
CI=true pytest tests/ -v
```

## Common Commands

| Command | Description |
|---------|-------------|
| `pytest tests/unit/ -v` | Run unit tests |
| `pytest tests/integration/ --run-hardware -v` | Run integration tests |
| `pytest tests/ --cov=modules` | Run with coverage |
| `pytest tests/ -v -k "pattern"` | Run tests matching "pattern" |
| `pytest tests/ -x` | Stop on first failure |
| `pytest tests/ --lf` | Run only last failed tests |
| `pytest tests/ -v -s` | Show print statements |

## Adding New Tests

### Unit Test Example

```python
# tests/unit/test_my_feature.py
import pytest
from unittest.mock import MagicMock, patch

def test_my_function():
    """Test description here."""
    # Arrange
    expected = "result"

    # Act
    result = my_function()

    # Assert
    assert result == expected

@pytest.mark.asyncio
async def test_async_function(async_client):
    """Test async endpoint."""
    response = await async_client.get("/my_endpoint")
    assert response.status_code == 200
```

### Integration Test Example

```python
# tests/integration/test_my_hardware.py
import pytest

@pytest.mark.hardware
@pytest.mark.slow
def test_hardware_operation(hardware_port, run_hardware):
    """Test that requires real hardware."""
    if not run_hardware:
        pytest.skip("Hardware tests disabled")

    from modules.connection import connection_manager

    conn = connection_manager.SerialConnection(hardware_port)
    try:
        assert conn.is_connected()
        # ... test hardware operation
    finally:
        conn.close()
```

## Troubleshooting

### Tests hang or timeout
- Check if hardware is connected and powered on
- Verify serial port is not in use by another application
- Try running with `-s` flag to see output

### Import errors
- Ensure you're in the project root directory
- Install dependencies: `pip install -r requirements-dev.txt`

### Hardware tests skip unexpectedly
- Make sure to pass `--run-hardware` flag
- Check that `CI` environment variable is not set

### Coverage shows 0%
- Ensure `relative_files = true` is in pyproject.toml
- Run from project root directory
