# Vendor API Testing Guide

## Overview

Comprehensive integration test suite for vendor API endpoints that use `get_current_vendor_api` for authentication.

## Test Coverage

### Current Coverage

| Endpoint | Auth Tests | Functionality Tests | Coverage |
|----------|-----------|-------------------|----------|
| `/api/v1/vendor/auth/me` | ✅ Complete | ✅ Complete | 100% |
| `/api/v1/vendor/dashboard/stats` | ✅ Complete | ✅ Complete | 100% |
| `/api/v1/vendor/profile` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/settings` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/products` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/orders` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/customers` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/inventory` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/marketplace` | ✅ Auth only | ⚠️ Pending | 50% |
| `/api/v1/vendor/analytics` | ✅ Auth only | ⚠️ Pending | 50% |

**Total Tests Created**: 58+ tests
**Tests in Structure**: 28 tests (authentication + dashboard)

## Test Files

### Location

```
tests/integration/api/v1/vendor/
├── __init__.py
├── test_authentication.py      # 30+ authentication tests
└── test_dashboard.py           # 12 dashboard tests
```

### 1. Authentication Tests

**File**: `tests/integration/api/v1/vendor/test_authentication.py`

**Purpose**: Verify authentication and authorization for vendor API endpoints

#### Test Classes

##### `TestVendorAPIAuthentication`

Core authentication tests for `get_current_vendor_api`:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
@pytest.mark.auth
class TestVendorAPIAuthentication:
    """Test authentication for vendor API endpoints"""
```

**Tests**:

- ✅ `test_vendor_auth_me_success` - Valid vendor token accepted
- ✅ `test_vendor_auth_me_no_token` - Missing token rejected (401)
- ✅ `test_vendor_auth_me_invalid_token` - Invalid token rejected (401)
- ✅ `test_vendor_auth_me_expired_token` - Expired token rejected (401)
- ✅ `test_vendor_auth_me_malformed_header` - Malformed header rejected (401)
- ✅ `test_admin_user_blocked` - Admin users blocked (403)
- ✅ `test_regular_user_blocked` - Regular users blocked (403)
- ✅ `test_inactive_vendor_user_rejected` - Inactive users rejected (401)
- ✅ `test_csrf_protection_header_required` - Requires Authorization header
- ✅ `test_csrf_protection_cookie_only_rejected` - Cookie-only auth rejected
- ✅ `test_concurrent_requests` - Concurrent requests with same token
- ✅ `test_token_with_missing_claims` - Token with missing claims rejected
- ✅ `test_empty_authorization_header` - Empty header rejected

##### `TestVendorAPIConsistency`

Consistency checks across all vendor endpoints:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
class TestVendorAPIConsistency:
    """Test that all vendor API endpoints have consistent auth behavior"""
```

**Tests**:

- ✅ Verifies all vendor endpoints require authentication
- ✅ Ensures consistent error responses
- ✅ Checks CSRF protection across all endpoints

#### What's Tested

**Authentication (`get_current_vendor_api`)**:
- [x] Requires Authorization header (not cookies)
- [x] Validates JWT token format
- [x] Checks token expiration
- [x] Verifies token signature
- [x] Extracts user from token
- [x] Verifies user exists and is active

**Authorization**:
- [x] Blocks admin users (403)
- [x] Blocks regular users (403)
- [x] Accepts vendor users (200/404)
- [x] Checks vendor-role requirement

**CSRF Protection**:
- [x] API endpoints require header auth
- [x] Cookie-only auth rejected
- [x] Consistent across all endpoints

**Vendor Context**:
- [x] Vendor association required
- [x] Inactive vendors rejected
- [x] Vendor data isolation
- [x] VendorUser relationship working

### 2. Dashboard Tests

**File**: `tests/integration/api/v1/vendor/test_dashboard.py`

**Purpose**: Test vendor dashboard statistics endpoint

#### Test Class

##### `TestVendorDashboardAPI`

Dashboard functionality tests:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
class TestVendorDashboardAPI:
    """Test vendor dashboard stats endpoint"""
