Files
orion/docs/testing/vendor-api-testing.md
Samir Boulahtit 3c7af0ccdf refactor: standardize markdown file naming to kebab-case convention
Renamed all documentation files to follow kebab-case naming standard:
- UPPERCASE files → lowercase (e.g., RBAC.md → rbac.md)
- snake_case files → kebab-case (e.g., icons_guide.md → icons-guide.md)
- SCREAMING_SNAKE_CASE → kebab-case (e.g., DATABASE_SETUP_GUIDE.md → database-setup-guide.md)

Files renamed (15 total):
API Documentation:
  - api/RBAC.md → api/rbac.md

Architecture:
  - architecture/API_CONSOLIDATION_PROPOSAL.md → api-consolidation-proposal.md
  - architecture/API_MIGRATION_STATUS.md → api-migration-status.md

Development:
  - development/AUTH_DEPENDENCIES_GUIDE.md → auth-dependencies-guide.md
  - development/CUSTOMER_AUTHENTICATION_IMPLEMENTATION.md → customer-authentication-implementation.md
  - development/CUSTOMER_AUTH_SUMMARY.md → customer-auth-summary.md
  - development/icons_guide.md → icons-guide.md

Database Seeder:
  - database-seeder/DATABASE_INIT_GUIDE.md → database-init-guide.md
  - database-seeder/DATABASE_QUICK_REFERENCE_GUIDE.md → database-quick-reference-guide.md
  - database-seeder/DATABASE_SEEDER_DOCUMENTATION.md → database-seeder-documentation.md
  - database-seeder/MAKEFILE_DATABASE_SEEDER.md → makefile-database-seeder.md

Error Rendering:
  - error-rendering/ERROR_RENDERING_DEVELOPER_DOCUMENTATION.md → error-rendering-developer-documentation.md
  - error-rendering/HTML_ERROR_RENDERING_FLOW_DIAGRAM.md → html-error-rendering-flow-diagram.md

Getting Started:
  - getting-started/DATABASE_QUICK_REFERENCE.md → database-quick-reference.md
  - getting-started/DATABASE_SETUP_GUIDE.md → database-setup-guide.md

Updates:
- Updated all references in mkdocs.yml
- Updated all cross-references in markdown files
- Verified mkdocs builds without warnings or errors

Standard: Use kebab-case (lowercase-with-hyphens) for all markdown files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 07:58:33 +01:00

17 KiB

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:

@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:

@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):

  • Requires Authorization header (not cookies)
  • Validates JWT token format
  • Checks token expiration
  • Verifies token signature
  • Extracts user from token
  • Verifies user exists and is active

Authorization:

  • Blocks admin users (403)
  • Blocks regular users (403)
  • Accepts vendor users (200/404)
  • Checks vendor-role requirement

CSRF Protection:

  • API endpoints require header auth
  • Cookie-only auth rejected
  • Consistent across all endpoints

Vendor Context:

  • Vendor association required
  • Inactive vendors rejected
  • Vendor data isolation
  • 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:

@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:

  • Correct response structure
  • All required fields present
  • Correct data types

Business Logic:

  • Vendor isolation (only shows own data)
  • Empty vendor returns zeros
  • Accurate product counts (total, active)
  • Orders, customers, revenue stats

Error Cases:

  • User not associated with vendor (403)
  • Inactive vendor (404)
  • Missing vendor association

Performance:

  • Response time < 2 seconds
  • Multiple requests consistency
  • Caching behavior

Test Fixtures

Authentication Fixtures

Located in tests/fixtures/auth_fixtures.py:

test_vendor_user

Creates a user with role="vendor":

@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:

@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:

@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

# 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

# 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

# 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

# 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

# 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

# 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

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

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

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

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

1. Vendor Products API

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

@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:

@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:

@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:

# 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:

# 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:

# 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:
    pytest_plugins = [
        "tests.fixtures.auth_fixtures",
        "tests.fixtures.vendor_fixtures",
    ]
    
  • Use absolute imports in test files

Best Practices

1. Use Appropriate Fixtures

# ✅ 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:

# ✅ 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:

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

@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

# ✅ 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