# Mock API for Integration Testing

The **Mock Flask API** provides a lightweight, self-contained REST API for integration testing. It's perfect for testing the socialseed-e2e framework itself or as a reference implementation when building your own tests.

## Overview

The mock API simulates a real-world REST API with:
- Health check endpoint
- Complete user CRUD operations
- Authentication system (login/register)
- In-memory data storage with seed data
- Pagination and search capabilities
- Proper HTTP status codes and error handling

## Quick Start

### Running the Mock API

#### Option 1: Direct Execution (for manual testing)

```bash
# From project root
python tests/fixtures/mock_api.py
```

Output:
```
🚀 Starting Mock API Server...
📍 URL: http://localhost:8765
📖 Endpoints:
   GET    /health
   GET    /api/users
   POST   /api/users
   GET    /api/users/{id}
   PUT    /api/users/{id}
   DELETE /api/users/{id}
   POST   /api/auth/login
   POST   /api/auth/register

Press Ctrl+C to stop
```

#### Option 2: As Pytest Fixture (for automated tests)

```python
import requests

def test_api(mock_api_url):
    """Test using the mock API fixture."""
    response = requests.get(f"{mock_api_url}/health")
    assert response.status_code == 200
```

## Available Endpoints

### Health Check

```
GET /health
```

Returns server status and timestamp.

**Response:**
```json
{
  "status": "healthy",
  "timestamp": "2026-01-31T12:00:00",
  "service": "mock-api",
  "version": "1.0.0"
}
```

### Users CRUD

#### List Users
```
GET /api/users?page=1&limit=10&search=keyword
```

Query parameters:
- `page`: Page number (default: 1)
- `limit`: Items per page (default: 10)
- `search`: Search in name or email (optional)

**Response:**
```json
{
  "items": [...],
  "total": 2,
  "page": 1,
  "limit": 10,
  "total_pages": 1
}
```

#### Create User
```
POST /api/users
```

**Request body:**
```json
{
  "email": "user@example.com",
  "password": "secret123",
  "name": "John Doe",
  "role": "user"
}
```

**Response:** `201 Created`
```json
{
  "id": "uuid-here",
  "email": "user@example.com",
  "name": "John Doe",
  "role": "user",
  "created_at": "...",
  "updated_at": "..."
}
```

#### Get User
```
GET /api/users/{id}
```

**Response:** `200 OK` or `404 Not Found`

#### Update User
```
PUT /api/users/{id}
```

**Request body:**
```json
{
  "name": "Updated Name",
  "email": "new@example.com",
  "role": "admin"
}
```

**Response:** `200 OK`

#### Delete User
```
DELETE /api/users/{id}
```

**Response:** `204 No Content`

### Authentication

#### Login
```
POST /api/auth/login
```

**Request body:**
```json
{
  "email": "admin@example.com",
  "password": "admin123"
}
```

**Response:**
```json
{
  "token": "auth-token-here",
  "user": {...}
}
```

#### Register
```
POST /api/auth/register
```

**Request body:**
```json
{
  "email": "new@example.com",
  "password": "secret123",
  "name": "New User"
}
```

**Response:** `201 Created`
```json
{
  "token": "auth-token-here",
  "user": {...}
}
```

## Seeded Data

The mock API comes with 2 pre-configured users:

| Email | Password | Role | Purpose |
|-------|----------|------|---------|
| admin@example.com | admin123 | admin | Admin testing |
| user@example.com | user123 | user | Regular user testing |

## Pytest Fixtures

### Available Fixtures

#### `mock_api_server`
Session-scoped fixture that starts the mock API in a background thread.

```python
def test_with_server(mock_api_server):
    url = mock_api_server.get_base_url()
    # Server is already running
```

#### `mock_api_url`
Convenience fixture providing the base URL string.

```python
def test_with_url(mock_api_url):
    response = requests.get(f"{mock_api_url}/health")
```

#### `mock_api_reset`
Function-scoped fixture that resets data before each test.

```python
def test_with_clean_data(mock_api_url, mock_api_reset):
    # API is reset to initial state with 2 seeded users
    response = requests.get(f"{mock_api_url}/api/users")
    assert response.json()['total'] == 2
```

#### `sample_user_data`
Provides sample user data for testing.

```python
def test_create(mock_api_url, sample_user_data):
    response = requests.post(
        f"{mock_api_url}/api/users",
        json=sample_user_data
    )
    assert response.status_code == 201
```

#### `admin_credentials` / `user_credentials`
Pre-configured credentials for seeded users.

```python
def test_login(mock_api_url, admin_credentials):
    response = requests.post(
        f"{mock_api_url}/api/auth/login",
        json=admin_credentials
    )
    assert response.status_code == 200
```

### Fixture Scopes

| Fixture | Scope | Description |
|---------|-------|-------------|
| `mock_api_server` | session | Server runs once for all tests |
| `mock_api_url` | session | URL string from server |
| `mock_api_reset` | function | Resets data before each test |
| `sample_user_data` | function | Fresh sample data each test |
| `admin_credentials` | function | Admin credentials |
| `user_credentials` | function | User credentials |

## For AI Agents

### Understanding the Architecture

```
tests/
├── fixtures/
│   └── mock_api.py          # MockAPIServer class
├── conftest.py              # Pytest fixtures
└── integration/
    └── test_mock_api_integration.py  # Example tests
```

### Key Classes

#### `MockAPIServer`

