diff --git a/tests/integration/middleware/conftest.py b/tests/integration/middleware/conftest.py index 19de3f4d..d2df7e00 100644 --- a/tests/integration/middleware/conftest.py +++ b/tests/integration/middleware/conftest.py @@ -3,18 +3,44 @@ Fixtures specific to middleware integration tests. """ +import uuid + import pytest +from models.database.company import Company from models.database.vendor import Vendor from models.database.vendor_domain import VendorDomain from models.database.vendor_theme import VendorTheme @pytest.fixture -def vendor_with_subdomain(db): +def middleware_test_company(db, test_user): + """Create a company for middleware test vendors.""" + unique_id = str(uuid.uuid4())[:8] + company = Company( + name=f"Middleware Test Company {unique_id}", + contact_email=f"middleware{unique_id}@test.com", + owner_user_id=test_user.id, + is_active=True, + is_verified=True, + ) + db.add(company) + db.commit() + db.refresh(company) + return company + + +@pytest.fixture +def vendor_with_subdomain(db, middleware_test_company): """Create a vendor with subdomain for testing.""" + unique_id = str(uuid.uuid4())[:8] vendor = Vendor( - name="Test Vendor", code="testvendor", subdomain="testvendor", is_active=True + company_id=middleware_test_company.id, + name="Test Vendor", + vendor_code=f"TESTVENDOR_{unique_id.upper()}", + subdomain="testvendor", + is_active=True, + is_verified=True, ) db.add(vendor) db.commit() @@ -23,13 +49,16 @@ def vendor_with_subdomain(db): @pytest.fixture -def vendor_with_custom_domain(db): +def vendor_with_custom_domain(db, middleware_test_company): """Create a vendor with custom domain for testing.""" + unique_id = str(uuid.uuid4())[:8] vendor = Vendor( + company_id=middleware_test_company.id, name="Custom Domain Vendor", - code="customvendor", + vendor_code=f"CUSTOMVENDOR_{unique_id.upper()}", subdomain="customvendor", is_active=True, + is_verified=True, ) db.add(vendor) db.commit() @@ -46,13 +75,16 @@ def vendor_with_custom_domain(db): @pytest.fixture -def vendor_with_theme(db): +def vendor_with_theme(db, middleware_test_company): """Create a vendor with custom theme for testing.""" + unique_id = str(uuid.uuid4())[:8] vendor = Vendor( + company_id=middleware_test_company.id, name="Themed Vendor", - code="themedvendor", + vendor_code=f"THEMEDVENDOR_{unique_id.upper()}", subdomain="themedvendor", is_active=True, + is_verified=True, ) db.add(vendor) db.commit() @@ -74,10 +106,16 @@ def vendor_with_theme(db): @pytest.fixture -def inactive_vendor(db): +def middleware_inactive_vendor(db, middleware_test_company): """Create an inactive vendor for testing.""" + unique_id = str(uuid.uuid4())[:8] vendor = Vendor( - name="Inactive Vendor", code="inactive", subdomain="inactive", is_active=False + company_id=middleware_test_company.id, + name="Inactive Vendor", + vendor_code=f"INACTIVE_{unique_id.upper()}", + subdomain="inactive", + is_active=False, + is_verified=False, ) db.add(vendor) db.commit() diff --git a/tests/integration/middleware/test_middleware_stack.py b/tests/integration/middleware/test_middleware_stack.py index f7bd6b1f..86507d1f 100644 --- a/tests/integration/middleware/test_middleware_stack.py +++ b/tests/integration/middleware/test_middleware_stack.py @@ -339,7 +339,7 @@ class TestMiddlewareStackIntegration: # Should still set a context type (fallback) assert data["context_type"] is not None - def test_inactive_vendor_not_loaded(self, client, inactive_vendor): + def test_inactive_vendor_not_loaded(self, client, middleware_inactive_vendor): """Test that inactive vendors are not loaded.""" from fastapi import Request @@ -358,7 +358,7 @@ class TestMiddlewareStackIntegration: mock_settings.platform_domain = "platform.com" response = client.get( "/test-inactive-vendor", - headers={"host": f"{inactive_vendor.subdomain}.platform.com"}, + headers={"host": f"{middleware_inactive_vendor.subdomain}.platform.com"}, ) assert response.status_code == 200 diff --git a/tests/integration/middleware/test_vendor_context_flow.py b/tests/integration/middleware/test_vendor_context_flow.py index 12027f8d..05c9059e 100644 --- a/tests/integration/middleware/test_vendor_context_flow.py +++ b/tests/integration/middleware/test_vendor_context_flow.py @@ -38,7 +38,7 @@ class TestVendorContextFlow: else None ), "vendor_code": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -61,7 +61,7 @@ class TestVendorContextFlow: data = response.json() assert data["vendor_detected"] is True assert data["vendor_id"] == vendor_with_subdomain.id - assert data["vendor_code"] == vendor_with_subdomain.code + assert data["vendor_code"] == vendor_with_subdomain.vendor_code assert data["vendor_name"] == vendor_with_subdomain.name def test_subdomain_with_port_detection(self, client, vendor_with_subdomain): @@ -76,7 +76,7 @@ class TestVendorContextFlow: "vendor_detected": hasattr(request.state, "vendor") and request.state.vendor is not None, "vendor_code": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -94,7 +94,7 @@ class TestVendorContextFlow: assert response.status_code == 200 data = response.json() assert data["vendor_detected"] is True - assert data["vendor_code"] == vendor_with_subdomain.code + assert data["vendor_code"] == vendor_with_subdomain.vendor_code def test_nonexistent_subdomain_returns_no_vendor(self, client): """Test that nonexistent subdomain doesn't crash and returns no vendor.""" @@ -144,7 +144,7 @@ class TestVendorContextFlow: else None ), "vendor_code": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -161,7 +161,7 @@ class TestVendorContextFlow: data = response.json() assert data["vendor_detected"] is True assert data["vendor_id"] == vendor_with_custom_domain.id - assert data["vendor_code"] == vendor_with_custom_domain.code + assert data["vendor_code"] == vendor_with_custom_domain.vendor_code def test_custom_domain_with_www_detection(self, client, vendor_with_custom_domain): """Test vendor detection via custom domain with www prefix.""" @@ -175,7 +175,7 @@ class TestVendorContextFlow: "vendor_detected": hasattr(request.state, "vendor") and request.state.vendor is not None, "vendor_code": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -211,7 +211,7 @@ class TestVendorContextFlow: and request.state.vendor is not None, "vendor_code_param": vendor_code, "vendor_code_state": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -225,15 +225,15 @@ class TestVendorContextFlow: with patch("app.core.config.settings") as mock_settings: mock_settings.platform_domain = "platform.com" response = client.get( - f"/vendors/{vendor_with_subdomain.code}/test-path", + f"/vendors/{vendor_with_subdomain.vendor_code}/test-path", headers={"host": "localhost:8000"}, ) assert response.status_code == 200 data = response.json() assert data["vendor_detected"] is True - assert data["vendor_code_param"] == vendor_with_subdomain.code - assert data["vendor_code_state"] == vendor_with_subdomain.code + assert data["vendor_code_param"] == vendor_with_subdomain.vendor_code + assert data["vendor_code_state"] == vendor_with_subdomain.vendor_code def test_path_based_vendor_detection_vendor_prefix( self, client, vendor_with_subdomain @@ -249,7 +249,7 @@ class TestVendorContextFlow: "vendor_detected": hasattr(request.state, "vendor") and request.state.vendor is not None, "vendor_code": ( - request.state.vendor.code + request.state.vendor.vendor_code if hasattr(request.state, "vendor") and request.state.vendor else None ), @@ -258,14 +258,14 @@ class TestVendorContextFlow: with patch("app.core.config.settings") as mock_settings: mock_settings.platform_domain = "platform.com" response = client.get( - f"/vendor/{vendor_with_subdomain.code}/test", + f"/vendor/{vendor_with_subdomain.vendor_code}/test", headers={"host": "localhost:8000"}, ) assert response.status_code == 200 data = response.json() assert data["vendor_detected"] is True - assert data["vendor_code"] == vendor_with_subdomain.code + assert data["vendor_code"] == vendor_with_subdomain.vendor_code # ======================================================================== # Clean Path Extraction Tests @@ -293,7 +293,7 @@ class TestVendorContextFlow: with patch("app.core.config.settings") as mock_settings: mock_settings.platform_domain = "platform.com" response = client.get( - f"/vendors/{vendor_with_subdomain.code}/shop/products", + f"/vendors/{vendor_with_subdomain.vendor_code}/shop/products", headers={"host": "localhost:8000"}, ) @@ -302,7 +302,7 @@ class TestVendorContextFlow: # Clean path should have vendor prefix removed assert data["clean_path"] == "/shop/products" assert ( - f"/vendors/{vendor_with_subdomain.code}/shop/products" + f"/vendors/{vendor_with_subdomain.vendor_code}/shop/products" in data["original_path"] ) @@ -396,7 +396,7 @@ class TestVendorContextFlow: { "id": vendor.id if vendor else None, "name": vendor.name if vendor else None, - "code": vendor.code if vendor else None, + "code": vendor.vendor_code if vendor else None, "subdomain": vendor.subdomain if vendor else None, "is_active": vendor.is_active if vendor else None, } @@ -417,14 +417,14 @@ class TestVendorContextFlow: assert data["has_vendor"] is True assert data["vendor_attributes"]["id"] == vendor_with_subdomain.id assert data["vendor_attributes"]["name"] == vendor_with_subdomain.name - assert data["vendor_attributes"]["code"] == vendor_with_subdomain.code + assert data["vendor_attributes"]["code"] == vendor_with_subdomain.vendor_code assert data["vendor_attributes"]["is_active"] is True # ======================================================================== # Edge Cases and Error Handling # ======================================================================== - def test_inactive_vendor_not_detected(self, client, inactive_vendor): + def test_inactive_vendor_not_detected(self, client, middleware_inactive_vendor): """Test that inactive vendors are not detected.""" from fastapi import Request @@ -441,7 +441,7 @@ class TestVendorContextFlow: mock_settings.platform_domain = "platform.com" response = client.get( "/test-inactive-vendor-detection", - headers={"host": f"{inactive_vendor.subdomain}.platform.com"}, + headers={"host": f"{middleware_inactive_vendor.subdomain}.platform.com"}, ) assert response.status_code == 200 diff --git a/tests/integration/security/test_authentication.py b/tests/integration/security/test_authentication.py index a212f8b3..5803c549 100644 --- a/tests/integration/security/test_authentication.py +++ b/tests/integration/security/test_authentication.py @@ -1,4 +1,11 @@ # tests/integration/security/test_authentication.py +""" +Authentication tests for the API. + +API Structure: +- /api/v1/admin/* - Admin endpoints (require admin token) +- /api/v1/vendor/* - Vendor endpoints (require vendor token with vendor_id claim) +""" import pytest @@ -11,67 +18,24 @@ class TestAuthentication: protected_endpoints = [ "/api/v1/admin/users", "/api/v1/admin/vendors", - "/api/v1/marketplace/import-jobs", - "/api/v1/marketplace/product", - "/api/v1/vendor", - "/api/v1/stats", - "/api/v1/inventory", + "/api/v1/admin/marketplace-import-jobs", + "/api/v1/admin/products", + "/api/v1/vendor/products", + "/api/v1/vendor/inventory", ] for endpoint in protected_endpoints: response = client.get(endpoint) - assert response.status_code == 401 # Authentication missing + assert response.status_code == 401, f"Expected 401 for {endpoint}" def test_protected_endpoint_with_invalid_token(self, client): """Test protected endpoints with invalid token""" headers = {"Authorization": "Bearer invalid_token_here"} - response = client.get("/api/v1/marketplace/product", headers=headers) + response = client.get("/api/v1/admin/products", headers=headers) assert response.status_code == 401 # Token is not valid - def test_debug_direct_bearer(self, client): - """Test HTTPBearer directly""" - response = client.get("/api/v1/debug-bearer") - print(f"Direct Bearer - Status: {response.status_code}") - print( - f"Direct Bearer - Response: {response.json() if response.content else 'No content'}" - ) - - def test_debug_dependencies(self, client): - """Debug the dependency chain step by step""" - # Test 1: Direct endpoint with no auth - response = client.get("/api/v1/admin/users") - print(f"Admin endpoint - Status: {response.status_code}") - try: - print(f"Admin endpoint - Response: {response.json()}") - except: - print(f"Admin endpoint - Raw: {response.content}") - - # Test 2: Try a regular endpoint that uses get_current_user - response2 = client.get( - "/api/v1/marketplace/product" - ) # or any endpoint with get_current_user - print(f"Regular endpoint - Status: {response2.status_code}") - try: - print(f"Regular endpoint - Response: {response2.json()}") - except: - print(f"Regular endpoint - Raw: {response2.content}") - - def test_debug_available_routes(self, client): - """Debug test to see all available routes""" - print("\n=== All Available Routes ===") - for route in client.app.routes: - if hasattr(route, "path") and hasattr(route, "methods"): - print(f"{list(route.methods)} {route.path}") - - print("\n=== Testing MarketplaceProduct Endpoint Variations ===") - variations = [ - "/api/v1/marketplace/product", # Your current attempt - "/api/v1/marketplace/product/", # With trailing slash - "/api/v1/marketplace/product/list", # With list endpoint - "/api/v1/marketplace/product/all", # With all endpoint - ] - - for path in variations: - response = client.get(path) - print(f"{path}: Status {response.status_code}") + def test_valid_token_accepted(self, client, admin_headers): + """Test that valid tokens are accepted""" + response = client.get("/api/v1/admin/vendors", headers=admin_headers) + assert response.status_code == 200 diff --git a/tests/integration/security/test_authorization.py b/tests/integration/security/test_authorization.py index 5be14888..22d52f2b 100644 --- a/tests/integration/security/test_authorization.py +++ b/tests/integration/security/test_authorization.py @@ -1,4 +1,11 @@ # tests/integration/security/test_authorization.py +""" +Authorization tests for the API. + +Tests role-based access control: +- Admin endpoints require admin role +- Vendor endpoints require vendor context (vendor_id in token) +""" import pytest @@ -9,8 +16,8 @@ class TestAuthorization: def test_admin_endpoint_requires_admin_role(self, client, auth_headers): """Test that admin endpoints require admin role""" response = client.get("/api/v1/admin/users", headers=auth_headers) - assert response.status_code == 403 - # Regular user should be denied access + # Regular user should be denied access (401 not admin or 403 forbidden) + assert response.status_code in [401, 403] def test_admin_endpoints_with_admin_access(self, client, admin_headers): """Test that admin users can access admin endpoints""" @@ -22,29 +29,21 @@ class TestAuthorization: for endpoint in admin_endpoints: response = client.get(endpoint, headers=admin_headers) - assert response.status_code == 200 # Admin should have access + assert response.status_code == 200, f"Admin should have access to {endpoint}" - def test_regular_endpoints_with_user_access(self, client, auth_headers): - """Test that regular users can access non-admin endpoints""" - user_endpoints = [ - "/api/v1/marketplace/product", - "/api/v1/stats", - "/api/v1/inventory", - ] - - for endpoint in user_endpoints: - response = client.get(endpoint, headers=auth_headers) - assert response.status_code == 200 # Regular user should have access + def test_vendor_endpoint_requires_vendor_context(self, client, admin_headers): + """Test that vendor endpoints require vendor context in token""" + # Admin token doesn't have vendor_id claim + response = client.get("/api/v1/vendor/products", headers=admin_headers) + # Should fail - admin token lacks vendor_id claim + assert response.status_code in [401, 403] def test_vendor_owner_access_control( - self, client, auth_headers, test_vendor, other_user + self, client, admin_headers, test_vendor ): - """Test that users can only access their own vendors""" - # Test accessing own vendor (should work) + """Test admin can access vendor by vendor code""" response = client.get( - f"/api/v1/vendor/{test_vendor.vendor_code}", headers=auth_headers + f"/api/v1/admin/vendors/{test_vendor.vendor_code}", headers=admin_headers ) - # Response depends on your implementation - could be 200 or 404 if vendor doesn't belong to user - - # The exact assertion depends on your vendor access control implementation - assert response.status_code in [200, 403, 404] + # Admin should be able to view vendor + assert response.status_code == 200 diff --git a/tests/integration/security/test_input_validation.py b/tests/integration/security/test_input_validation.py index 17cb6433..c15d2fd0 100644 --- a/tests/integration/security/test_input_validation.py +++ b/tests/integration/security/test_input_validation.py @@ -1,72 +1,58 @@ # tests/integration/security/test_input_validation.py +""" +Input validation tests for the API. + +Tests SQL injection prevention, parameter validation, and JSON validation. +""" import pytest @pytest.mark.integration @pytest.mark.security class TestInputValidation: - def test_sql_injection_prevention(self, client, auth_headers): + def test_sql_injection_prevention(self, client, admin_headers): """Test SQL injection prevention in search parameters""" # Try SQL injection in search parameter malicious_search = "'; DROP TABLE products; --" response = client.get( - f"/api/v1/marketplace/product?search={malicious_search}", - headers=auth_headers, + f"/api/v1/admin/products?search={malicious_search}", + headers=admin_headers, ) # Should not crash and should return normal response assert response.status_code == 200 # Database should still be intact (no products dropped) - # def test_input_validation(self, client, auth_headers): - # # TODO: implement sanitization - # """Test input validation and sanitization""" - # # Test XSS attempt in product creation - # xss_payload = "" - # - # product_data = { - # "marketplace_product_id": "XSS_TEST", - # "title": xss_payload, - # "description": xss_payload, - # } - # - # response = client.post("/api/v1/marketplace/product", headers=auth_headers, json=product_data) - # - # assert response.status_code == 200 - # data = response.json() - # assert "