```

**Tests**:

- ✅ `test_dashboard_stats_structure` - Correct response structure
- ✅ `test_dashboard_stats_vendor_isolation` - Only shows vendor's data
- ✅ `test_dashboard_stats_empty_vendor` - Empty vendor returns zeros
- ✅ `test_dashboard_stats_product_counts` - Accurate product counts
- ✅ `test_dashboard_stats_order_counts` - Accurate order counts
- ✅ `test_dashboard_stats_customer_counts` - Accurate customer counts
- ✅ `test_dashboard_stats_revenue` - Accurate revenue calculations
- ✅ `test_dashboard_stats_no_vendor_association` - User not associated (403)
- ✅ `test_dashboard_stats_inactive_vendor` - Inactive vendor (404)
- ✅ `test_dashboard_stats_performance` - Response time < 2 seconds
- ✅ `test_dashboard_stats_multiple_requests` - Consistency across requests
- ✅ `test_dashboard_stats_data_types` - Correct data types

#### What's Tested

**Data Structure**:
- [x] Correct response structure
- [x] All required fields present
- [x] Correct data types

**Business Logic**:
- [x] Vendor isolation (only shows own data)
- [x] Empty vendor returns zeros
- [x] Accurate product counts (total, active)
- [x] Orders, customers, revenue stats

**Error Cases**:
- [x] User not associated with vendor (403)
- [x] Inactive vendor (404)
- [x] Missing vendor association

**Performance**:
- [x] Response time < 2 seconds
- [x] Multiple requests consistency
- [x] Caching behavior

## Test Fixtures

### Authentication Fixtures

Located in `tests/fixtures/auth_fixtures.py`:

#### `test_vendor_user`

Creates a user with `role="vendor"`:

```python
@pytest.fixture
def test_vendor_user(db, auth_manager):
    """Create a test vendor user with unique username"""
    unique_id = str(uuid.uuid4())[:8]
    hashed_password = auth_manager.hash_password("vendorpass123")
    user = User(
        email=f"vendor_{unique_id}@example.com",
        username=f"vendoruser_{unique_id}",
        hashed_password=hashed_password,
        role="vendor",
        is_active=True,
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user
```

**Usage**: Base fixture for vendor-role users

#### `vendor_user_headers`

Returns Authorization headers for vendor user:

```python
@pytest.fixture
def vendor_user_headers(client, test_vendor_user):
    """Get authentication headers for vendor user"""
    response = client.post(
        "/api/v1/auth/login",
        json={
            "username": test_vendor_user.username,
            "password": "vendorpass123"
        },
    )
    token = response.json()["access_token"]
    return {"Authorization": f"Bearer {token}"}
```

**Usage**: Use for authenticated vendor API requests

**Format**: `{"Authorization": "Bearer <token>"}`

### Vendor Fixtures

Located in `tests/fixtures/vendor_fixtures.py`:

#### `test_vendor_with_vendor_user`

Creates a vendor owned by vendor user:

```python
@pytest.fixture
def test_vendor_with_vendor_user(db, test_vendor_user):
    """Create a vendor owned by a vendor user"""
    vendor = Vendor(
        vendor_code=f"VENDORAPI_{unique_id}",
        subdomain=f"vendorapi{unique_id.lower()}",
        name=f"Vendor API Test {unique_id}",
        owner_user_id=test_vendor_user.id,
        is_active=True,
        is_verified=True,
    )
    db.add(vendor)
    db.commit()

    # Create VendorUser association
    vendor_user = VendorUser(
        vendor_id=vendor.id,
        user_id=test_vendor_user.id,
        is_owner=True,
        is_active=True,
    )
    db.add(vendor_user)
    db.commit()

    return vendor
```

**Usage**: Fully configured vendor for API testing with VendorUser association

## Running Tests

### All Vendor Tests

```bash
# Run all vendor integration tests
pytest tests/integration/api/v1/vendor/ -v

# With output
pytest tests/integration/api/v1/vendor/ -v -s
```

### Specific Test Files

```bash
# Authentication tests only
pytest tests/integration/api/v1/vendor/test_authentication.py -v

# Dashboard tests only
pytest tests/integration/api/v1/vendor/test_dashboard.py -v
```

### Specific Test Classes

```bash
# Authentication tests class
pytest tests/integration/api/v1/vendor/test_authentication.py::TestVendorAPIAuthentication -v

# Dashboard tests class
pytest tests/integration/api/v1/vendor/test_dashboard.py::TestVendorDashboardAPI -v
```

### Specific Tests

```bash
# Single authentication test
pytest tests/integration/api/v1/vendor/test_authentication.py::TestVendorAPIAuthentication::test_vendor_auth_me_success -v

# Single dashboard test
pytest tests/integration/api/v1/vendor/test_dashboard.py::TestVendorDashboardAPI::test_dashboard_stats_structure -v
```

### With Coverage

```bash
# Coverage for all vendor tests
pytest tests/integration/api/v1/vendor/ \
  --cov=app/api/v1/vendor \
  --cov-report=html

# View coverage report
open htmlcov/index.html

# Coverage for specific endpoint
pytest tests/integration/api/v1/vendor/test_dashboard.py \
  --cov=app/api/v1/vendor/dashboard \
  --cov-report=term-missing
```

### Using Markers

```bash
# All vendor tests
pytest -m vendor -v

# Vendor API tests only
pytest -m "vendor and api" -v

# Vendor authentication tests
pytest -m "vendor and auth" -v
```

## Test Examples

### Basic Authenticated Request

```python
def test_vendor_endpoint(client, vendor_user_headers):
    """Test vendor endpoint with authentication"""
    response = client.get(
        "/api/v1/vendor/some-endpoint",
        headers=vendor_user_headers
    )

    assert response.status_code == 200
    data = response.json()
    assert "expected_field" in data
```

### Test with Vendor Context

```python
def test_with_vendor_data(
    client,
    vendor_user_headers,
    test_vendor_with_vendor_user,
    db
):
    """Test with specific vendor data"""
    vendor = test_vendor_with_vendor_user

    # Create test data for this vendor
    product = Product(
        vendor_id=vendor.id,
        name="Test Product",
        price=29.99
    )
    db.add(product)
    db.commit()

    response = client.get(
        "/api/v1/vendor/products",
        headers=vendor_user_headers
    )

    assert response.status_code == 200
    data = response.json()
    assert len(data["products"]) == 1
```

### Test Negative Cases

```python
def test_rejects_non_vendor(client, auth_headers):
    """Test endpoint rejects non-vendor users"""
    response = client.get(
        "/api/v1/vendor/endpoint",
        headers=auth_headers  # Regular user token
    )

    assert response.status_code == 403
    data = response.json()
    assert data["detail"] == "This endpoint is only for vendor users"
```

### Test Authentication Failure

```python
def test_requires_authentication(client):
    """Test endpoint requires authentication"""
    response = client.get("/api/v1/vendor/endpoint")

    assert response.status_code == 401
    data = response.json()
    assert data["detail"] == "Not authenticated"
```

## Expanding Test Coverage

### Recommended Next Tests

#### 1. Vendor Products API

Create `tests/integration/api/v1/vendor/test_products.py`:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
class TestVendorProductsAPI:
    """Test vendor product management endpoints"""

    def test_list_products(self, client, vendor_user_headers):
        """Test listing products with pagination"""

    def test_create_product_from_marketplace(self, client, vendor_user_headers):
        """Test creating product from marketplace"""

    def test_update_product(self, client, vendor_user_headers):
        """Test updating product details"""

    def test_delete_product(self, client, vendor_user_headers):
        """Test deleting product"""

    def test_toggle_product_active(self, client, vendor_user_headers):
        """Test toggling product active status"""
```

#### 2. Vendor Orders API

Create `tests/integration/api/v1/vendor/test_orders.py`:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
class TestVendorOrdersAPI:
    """Test vendor order management endpoints"""

    def test_list_orders(self, client, vendor_user_headers):
        """Test listing orders with filters"""

    def test_get_order_details(self, client, vendor_user_headers):
        """Test getting order details"""

    def test_update_order_status(self, client, vendor_user_headers):
        """Test updating order status"""

    def test_order_data_isolation(self, client, vendor_user_headers):
        """Test vendor can only see their orders"""
```

#### 3. Vendor Profile API

Create `tests/integration/api/v1/vendor/test_profile.py`:

```python
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.vendor
class TestVendorProfileAPI:
    """Test vendor profile endpoints"""

    def test_get_profile(self, client, vendor_user_headers):
        """Test getting vendor profile"""

    def test_update_profile(self, client, vendor_user_headers):
        """Test updating vendor profile"""

    def test_profile_permissions(self, client, vendor_user_headers):
        """Test profile permission checks"""
```

## Troubleshooting

### Test Fails with 401 (Unauthorized)

**Problem**: Tests fail even with valid token

**Common Causes**:
- User doesn't have `role="vendor"`
- Token is expired
- Authorization header format incorrect

**Solutions**:
```python
# Verify user role
assert test_vendor_user.role == "vendor"

# Check token format
assert vendor_user_headers["Authorization"].startswith("Bearer ")

# Generate fresh token
response = client.post("/api/v1/auth/login", json={...})
```

### Test Fails with 403 (Forbidden)

**Problem**: "Not associated with vendor" error

**Common Causes**:
- `VendorUser` record doesn't exist
- Vendor is inactive
- User not linked to vendor

**Solutions**:
```python
# Use fixture that creates VendorUser
def test_example(
    client,
    vendor_user_headers,
    test_vendor_with_vendor_user  # ← Use this fixture
):
    pass

# Or manually create VendorUser
vendor_user = VendorUser(
    vendor_id=vendor.id,
    user_id=test_vendor_user.id,
    is_active=True
)
db.add(vendor_user)
db.commit()
```

### Test Fails with 404 (Not Found)

**Problem**: "Vendor not found" error

**Common Causes**:
- Vendor is inactive (`is_active=False`)
- Vendor doesn't exist
- Database not set up correctly

**Solutions**:
```python
# Verify vendor is active
assert vendor.is_active is True

# Verify vendor exists
vendor_in_db = db.query(Vendor).filter_by(id=vendor.id).first()
assert vendor_in_db is not None
```

### Import Errors for Fixtures

**Problem**: `fixture not found` errors

**Solutions**:
- Fixtures are auto-loaded via `conftest.py`
- Check `pytest_plugins` list in `tests/conftest.py`:
  ```python
  pytest_plugins = [
      "tests.fixtures.auth_fixtures",
      "tests.fixtures.vendor_fixtures",
  ]
  ```
- Use absolute imports in test files

## Best Practices

### 1. Use Appropriate Fixtures

```python
# ✅ Good - Use specific fixtures
def test_vendor_endpoint(client, vendor_user_headers, test_vendor_with_vendor_user):
    pass

# ❌ Avoid - Manual setup
def test_vendor_endpoint(client):
    user = User(role="vendor", ...)  # Don't do this
    db.add(user)
```

### 2. Test Isolation

Each test should be independent:

```python
# ✅ Good - Test creates its own data
def test_product_list(client, vendor_user_headers, db):
    product = Product(...)
    db.add(product)
    db.commit()

    response = client.get("/api/v1/vendor/products", headers=vendor_user_headers)
    # Test assertions

# ❌ Avoid - Depending on other tests
def test_product_list(client, vendor_user_headers):
    # Assumes data from previous test
    response = client.get("/api/v1/vendor/products", headers=vendor_user_headers)
```

### 3. Comprehensive Assertions

Check multiple aspects:

```python
def test_dashboard_stats(client, vendor_user_headers):
    response = client.get(
        "/api/v1/vendor/dashboard/stats",
        headers=vendor_user_headers
    )

    # Check status
    assert response.status_code == 200

    # Check structure
    data = response.json()
    assert "products" in data
    assert "orders" in data

    # Check data types
    assert isinstance(data["products"]["total"], int)

    # Check business logic
    assert data["products"]["total"] >= 0
```

### 4. Use Markers Consistently

```python
@pytest.mark.integration  # Always for integration tests
@pytest.mark.api          # For API endpoint tests
@pytest.mark.vendor       # For vendor-specific tests
class TestVendorAPI:
    pass
```

### 5. Clear Test Names

```python
# ✅ Good - Descriptive names
def test_list_products_returns_paginated_results(self):
def test_create_product_requires_authentication(self):
def test_update_product_rejects_invalid_price(self):

# ❌ Avoid - Vague names
def test_products(self):
def test_api_1(self):
def test_endpoint(self):
```

## See Also

- [Test Structure](test-structure.md) - Overall test organization
- [Testing Guide](testing-guide.md) - Testing strategy and best practices
- [Test Maintenance](test-maintenance.md) - Maintaining test quality
- [API Authentication](../api/authentication.md) - Authentication documentation
- [RBAC](../api/RBAC.md) - Role-based access control