The main server class that wraps Flask:

```python
from tests.fixtures.mock_api import MockAPIServer

# Create instance
server = MockAPIServer(port=8765)

# Access data
print(server.users)           # Dict of users
print(server.auth_tokens)     # Dict of active tokens

# Control server
server.run(debug=True)        # Blocking
server.reset()                # Reset to initial state
url = server.get_base_url()   # Get "http://localhost:8765"
```

### Writing Tests with Mock API

#### Pattern 1: Simple API Test

```python
import requests

def test_health_check(mock_api_url):
    """Basic health check test."""
    response = requests.get(f"{mock_api_url}/health")
    assert response.status_code == 200
    data = response.json()
    assert data['status'] == 'healthy'
```

#### Pattern 2: CRUD Operations Test

```python
def test_user_lifecycle(mock_api_url, mock_api_reset):
    """Test complete user CRUD with clean state."""
    # Create
    create_resp = requests.post(
        f"{mock_api_url}/api/users",
        json={"email": "test@test.com", "password": "pass", "name": "Test"}
    )
    assert create_resp.status_code == 201
    user_id = create_resp.json()['id']

    # Read
    get_resp = requests.get(f"{mock_api_url}/api/users/{user_id}")
    assert get_resp.status_code == 200

    # Update
    update_resp = requests.put(
        f"{mock_api_url}/api/users/{user_id}",
        json={"name": "Updated"}
    )
    assert update_resp.json()['name'] == "Updated"

    # Delete
    delete_resp = requests.delete(f"{mock_api_url}/api/users/{user_id}")
    assert delete_resp.status_code == 204
```

#### Pattern 3: Authentication Flow

```python
def test_auth_flow(mock_api_url, mock_api_reset):
    """Test registration and login flow."""
    # Register new user
    register_resp = requests.post(
        f"{mock_api_url}/api/auth/register",
        json={"email": "new@test.com", "password": "pass", "name": "New"}
    )
    assert register_resp.status_code == 201
    token = register_resp.json()['token']

    # Login with same credentials
    login_resp = requests.post(
        f"{mock_api_url}/api/auth/login",
        json={"email": "new@test.com", "password": "pass"}
    )
    assert login_resp.status_code == 200
    assert 'token' in login_resp.json()
```

### Best Practices for AI Agents

1. **Always use `mock_api_reset`** for tests that modify data
   - Ensures tests don't interfere with each other
   - Provides predictable initial state

2. **Use seeded credentials** for authentication tests
   - `admin_credentials` and `user_credentials` fixtures
   - Avoid creating users just to test login

3. **Test error cases**
   - 404 for missing resources
   - 400 for invalid input
   - 409 for duplicates
   - 401 for invalid auth

4. **Verify response structure**
   - Check status codes
   - Verify JSON structure
   - Ensure passwords are NOT returned

### Example: Complete Test Suite

```python
import requests
import pytest

class TestUserAPI:
    """Comprehensive tests for user endpoints."""

    def test_list_users_pagination(self, mock_api_url):
        """Test pagination works correctly."""
        resp = requests.get(f"{mock_api_url}/api/users?limit=1")
        data = resp.json()
        assert len(data['items']) == 1
        assert data['total_pages'] >= 2

    def test_search_users(self, mock_api_url):
        """Test search functionality."""
        resp = requests.get(f"{mock_api_url}/api/users?search=admin")
        data = resp.json()
        assert all('admin' in u['email'] for u in data['items'])

    def test_create_duplicate_fails(self, mock_api_url):
        """Test duplicate email returns 409."""
        resp = requests.post(
            f"{mock_api_url}/api/users",
            json={"email": "admin@example.com", "password": "x", "name": "X"}
        )
        assert resp.status_code == 409
```

## Troubleshooting

### Port Already in Use

If port 8765 is occupied:

```python
# Use different port
server = MockAPIServer(port=9999)
```

### Server Not Starting in Tests

The fixture has built-in retry logic (30 retries, 0.1s delay). If it fails:

```python
# Check if something else is using port 8765
lsof -i :8765

# Or modify the port in conftest.py
server = MockAPIServer(port=9999)  # Change port
```

### Data Persistence Between Tests

By default, data persists between tests for performance. Use `mock_api_reset` to clean:

```python
def test_with_reset(mock_api_url, mock_api_reset):
    # Data is fresh for this test
    pass

def test_without_reset(mock_api_url):
    # Data persists from previous tests
    pass
```

## Advanced Usage

### Customizing Seed Data

Edit `tests/fixtures/mock_api.py`:

```python
def _seed_data(self) -> None:
    """Override with your own seed data."""
    self.users = {
        "custom-id": {
            "id": "custom-id",
            "email": "custom@example.com",
            "password": "custompass",
            "name": "Custom User",
            "role": "user",
            "created_at": datetime.utcnow().isoformat(),
            "updated_at": datetime.utcnow().isoformat(),
        }
    }
```

### Adding Custom Endpoints

Extend the MockAPIServer class:

```python
class CustomMockAPI(MockAPIServer):
    def _register_routes(self):
        super()._register_routes()

        @self.app.route('/api/custom', methods=['GET'])
        def custom_endpoint():
            return jsonify({"custom": "data"})
```

## See Also

- [Writing Tests](writing-tests.md) - General test writing guide
- [API Reference](api-reference.md) - Framework API documentation
- `tests/integration/test_mock_api_integration.py` - Example tests
