# Store API Testing Guide ## Overview Comprehensive integration test suite for store API endpoints that use `get_current_store_api` for authentication. ## Test Coverage ### Current Coverage | Endpoint | Auth Tests | Functionality Tests | Coverage | |----------|-----------|-------------------|----------| | `/api/v1/store/auth/me` | ✅ Complete | ✅ Complete | 100% | | `/api/v1/store/dashboard/stats` | ✅ Complete | ✅ Complete | 100% | | `/api/v1/store/profile` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/settings` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/products` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/orders` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/customers` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/inventory` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/marketplace` | ✅ Auth only | ⚠️ Pending | 50% | | `/api/v1/store/analytics` | ✅ Auth only | ⚠️ Pending | 50% | **Total Tests Created**: 58+ tests **Tests in Structure**: 28 tests (authentication + dashboard) ## Test Files ### Location ``` tests/integration/api/v1/store/ ├── __init__.py ├── test_authentication.py # 30+ authentication tests └── test_dashboard.py # 12 dashboard tests ``` ### 1. Authentication Tests **File**: `tests/integration/api/v1/store/test_authentication.py` **Purpose**: Verify authentication and authorization for store API endpoints #### Test Classes ##### `TestStoreAPIAuthentication` Core authentication tests for `get_current_store_api`: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store @pytest.mark.auth class TestStoreAPIAuthentication: """Test authentication for store API endpoints""" ``` **Tests**: - ✅ `test_store_auth_me_success` - Valid store token accepted - ✅ `test_store_auth_me_no_token` - Missing token rejected (401) - ✅ `test_store_auth_me_invalid_token` - Invalid token rejected (401) - ✅ `test_store_auth_me_expired_token` - Expired token rejected (401) - ✅ `test_store_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_store_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 ##### `TestStoreAPIConsistency` Consistency checks across all store endpoints: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store class TestStoreAPIConsistency: """Test that all store API endpoints have consistent auth behavior""" ``` **Tests**: - ✅ Verifies all store endpoints require authentication - ✅ Ensures consistent error responses - ✅ Checks CSRF protection across all endpoints #### What's Tested **Authentication (`get_current_store_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 store users (200/404) - [x] Checks store-role requirement **CSRF Protection**: - [x] API endpoints require header auth - [x] Cookie-only auth rejected - [x] Consistent across all endpoints **Store Context**: - [x] Store association required - [x] Inactive stores rejected - [x] Store data isolation - [x] StoreUser relationship working ### 2. Dashboard Tests **File**: `tests/integration/api/v1/store/test_dashboard.py` **Purpose**: Test store dashboard statistics endpoint #### Test Class ##### `TestStoreDashboardAPI` Dashboard functionality tests: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store class TestStoreDashboardAPI: """Test store dashboard stats endpoint""" ``` **Tests**: - ✅ `test_dashboard_stats_structure` - Correct response structure - ✅ `test_dashboard_stats_store_isolation` - Only shows store's data - ✅ `test_dashboard_stats_empty_store` - Empty store 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_store_association` - User not associated (403) - ✅ `test_dashboard_stats_inactive_store` - Inactive store (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] Store isolation (only shows own data) - [x] Empty store returns zeros - [x] Accurate product counts (total, active) - [x] Orders, customers, revenue stats **Error Cases**: - [x] User not associated with store (403) - [x] Inactive store (404) - [x] Missing store 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_store_user` Creates a user with `role="store"`: ```python @pytest.fixture def test_store_user(db, auth_manager): """Create a test store user with unique username""" unique_id = str(uuid.uuid4())[:8] hashed_password = auth_manager.hash_password("storepass123") user = User( email=f"store_{unique_id}@example.com", username=f"storeuser_{unique_id}", hashed_password=hashed_password, role="store", is_active=True, ) db.add(user) db.commit() db.refresh(user) return user ``` **Usage**: Base fixture for store-role users #### `store_user_headers` Returns Authorization headers for store user: ```python @pytest.fixture def store_user_headers(client, test_store_user): """Get authentication headers for store user""" response = client.post( "/api/v1/auth/login", json={ "username": test_store_user.username, "password": "storepass123" }, ) token = response.json()["access_token"] return {"Authorization": f"Bearer {token}"} ``` **Usage**: Use for authenticated store API requests **Format**: `{"Authorization": "Bearer "}` ### Store Fixtures Located in `tests/fixtures/store_fixtures.py`: #### `test_store_with_store_user` Creates a store owned by store user: ```python @pytest.fixture def test_store_with_store_user(db, test_store_user): """Create a store owned by a store user""" store = Store( store_code=f"STOREAPI_{unique_id}", subdomain=f"storeapi{unique_id.lower()}", name=f"Store API Test {unique_id}", owner_user_id=test_store_user.id, is_active=True, is_verified=True, ) db.add(store) db.commit() # Create StoreUser association store_user = StoreUser( store_id=store.id, user_id=test_store_user.id, is_owner=True, is_active=True, ) db.add(store_user) db.commit() return store ``` **Usage**: Fully configured store for API testing with StoreUser association ## Running Tests ### All Store Tests ```bash # Run all store integration tests pytest tests/integration/api/v1/store/ -v # With output pytest tests/integration/api/v1/store/ -v -s ``` ### Specific Test Files ```bash # Authentication tests only pytest tests/integration/api/v1/store/test_authentication.py -v # Dashboard tests only pytest tests/integration/api/v1/store/test_dashboard.py -v ``` ### Specific Test Classes ```bash # Authentication tests class pytest tests/integration/api/v1/store/test_authentication.py::TestStoreAPIAuthentication -v # Dashboard tests class pytest tests/integration/api/v1/store/test_dashboard.py::TestStoreDashboardAPI -v ``` ### Specific Tests ```bash # Single authentication test pytest tests/integration/api/v1/store/test_authentication.py::TestStoreAPIAuthentication::test_store_auth_me_success -v # Single dashboard test pytest tests/integration/api/v1/store/test_dashboard.py::TestStoreDashboardAPI::test_dashboard_stats_structure -v ``` ### With Coverage ```bash # Coverage for all store tests pytest tests/integration/api/v1/store/ \ --cov=app/api/v1/store \ --cov-report=html # View coverage report open htmlcov/index.html # Coverage for specific endpoint pytest tests/integration/api/v1/store/test_dashboard.py \ --cov=app/api/v1/store/dashboard \ --cov-report=term-missing ``` ### Using Markers ```bash # All store tests pytest -m store -v # Store API tests only pytest -m "store and api" -v # Store authentication tests pytest -m "store and auth" -v ``` ## Test Examples ### Basic Authenticated Request ```python def test_store_endpoint(client, store_user_headers): """Test store endpoint with authentication""" response = client.get( "/api/v1/store/some-endpoint", headers=store_user_headers ) assert response.status_code == 200 data = response.json() assert "expected_field" in data ``` ### Test with Store Context ```python def test_with_store_data( client, store_user_headers, test_store_with_store_user, db ): """Test with specific store data""" store = test_store_with_store_user # Create test data for this store product = Product( store_id=store.id, name="Test Product", price=29.99 ) db.add(product) db.commit() response = client.get( "/api/v1/store/products", headers=store_user_headers ) assert response.status_code == 200 data = response.json() assert len(data["products"]) == 1 ``` ### Test Negative Cases ```python def test_rejects_non_store(client, auth_headers): """Test endpoint rejects non-store users""" response = client.get( "/api/v1/store/endpoint", headers=auth_headers # Regular user token ) assert response.status_code == 403 data = response.json() assert data["detail"] == "This endpoint is only for store users" ``` ### Test Authentication Failure ```python def test_requires_authentication(client): """Test endpoint requires authentication""" response = client.get("/api/v1/store/endpoint") assert response.status_code == 401 data = response.json() assert data["detail"] == "Not authenticated" ``` ## Expanding Test Coverage ### Recommended Next Tests #### 1. Store Products API Create `tests/integration/api/v1/store/test_products.py`: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store class TestStoreProductsAPI: """Test store product management endpoints""" def test_list_products(self, client, store_user_headers): """Test listing products with pagination""" def test_create_product_from_marketplace(self, client, store_user_headers): """Test creating product from marketplace""" def test_update_product(self, client, store_user_headers): """Test updating product details""" def test_delete_product(self, client, store_user_headers): """Test deleting product""" def test_toggle_product_active(self, client, store_user_headers): """Test toggling product active status""" ``` #### 2. Store Orders API Create `tests/integration/api/v1/store/test_orders.py`: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store class TestStoreOrdersAPI: """Test store order management endpoints""" def test_list_orders(self, client, store_user_headers): """Test listing orders with filters""" def test_get_order_details(self, client, store_user_headers): """Test getting order details""" def test_update_order_status(self, client, store_user_headers): """Test updating order status""" def test_order_data_isolation(self, client, store_user_headers): """Test store can only see their orders""" ``` #### 3. Store Profile API Create `tests/integration/api/v1/store/test_profile.py`: ```python @pytest.mark.integration @pytest.mark.api @pytest.mark.store class TestStoreProfileAPI: """Test store profile endpoints""" def test_get_profile(self, client, store_user_headers): """Test getting store profile""" def test_update_profile(self, client, store_user_headers): """Test updating store profile""" def test_profile_permissions(self, client, store_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="store"` - Token is expired - Authorization header format incorrect **Solutions**: ```python # Verify user role assert test_store_user.role == "store" # Check token format assert store_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 store" error **Common Causes**: - `StoreUser` record doesn't exist - Store is inactive - User not linked to store **Solutions**: ```python # Use fixture that creates StoreUser def test_example( client, store_user_headers, test_store_with_store_user # ← Use this fixture ): pass # Or manually create StoreUser store_user = StoreUser( store_id=store.id, user_id=test_store_user.id, is_active=True ) db.add(store_user) db.commit() ``` ### Test Fails with 404 (Not Found) **Problem**: "Store not found" error **Common Causes**: - Store is inactive (`is_active=False`) - Store doesn't exist - Database not set up correctly **Solutions**: ```python # Verify store is active assert store.is_active is True # Verify store exists store_in_db = db.query(Store).filter_by(id=store.id).first() assert store_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.store_fixtures", ] ``` - Use absolute imports in test files ## Best Practices ### 1. Use Appropriate Fixtures ```python # ✅ Good - Use specific fixtures def test_store_endpoint(client, store_user_headers, test_store_with_store_user): pass # ❌ Avoid - Manual setup def test_store_endpoint(client): user = User(role="store", ...) # 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, store_user_headers, db): product = Product(...) db.add(product) db.commit() response = client.get("/api/v1/store/products", headers=store_user_headers) # Test assertions # ❌ Avoid - Depending on other tests def test_product_list(client, store_user_headers): # Assumes data from previous test response = client.get("/api/v1/store/products", headers=store_user_headers) ``` ### 3. Comprehensive Assertions Check multiple aspects: ```python def test_dashboard_stats(client, store_user_headers): response = client.get( "/api/v1/store/dashboard/stats", headers=store_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.store # For store-specific tests class TestStoreAPI: 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