From 917344864549cc444936f8b2f12d7dd26173acfb Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Fri, 13 Feb 2026 13:16:43 +0100 Subject: [PATCH] refactor: remove legacy /shop and /api/v1/shop dead code After the storefront migration, no live routes mount under /api/v1/shop/. Remove all dead code that detected/handled shop API requests: the is_shop_api_request() method, the shop API dispatch branch in middleware, the RequestContext.SHOP enum member (renamed to STOREFRONT), legacy path prefixes in FrontendDetector, and all associated tests. Co-Authored-By: Claude Opus 4.6 --- .architecture-rules/naming.yaml | 10 +- app/api/deps.py | 20 +- app/core/frontend_detector.py | 4 +- middleware/context.py | 8 +- middleware/store_context.py | 90 +------ tests/integration/middleware/conftest.py | 4 +- .../middleware/middleware_test_routes.py | 24 +- tests/unit/core/test_frontend_detector.py | 10 - tests/unit/middleware/test_context.py | 6 +- tests/unit/middleware/test_store_context.py | 233 ++---------------- 10 files changed, 76 insertions(+), 333 deletions(-) diff --git a/.architecture-rules/naming.yaml b/.architecture-rules/naming.yaml index d30cea6e..5f8cd4cb 100644 --- a/.architecture-rules/naming.yaml +++ b/.architecture-rules/naming.yaml @@ -22,7 +22,9 @@ naming_rules: description: | Service files should use singular name + _service (vendor_service.py) pattern: - file_pattern: "app/services/**/*.py" + file_pattern: + - "app/services/**/*.py" + - "app/modules/*/services/**/*.py" check: "service_naming" - id: "NAM-003" @@ -31,14 +33,16 @@ naming_rules: description: | Both database and schema model files use singular names (product.py) pattern: - file_pattern: "models/**/*.py" + file_pattern: + - "models/**/*.py" + - "app/modules/*/models/**/*.py" check: "singular_naming" - id: "NAM-004" name: "Use consistent terminology: vendor not shop" severity: "warning" description: | - Use 'vendor' consistently, not 'shop' (except for shop frontend) + Use 'vendor' consistently, not 'shop' (except for storefront) pattern: file_pattern: "app/**/*.py" discouraged_terms: diff --git a/app/api/deps.py b/app/api/deps.py index 8be273e4..271db1ba 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -20,19 +20,19 @@ MERCHANT ROUTES (/merchants/*): - Role: store (merchant owners are store-role users who own merchants) - Validates: User owns the merchant via Merchant.owner_user_id -CUSTOMER/SHOP ROUTES (/shop/account/*): -- Cookie: customer_token (path=/shop) OR Authorization header +CUSTOMER/STOREFRONT ROUTES (/storefront/account/*): +- Cookie: customer_token (path=/storefront) OR Authorization header - Role: customer only - Blocks: admins, stores -- Note: Public shop pages (/shop/products, etc.) don't require auth +- Note: Public storefront pages (/storefront/products, etc.) don't require auth This dual authentication approach supports: - HTML pages: Use cookies (automatic browser behavior) - API calls: Use Authorization headers (explicit JavaScript control) The cookie path restrictions prevent cross-context cookie leakage: -- admin_token is NEVER sent to /store/* or /shop/* -- store_token is NEVER sent to /admin/* or /shop/* +- admin_token is NEVER sent to /store/* or /storefront/* +- store_token is NEVER sent to /admin/* or /storefront/* - customer_token is NEVER sent to /admin/* or /store/* """ @@ -1019,7 +1019,7 @@ def get_merchant_for_current_user_page( # ============================================================================ -# CUSTOMER AUTHENTICATION (SHOP) +# CUSTOMER AUTHENTICATION (STOREFRONT) # ============================================================================ @@ -1095,7 +1095,7 @@ def _validate_customer_token(token: str, request: Request, db: Session): raise InvalidTokenException("Customer account is inactive") # Validate store context matches token - # This prevents using a customer token from store A on store B's shop + # This prevents using a customer token from store A on store B's storefront request_store = getattr(request.state, "store", None) if request_store and token_store_id: if request_store.id != token_store_id: @@ -1123,8 +1123,8 @@ def get_current_customer_from_cookie_or_header( """ Get current customer from customer_token cookie or Authorization header. - Used for shop account HTML pages (/shop/account/*) that need cookie-based auth. - Note: Public shop pages (/shop/products, etc.) don't use this dependency. + Used for storefront account HTML pages (/storefront/account/*) that need cookie-based auth. + Note: Public storefront pages (/storefront/products, etc.) don't use this dependency. Validates that token store_id matches request store (URL-based detection). @@ -1164,7 +1164,7 @@ def get_current_customer_api( """ Get current customer from Authorization header ONLY. - Used for shop API endpoints that should not accept cookies. + Used for storefront API endpoints that should not accept cookies. Validates that token store_id matches request store (URL-based detection). Args: diff --git a/app/core/frontend_detector.py b/app/core/frontend_detector.py index 00d12efb..2b41c6eb 100644 --- a/app/core/frontend_detector.py +++ b/app/core/frontend_detector.py @@ -46,8 +46,6 @@ class FrontendDetector: STOREFRONT_PATH_PREFIXES = ( "/storefront", "/api/v1/storefront", - "/shop", # Legacy support - "/api/v1/shop", # Legacy support "/stores/", # Path-based store access ) MERCHANT_PATH_PREFIXES = ("/merchants", "/api/v1/merchants") @@ -113,7 +111,7 @@ class FrontendDetector: return FrontendType.PLATFORM # 3. Store subdomain detection (wizamart.oms.lu) - # If subdomain exists and is not reserved -> it's a store shop + # If subdomain exists and is not reserved -> it's a store storefront if subdomain and subdomain not in cls.RESERVED_SUBDOMAINS: logger.debug( f"[FRONTEND_DETECTOR] Detected STOREFRONT from subdomain: {subdomain}" diff --git a/middleware/context.py b/middleware/context.py index 9aa797d8..f358e3ad 100644 --- a/middleware/context.py +++ b/middleware/context.py @@ -14,7 +14,7 @@ Migration guide: - RequestContext.API -> Check with FrontendDetector.is_api_request() - RequestContext.ADMIN -> FrontendType.ADMIN - RequestContext.STORE_DASHBOARD -> FrontendType.STORE -- RequestContext.SHOP -> FrontendType.STOREFRONT +- RequestContext.STOREFRONT -> FrontendType.STOREFRONT - RequestContext.FALLBACK -> FrontendType.PLATFORM (or handle API separately) - get_request_context(request) -> get_frontend_type(request) @@ -44,14 +44,14 @@ class RequestContext(str, Enum): - API -> Use FrontendDetector.is_api_request() + FrontendType - ADMIN -> FrontendType.ADMIN - STORE_DASHBOARD -> FrontendType.STORE - - SHOP -> FrontendType.STOREFRONT + - STOREFRONT -> FrontendType.STOREFRONT - FALLBACK -> FrontendType.PLATFORM """ API = "api" ADMIN = "admin" STORE_DASHBOARD = "store" - SHOP = "shop" + STOREFRONT = "storefront" FALLBACK = "fallback" @@ -82,7 +82,7 @@ def get_request_context(request: Request) -> RequestContext: mapping = { FrontendType.ADMIN: RequestContext.ADMIN, FrontendType.STORE: RequestContext.STORE_DASHBOARD, - FrontendType.STOREFRONT: RequestContext.SHOP, + FrontendType.STOREFRONT: RequestContext.STOREFRONT, FrontendType.PLATFORM: RequestContext.FALLBACK, } diff --git a/middleware/store_context.py b/middleware/store_context.py index 715f0a3d..e94f6897 100644 --- a/middleware/store_context.py +++ b/middleware/store_context.py @@ -239,31 +239,26 @@ class StoreContextManager: """Check if request is for API endpoints.""" return FrontendDetector.is_api_request(request.url.path) - @staticmethod - def is_shop_api_request(request: Request) -> bool: - """Check if request is for shop API endpoints.""" - return request.url.path.startswith("/api/v1/shop/") - @staticmethod def extract_store_from_referer(request: Request) -> dict | None: """ Extract store context from Referer header. - Used for shop API requests where store context comes from the page - that made the API call (e.g., JavaScript on /stores/wizamart/shop/products - calling /api/v1/shop/products). + Used for storefront API requests where store context comes from the page + that made the API call (e.g., JavaScript on /stores/wizamart/storefront/products + calling /api/v1/storefront/products). Extracts store from Referer URL patterns: - - http://localhost:8000/stores/wizamart/shop/... → wizamart - - http://wizamart.platform.com/shop/... → wizamart (subdomain) # noqa - - http://custom-domain.com/shop/... → custom-domain.com # noqa + - http://localhost:8000/stores/wizamart/storefront/... → wizamart + - http://wizamart.platform.com/storefront/... → wizamart (subdomain) # noqa + - http://custom-domain.com/storefront/... → custom-domain.com # noqa Returns store context dict or None if unable to extract. """ referer = request.headers.get("referer") or request.headers.get("origin") if not referer: - logger.debug("[STORE] No Referer/Origin header for shop API request") + logger.debug("[STORE] No Referer/Origin header for storefront API request") return None try: @@ -287,7 +282,7 @@ class StoreContextManager: ) # Method 1: Path-based detection from referer path - # /stores/wizamart/shop/products → wizamart + # /stores/wizamart/storefront/products → wizamart if referer_path.startswith(("/stores/", "/store/")): prefix = ( "/stores/" if referer_path.startswith("/stores/") else "/store/" @@ -448,75 +443,10 @@ class StoreContextMiddleware(BaseHTTPMiddleware): request.state.clean_path = request.url.path return await call_next(request) - # Handle shop API routes specially - extract store from Referer header - if StoreContextManager.is_shop_api_request(request): - logger.debug( - f"[STORE] Shop API request detected: {request.url.path}", - extra={ - "path": request.url.path, - "referer": request.headers.get("referer", ""), - }, - ) - - store_context = StoreContextManager.extract_store_from_referer(request) - - if store_context: - db_gen = get_db() - db = next(db_gen) - try: - store = StoreContextManager.get_store_from_context( - db, store_context - ) - - if store: - request.state.store = store - request.state.store_context = store_context - request.state.clean_path = request.url.path - - logger.debug( - "[STORE_CONTEXT] Store detected from Referer for shop API", - extra={ - "store_id": store.id, - "store_name": store.name, - "store_subdomain": store.subdomain, - "detection_method": store_context.get( - "detection_method" - ), - "api_path": request.url.path, - "referer": store_context.get("referer", ""), - }, - ) - else: - logger.warning( - "[WARNING] Store context from Referer but store not found", - extra={ - "context": store_context, - "detection_method": store_context.get( - "detection_method" - ), - "api_path": request.url.path, - }, - ) - request.state.store = None - request.state.store_context = store_context - request.state.clean_path = request.url.path - finally: - db.close() - else: - logger.warning( - "[STORE] Shop API request without Referer header", - extra={"path": request.url.path}, - ) - request.state.store = None - request.state.store_context = None - request.state.clean_path = request.url.path - - return await call_next(request) - - # Skip store detection for other API routes (admin API, store API have store_id in URL) + # Skip store detection for API routes (admin API, store API have store_id in URL) if StoreContextManager.is_api_request(request): logger.debug( - f"[STORE] Skipping store detection for non-shop API: {request.url.path}", + f"[STORE] Skipping store detection for non-storefront API: {request.url.path}", extra={"path": request.url.path, "reason": "api"}, ) request.state.store = None diff --git a/tests/integration/middleware/conftest.py b/tests/integration/middleware/conftest.py index c606ba53..f31b5740 100644 --- a/tests/integration/middleware/conftest.py +++ b/tests/integration/middleware/conftest.py @@ -26,7 +26,7 @@ from main import app from tests.integration.middleware.middleware_test_routes import ( admin_router, api_router, - shop_router, + storefront_router, store_router, ) from tests.integration.middleware.middleware_test_routes import ( @@ -39,7 +39,7 @@ if not any(r.path.startswith("/middleware-test") for r in app.routes if hasattr( app.include_router(api_router) app.include_router(admin_router) app.include_router(store_router) - app.include_router(shop_router) + app.include_router(storefront_router) @pytest.fixture diff --git a/tests/integration/middleware/middleware_test_routes.py b/tests/integration/middleware/middleware_test_routes.py index 7b351488..c12def5c 100644 --- a/tests/integration/middleware/middleware_test_routes.py +++ b/tests/integration/middleware/middleware_test_routes.py @@ -10,7 +10,7 @@ IMPORTANT: Routes are organized by prefix to avoid conflicts: - /api/middleware-test/* - API context testing - /admin/middleware-test/* - Admin context testing - /store/middleware-test/* - Store dashboard context testing -- /shop/middleware-test/* - Shop context testing +- /storefront/middleware-test/* - Storefront context testing """ from fastapi import APIRouter, Request @@ -531,15 +531,15 @@ async def test_store_dashboard_theme(request: Request): # ============================================================================= -# Shop Context Test Router +# Storefront Context Test Router # ============================================================================= -shop_router = APIRouter(prefix="/shop/middleware-test") +storefront_router = APIRouter(prefix="/storefront/middleware-test") -@shop_router.get("/context") -async def test_shop_context(request: Request): - """Test shop context detection.""" +@storefront_router.get("/context") +async def test_storefront_context(request: Request): + """Test storefront context detection.""" context_type = getattr(request.state, "context_type", None) store = getattr(request.state, "store", None) theme = getattr(request.state, "theme", None) @@ -552,9 +552,9 @@ async def test_shop_context(request: Request): } -@shop_router.get("/custom-domain-context") -async def test_shop_custom_domain_context(request: Request): - """Test shop context with custom domain.""" +@storefront_router.get("/custom-domain-context") +async def test_storefront_custom_domain_context(request: Request): + """Test storefront context with custom domain.""" context_type = getattr(request.state, "context_type", None) store = getattr(request.state, "store", None) return { @@ -564,9 +564,9 @@ async def test_shop_custom_domain_context(request: Request): } -@shop_router.get("/theme") -async def test_shop_theme(request: Request): - """Test theme in shop context.""" +@storefront_router.get("/theme") +async def test_storefront_theme(request: Request): + """Test theme in storefront context.""" context_type = getattr(request.state, "context_type", None) theme = getattr(request.state, "theme", None) colors = theme.get("colors", {}) if theme else {} diff --git a/tests/unit/core/test_frontend_detector.py b/tests/unit/core/test_frontend_detector.py index f418dbda..23340634 100644 --- a/tests/unit/core/test_frontend_detector.py +++ b/tests/unit/core/test_frontend_detector.py @@ -7,7 +7,6 @@ Tests cover: - Path-based detection (dev mode) - Subdomain-based detection (prod mode) - Custom domain detection -- Legacy /shop/ path support - Priority order of detection methods """ @@ -103,15 +102,6 @@ class TestFrontendDetectorStorefront: ) assert result == FrontendType.STOREFRONT - def test_detect_storefront_legacy_shop_path(self): - """Test storefront detection from legacy /shop path.""" - result = FrontendDetector.detect(host="localhost", path="/shop/products") - assert result == FrontendType.STOREFRONT - - def test_detect_storefront_legacy_shop_api_path(self): - """Test storefront detection from legacy /api/v1/shop path.""" - result = FrontendDetector.detect(host="localhost", path="/api/v1/shop/cart") - assert result == FrontendType.STOREFRONT @pytest.mark.unit diff --git a/tests/unit/middleware/test_context.py b/tests/unit/middleware/test_context.py index 2082c61e..8e9742d7 100644 --- a/tests/unit/middleware/test_context.py +++ b/tests/unit/middleware/test_context.py @@ -32,7 +32,7 @@ class TestRequestContextEnumBackwardCompatibility: assert RequestContext.API.value == "api" assert RequestContext.ADMIN.value == "admin" assert RequestContext.STORE_DASHBOARD.value == "store" - assert RequestContext.SHOP.value == "shop" + assert RequestContext.STOREFRONT.value == "storefront" assert RequestContext.FALLBACK.value == "fallback" def test_request_context_types(self): @@ -101,7 +101,7 @@ class TestGetRequestContextBackwardCompatibility: assert context == RequestContext.STORE_DASHBOARD def test_get_request_context_maps_storefront(self): - """Test get_request_context maps FrontendType.STOREFRONT to RequestContext.SHOP.""" + """Test get_request_context maps FrontendType.STOREFRONT to RequestContext.STOREFRONT.""" from app.modules.enums import FrontendType request = Mock(spec=Request) @@ -113,7 +113,7 @@ class TestGetRequestContextBackwardCompatibility: warnings.simplefilter("ignore", DeprecationWarning) context = get_request_context(request) - assert context == RequestContext.SHOP + assert context == RequestContext.STOREFRONT def test_get_request_context_maps_platform_to_fallback(self): """Test get_request_context maps FrontendType.PLATFORM to RequestContext.FALLBACK.""" diff --git a/tests/unit/middleware/test_store_context.py b/tests/unit/middleware/test_store_context.py index 08cc4561..325a64b6 100644 --- a/tests/unit/middleware/test_store_context.py +++ b/tests/unit/middleware/test_store_context.py @@ -102,10 +102,10 @@ class TestStoreContextManager: """Test path-based detection with /store/ prefix.""" request = Mock(spec=Request) request.headers = {"host": "localhost"} - request.url = Mock(path="/store/store1/shop") + request.url = Mock(path="/store/store1/storefront") # Set platform_clean_path to simulate PlatformContextMiddleware output request.state = Mock() - request.state.platform_clean_path = "/store/store1/shop" + request.state.platform_clean_path = "/store/store1/storefront" context = StoreContextManager.detect_store_context(request) @@ -119,10 +119,10 @@ class TestStoreContextManager: """Test path-based detection with /stores/ prefix.""" request = Mock(spec=Request) request.headers = {"host": "localhost"} - request.url = Mock(path="/stores/store1/shop") + request.url = Mock(path="/stores/store1/storefront") # Set platform_clean_path to simulate PlatformContextMiddleware output request.state = Mock() - request.state.platform_clean_path = "/stores/store1/shop" + request.state.platform_clean_path = "/stores/store1/storefront" context = StoreContextManager.detect_store_context(request) @@ -310,24 +310,24 @@ class TestStoreContextManager: def test_extract_clean_path_from_store_path(self): """Test extracting clean path from /store/ prefix.""" request = Mock(spec=Request) - request.url = Mock(path="/store/store1/shop/products") + request.url = Mock(path="/store/store1/storefront/products") store_context = {"detection_method": "path", "path_prefix": "/store/store1"} clean_path = StoreContextManager.extract_clean_path(request, store_context) - assert clean_path == "/shop/products" + assert clean_path == "/storefront/products" def test_extract_clean_path_from_stores_path(self): """Test extracting clean path from /stores/ prefix.""" request = Mock(spec=Request) - request.url = Mock(path="/stores/store1/shop/products") + request.url = Mock(path="/stores/store1/storefront/products") store_context = {"detection_method": "path", "path_prefix": "/stores/store1"} clean_path = StoreContextManager.extract_clean_path(request, store_context) - assert clean_path == "/shop/products" + assert clean_path == "/storefront/products" def test_extract_clean_path_root(self): """Test extracting clean path when result is empty (should return /).""" @@ -343,22 +343,22 @@ class TestStoreContextManager: def test_extract_clean_path_no_path_context(self): """Test extracting clean path for non-path detection methods.""" request = Mock(spec=Request) - request.url = Mock(path="/shop/products") + request.url = Mock(path="/storefront/products") store_context = {"detection_method": "subdomain", "subdomain": "store1"} clean_path = StoreContextManager.extract_clean_path(request, store_context) - assert clean_path == "/shop/products" + assert clean_path == "/storefront/products" def test_extract_clean_path_no_context(self): """Test extracting clean path with no store context.""" request = Mock(spec=Request) - request.url = Mock(path="/shop/products") + request.url = Mock(path="/storefront/products") clean_path = StoreContextManager.extract_clean_path(request, None) - assert clean_path == "/shop/products" + assert clean_path == "/storefront/products" # ======================================================================== # Request Type Detection Tests @@ -392,7 +392,7 @@ class TestStoreContextManager: """Test non-admin request.""" request = Mock(spec=Request) request.headers = {"host": "store1.platform.com"} - request.url = Mock(path="/shop") + request.url = Mock(path="/storefront") assert StoreContextManager.is_admin_request(request) is False @@ -406,49 +406,10 @@ class TestStoreContextManager: def test_is_not_api_request(self): """Test non-API request.""" request = Mock(spec=Request) - request.url = Mock(path="/shop/products") + request.url = Mock(path="/storefront/products") assert StoreContextManager.is_api_request(request) is False - # ======================================================================== - # Shop API Request Detection Tests - # ======================================================================== - - def test_is_shop_api_request(self): - """Test shop API request detection.""" - request = Mock(spec=Request) - request.url = Mock(path="/api/v1/shop/products") - - assert StoreContextManager.is_shop_api_request(request) is True - - def test_is_shop_api_request_cart(self): - """Test shop API request detection for cart endpoint.""" - request = Mock(spec=Request) - request.url = Mock(path="/api/v1/shop/cart") - - assert StoreContextManager.is_shop_api_request(request) is True - - def test_is_not_shop_api_request_admin(self): - """Test non-shop API request (admin API).""" - request = Mock(spec=Request) - request.url = Mock(path="/api/v1/admin/stores") - - assert StoreContextManager.is_shop_api_request(request) is False - - def test_is_not_shop_api_request_store(self): - """Test non-shop API request (store API).""" - request = Mock(spec=Request) - request.url = Mock(path="/api/v1/store/products") - - assert StoreContextManager.is_shop_api_request(request) is False - - def test_is_not_shop_api_request_non_api(self): - """Test non-shop API request (non-API path).""" - request = Mock(spec=Request) - request.url = Mock(path="/shop/products") - - assert StoreContextManager.is_shop_api_request(request) is False - # ======================================================================== # Extract Store From Referer Tests # ======================================================================== @@ -457,7 +418,7 @@ class TestStoreContextManager: """Test extracting store from referer with /stores/ path.""" request = Mock(spec=Request) request.headers = { - "referer": "http://localhost:8000/stores/wizamart/shop/products" + "referer": "http://localhost:8000/stores/wizamart/storefront/products" } context = StoreContextManager.extract_store_from_referer(request) @@ -472,7 +433,7 @@ class TestStoreContextManager: """Test extracting store from referer with /store/ path.""" request = Mock(spec=Request) request.headers = { - "referer": "http://localhost:8000/store/myshop/shop/products" + "referer": "http://localhost:8000/store/myshop/storefront/products" } context = StoreContextManager.extract_store_from_referer(request) @@ -486,7 +447,7 @@ class TestStoreContextManager: def test_extract_store_from_referer_subdomain(self): """Test extracting store from referer with subdomain.""" request = Mock(spec=Request) - request.headers = {"referer": "http://wizamart.platform.com/shop/products"} + request.headers = {"referer": "http://wizamart.platform.com/storefront/products"} with patch("middleware.store_context.settings") as mock_settings: mock_settings.platform_domain = "platform.com" @@ -501,7 +462,7 @@ class TestStoreContextManager: def test_extract_store_from_referer_custom_domain(self): """Test extracting store from referer with custom domain.""" request = Mock(spec=Request) - request.headers = {"referer": "http://my-custom-shop.com/shop/products"} + request.headers = {"referer": "http://my-custom-shop.com/storefront/products"} with patch("middleware.store_context.settings") as mock_settings: mock_settings.platform_domain = "platform.com" @@ -525,7 +486,7 @@ class TestStoreContextManager: def test_extract_store_from_referer_origin_header(self): """Test extracting store from origin header when referer is missing.""" request = Mock(spec=Request) - request.headers = {"origin": "http://localhost:8000/stores/testshop/shop"} + request.headers = {"origin": "http://localhost:8000/stores/testshop/storefront"} context = StoreContextManager.extract_store_from_referer(request) @@ -548,7 +509,7 @@ class TestStoreContextManager: def test_extract_store_from_referer_ignores_www_subdomain(self): """Test that www subdomain is not extracted from referer.""" request = Mock(spec=Request) - request.headers = {"referer": "http://www.platform.com/shop"} + request.headers = {"referer": "http://www.platform.com/storefront"} with patch("middleware.store_context.settings") as mock_settings: mock_settings.platform_domain = "platform.com" @@ -560,7 +521,7 @@ class TestStoreContextManager: def test_extract_store_from_referer_localhost_not_custom_domain(self): """Test that localhost is not treated as custom domain.""" request = Mock(spec=Request) - request.headers = {"referer": "http://localhost:8000/shop"} + request.headers = {"referer": "http://localhost:8000/storefront"} with patch("middleware.store_context.settings") as mock_settings: mock_settings.platform_domain = "platform.com" @@ -601,7 +562,7 @@ class TestStoreContextManager: @pytest.mark.parametrize( "path", [ - "/shop/products", + "/storefront/products", "/admin/dashboard", "/api/stores", "/about", @@ -686,7 +647,7 @@ class TestStoreContextMiddleware: request = Mock(spec=Request) request.headers = {"host": "store1.platform.com"} - request.url = Mock(path="/shop/products") + request.url = Mock(path="/storefront/products") request.state = Mock() call_next = AsyncMock(return_value=Mock()) @@ -714,7 +675,7 @@ class TestStoreContextMiddleware: patch.object( StoreContextManager, "extract_clean_path", - return_value="/shop/products", + return_value="/storefront/products", ), patch("middleware.store_context.get_db", return_value=iter([mock_db])), ): @@ -722,7 +683,7 @@ class TestStoreContextMiddleware: assert request.state.store is mock_store assert request.state.store_context == store_context - assert request.state.clean_path == "/shop/products" + assert request.state.clean_path == "/storefront/products" call_next.assert_called_once_with(request) @pytest.mark.asyncio @@ -732,7 +693,7 @@ class TestStoreContextMiddleware: request = Mock(spec=Request) request.headers = {"host": "nonexistent.platform.com"} - request.url = Mock(path="/shop") + request.url = Mock(path="/storefront") request.state = Mock() call_next = AsyncMock(return_value=Mock()) @@ -756,7 +717,7 @@ class TestStoreContextMiddleware: assert request.state.store is None assert request.state.store_context == store_context - assert request.state.clean_path == "/shop" + assert request.state.clean_path == "/storefront" call_next.assert_called_once_with(request) @pytest.mark.asyncio @@ -820,146 +781,6 @@ class TestStoreContextMiddleware: assert request.state.clean_path == path call_next.assert_called_once_with(request) - # ======================================================================== - # Shop API Request Handling Tests - # ======================================================================== - - @pytest.mark.asyncio - async def test_middleware_shop_api_with_referer_store_found(self): - """Test middleware handles shop API request with store from Referer.""" - middleware = StoreContextMiddleware(app=None) - - request = Mock(spec=Request) - request.headers = { - "host": "localhost", - "referer": "http://localhost:8000/stores/wizamart/shop/products", - } - request.url = Mock(path="/api/v1/shop/cart") - request.state = Mock() - - call_next = AsyncMock(return_value=Mock()) - - mock_store = Mock() - mock_store.id = 1 - mock_store.name = "Wizamart" - mock_store.subdomain = "wizamart" - - store_context = { - "subdomain": "wizamart", - "detection_method": "path", - "path_prefix": "/stores/wizamart", - "full_prefix": "/stores/", - } - - mock_db = MagicMock() - - with ( - patch.object(StoreContextManager, "is_admin_request", return_value=False), - patch.object( - StoreContextManager, "is_static_file_request", return_value=False - ), - patch.object( - StoreContextManager, "is_shop_api_request", return_value=True - ), - patch.object( - StoreContextManager, - "extract_store_from_referer", - return_value=store_context, - ), - patch.object( - StoreContextManager, - "get_store_from_context", - return_value=mock_store, - ), - patch("middleware.store_context.get_db", return_value=iter([mock_db])), - ): - await middleware.dispatch(request, call_next) - - assert request.state.store is mock_store - assert request.state.store_context == store_context - assert request.state.clean_path == "/api/v1/shop/cart" - call_next.assert_called_once_with(request) - - @pytest.mark.asyncio - async def test_middleware_shop_api_with_referer_store_not_found(self): - """Test middleware handles shop API when store from Referer not in database.""" - middleware = StoreContextMiddleware(app=None) - - request = Mock(spec=Request) - request.headers = { - "host": "localhost", - "referer": "http://localhost:8000/stores/nonexistent/shop/products", - } - request.url = Mock(path="/api/v1/shop/cart") - request.state = Mock() - - call_next = AsyncMock(return_value=Mock()) - - store_context = { - "subdomain": "nonexistent", - "detection_method": "path", - "path_prefix": "/stores/nonexistent", - "full_prefix": "/stores/", - } - - mock_db = MagicMock() - - with ( - patch.object(StoreContextManager, "is_admin_request", return_value=False), - patch.object( - StoreContextManager, "is_static_file_request", return_value=False - ), - patch.object( - StoreContextManager, "is_shop_api_request", return_value=True - ), - patch.object( - StoreContextManager, - "extract_store_from_referer", - return_value=store_context, - ), - patch.object( - StoreContextManager, "get_store_from_context", return_value=None - ), - patch("middleware.store_context.get_db", return_value=iter([mock_db])), - ): - await middleware.dispatch(request, call_next) - - assert request.state.store is None - assert request.state.store_context == store_context - assert request.state.clean_path == "/api/v1/shop/cart" - call_next.assert_called_once_with(request) - - @pytest.mark.asyncio - async def test_middleware_shop_api_without_referer(self): - """Test middleware handles shop API request without Referer header.""" - middleware = StoreContextMiddleware(app=None) - - request = Mock(spec=Request) - request.headers = {"host": "localhost"} - request.url = Mock(path="/api/v1/shop/products") - request.state = Mock() - - call_next = AsyncMock(return_value=Mock()) - - with ( - patch.object(StoreContextManager, "is_admin_request", return_value=False), - patch.object( - StoreContextManager, "is_static_file_request", return_value=False - ), - patch.object( - StoreContextManager, "is_shop_api_request", return_value=True - ), - patch.object( - StoreContextManager, "extract_store_from_referer", return_value=None - ), - ): - await middleware.dispatch(request, call_next) - - assert request.state.store is None - assert request.state.store_context is None - assert request.state.clean_path == "/api/v1/shop/products" - call_next.assert_called_once_with(request) - @pytest.mark.unit @pytest.mark.stores