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 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,9 @@ naming_rules:
|
|||||||
description: |
|
description: |
|
||||||
Service files should use singular name + _service (vendor_service.py)
|
Service files should use singular name + _service (vendor_service.py)
|
||||||
pattern:
|
pattern:
|
||||||
file_pattern: "app/services/**/*.py"
|
file_pattern:
|
||||||
|
- "app/services/**/*.py"
|
||||||
|
- "app/modules/*/services/**/*.py"
|
||||||
check: "service_naming"
|
check: "service_naming"
|
||||||
|
|
||||||
- id: "NAM-003"
|
- id: "NAM-003"
|
||||||
@@ -31,14 +33,16 @@ naming_rules:
|
|||||||
description: |
|
description: |
|
||||||
Both database and schema model files use singular names (product.py)
|
Both database and schema model files use singular names (product.py)
|
||||||
pattern:
|
pattern:
|
||||||
file_pattern: "models/**/*.py"
|
file_pattern:
|
||||||
|
- "models/**/*.py"
|
||||||
|
- "app/modules/*/models/**/*.py"
|
||||||
check: "singular_naming"
|
check: "singular_naming"
|
||||||
|
|
||||||
- id: "NAM-004"
|
- id: "NAM-004"
|
||||||
name: "Use consistent terminology: vendor not shop"
|
name: "Use consistent terminology: vendor not shop"
|
||||||
severity: "warning"
|
severity: "warning"
|
||||||
description: |
|
description: |
|
||||||
Use 'vendor' consistently, not 'shop' (except for shop frontend)
|
Use 'vendor' consistently, not 'shop' (except for storefront)
|
||||||
pattern:
|
pattern:
|
||||||
file_pattern: "app/**/*.py"
|
file_pattern: "app/**/*.py"
|
||||||
discouraged_terms:
|
discouraged_terms:
|
||||||
|
|||||||
@@ -20,19 +20,19 @@ MERCHANT ROUTES (/merchants/*):
|
|||||||
- Role: store (merchant owners are store-role users who own merchants)
|
- Role: store (merchant owners are store-role users who own merchants)
|
||||||
- Validates: User owns the merchant via Merchant.owner_user_id
|
- Validates: User owns the merchant via Merchant.owner_user_id
|
||||||
|
|
||||||
CUSTOMER/SHOP ROUTES (/shop/account/*):
|
CUSTOMER/STOREFRONT ROUTES (/storefront/account/*):
|
||||||
- Cookie: customer_token (path=/shop) OR Authorization header
|
- Cookie: customer_token (path=/storefront) OR Authorization header
|
||||||
- Role: customer only
|
- Role: customer only
|
||||||
- Blocks: admins, stores
|
- 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:
|
This dual authentication approach supports:
|
||||||
- HTML pages: Use cookies (automatic browser behavior)
|
- HTML pages: Use cookies (automatic browser behavior)
|
||||||
- API calls: Use Authorization headers (explicit JavaScript control)
|
- API calls: Use Authorization headers (explicit JavaScript control)
|
||||||
|
|
||||||
The cookie path restrictions prevent cross-context cookie leakage:
|
The cookie path restrictions prevent cross-context cookie leakage:
|
||||||
- admin_token is NEVER sent to /store/* or /shop/*
|
- admin_token is NEVER sent to /store/* or /storefront/*
|
||||||
- store_token is NEVER sent to /admin/* or /shop/*
|
- store_token is NEVER sent to /admin/* or /storefront/*
|
||||||
- customer_token is NEVER sent to /admin/* or /store/*
|
- 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")
|
raise InvalidTokenException("Customer account is inactive")
|
||||||
|
|
||||||
# Validate store context matches token
|
# 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)
|
request_store = getattr(request.state, "store", None)
|
||||||
if request_store and token_store_id:
|
if request_store and token_store_id:
|
||||||
if request_store.id != 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.
|
Get current customer from customer_token cookie or Authorization header.
|
||||||
|
|
||||||
Used for shop account HTML pages (/shop/account/*) that need cookie-based auth.
|
Used for storefront account HTML pages (/storefront/account/*) that need cookie-based auth.
|
||||||
Note: Public shop pages (/shop/products, etc.) don't use this dependency.
|
Note: Public storefront pages (/storefront/products, etc.) don't use this dependency.
|
||||||
|
|
||||||
Validates that token store_id matches request store (URL-based detection).
|
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.
|
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).
|
Validates that token store_id matches request store (URL-based detection).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -46,8 +46,6 @@ class FrontendDetector:
|
|||||||
STOREFRONT_PATH_PREFIXES = (
|
STOREFRONT_PATH_PREFIXES = (
|
||||||
"/storefront",
|
"/storefront",
|
||||||
"/api/v1/storefront",
|
"/api/v1/storefront",
|
||||||
"/shop", # Legacy support
|
|
||||||
"/api/v1/shop", # Legacy support
|
|
||||||
"/stores/", # Path-based store access
|
"/stores/", # Path-based store access
|
||||||
)
|
)
|
||||||
MERCHANT_PATH_PREFIXES = ("/merchants", "/api/v1/merchants")
|
MERCHANT_PATH_PREFIXES = ("/merchants", "/api/v1/merchants")
|
||||||
@@ -113,7 +111,7 @@ class FrontendDetector:
|
|||||||
return FrontendType.PLATFORM
|
return FrontendType.PLATFORM
|
||||||
|
|
||||||
# 3. Store subdomain detection (wizamart.oms.lu)
|
# 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:
|
if subdomain and subdomain not in cls.RESERVED_SUBDOMAINS:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"[FRONTEND_DETECTOR] Detected STOREFRONT from subdomain: {subdomain}"
|
f"[FRONTEND_DETECTOR] Detected STOREFRONT from subdomain: {subdomain}"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Migration guide:
|
|||||||
- RequestContext.API -> Check with FrontendDetector.is_api_request()
|
- RequestContext.API -> Check with FrontendDetector.is_api_request()
|
||||||
- RequestContext.ADMIN -> FrontendType.ADMIN
|
- RequestContext.ADMIN -> FrontendType.ADMIN
|
||||||
- RequestContext.STORE_DASHBOARD -> FrontendType.STORE
|
- RequestContext.STORE_DASHBOARD -> FrontendType.STORE
|
||||||
- RequestContext.SHOP -> FrontendType.STOREFRONT
|
- RequestContext.STOREFRONT -> FrontendType.STOREFRONT
|
||||||
- RequestContext.FALLBACK -> FrontendType.PLATFORM (or handle API separately)
|
- RequestContext.FALLBACK -> FrontendType.PLATFORM (or handle API separately)
|
||||||
|
|
||||||
- get_request_context(request) -> get_frontend_type(request)
|
- get_request_context(request) -> get_frontend_type(request)
|
||||||
@@ -44,14 +44,14 @@ class RequestContext(str, Enum):
|
|||||||
- API -> Use FrontendDetector.is_api_request() + FrontendType
|
- API -> Use FrontendDetector.is_api_request() + FrontendType
|
||||||
- ADMIN -> FrontendType.ADMIN
|
- ADMIN -> FrontendType.ADMIN
|
||||||
- STORE_DASHBOARD -> FrontendType.STORE
|
- STORE_DASHBOARD -> FrontendType.STORE
|
||||||
- SHOP -> FrontendType.STOREFRONT
|
- STOREFRONT -> FrontendType.STOREFRONT
|
||||||
- FALLBACK -> FrontendType.PLATFORM
|
- FALLBACK -> FrontendType.PLATFORM
|
||||||
"""
|
"""
|
||||||
|
|
||||||
API = "api"
|
API = "api"
|
||||||
ADMIN = "admin"
|
ADMIN = "admin"
|
||||||
STORE_DASHBOARD = "store"
|
STORE_DASHBOARD = "store"
|
||||||
SHOP = "shop"
|
STOREFRONT = "storefront"
|
||||||
FALLBACK = "fallback"
|
FALLBACK = "fallback"
|
||||||
|
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ def get_request_context(request: Request) -> RequestContext:
|
|||||||
mapping = {
|
mapping = {
|
||||||
FrontendType.ADMIN: RequestContext.ADMIN,
|
FrontendType.ADMIN: RequestContext.ADMIN,
|
||||||
FrontendType.STORE: RequestContext.STORE_DASHBOARD,
|
FrontendType.STORE: RequestContext.STORE_DASHBOARD,
|
||||||
FrontendType.STOREFRONT: RequestContext.SHOP,
|
FrontendType.STOREFRONT: RequestContext.STOREFRONT,
|
||||||
FrontendType.PLATFORM: RequestContext.FALLBACK,
|
FrontendType.PLATFORM: RequestContext.FALLBACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -239,31 +239,26 @@ class StoreContextManager:
|
|||||||
"""Check if request is for API endpoints."""
|
"""Check if request is for API endpoints."""
|
||||||
return FrontendDetector.is_api_request(request.url.path)
|
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
|
@staticmethod
|
||||||
def extract_store_from_referer(request: Request) -> dict | None:
|
def extract_store_from_referer(request: Request) -> dict | None:
|
||||||
"""
|
"""
|
||||||
Extract store context from Referer header.
|
Extract store context from Referer header.
|
||||||
|
|
||||||
Used for shop API requests where store context comes from the page
|
Used for storefront API requests where store context comes from the page
|
||||||
that made the API call (e.g., JavaScript on /stores/wizamart/shop/products
|
that made the API call (e.g., JavaScript on /stores/wizamart/storefront/products
|
||||||
calling /api/v1/shop/products).
|
calling /api/v1/storefront/products).
|
||||||
|
|
||||||
Extracts store from Referer URL patterns:
|
Extracts store from Referer URL patterns:
|
||||||
- http://localhost:8000/stores/wizamart/shop/... → wizamart
|
- http://localhost:8000/stores/wizamart/storefront/... → wizamart
|
||||||
- http://wizamart.platform.com/shop/... → wizamart (subdomain) # noqa
|
- http://wizamart.platform.com/storefront/... → wizamart (subdomain) # noqa
|
||||||
- http://custom-domain.com/shop/... → custom-domain.com # noqa
|
- http://custom-domain.com/storefront/... → custom-domain.com # noqa
|
||||||
|
|
||||||
Returns store context dict or None if unable to extract.
|
Returns store context dict or None if unable to extract.
|
||||||
"""
|
"""
|
||||||
referer = request.headers.get("referer") or request.headers.get("origin")
|
referer = request.headers.get("referer") or request.headers.get("origin")
|
||||||
|
|
||||||
if not referer:
|
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
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -287,7 +282,7 @@ class StoreContextManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Method 1: Path-based detection from referer path
|
# Method 1: Path-based detection from referer path
|
||||||
# /stores/wizamart/shop/products → wizamart
|
# /stores/wizamart/storefront/products → wizamart
|
||||||
if referer_path.startswith(("/stores/", "/store/")):
|
if referer_path.startswith(("/stores/", "/store/")):
|
||||||
prefix = (
|
prefix = (
|
||||||
"/stores/" if referer_path.startswith("/stores/") else "/store/"
|
"/stores/" if referer_path.startswith("/stores/") else "/store/"
|
||||||
@@ -448,75 +443,10 @@ class StoreContextMiddleware(BaseHTTPMiddleware):
|
|||||||
request.state.clean_path = request.url.path
|
request.state.clean_path = request.url.path
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
# Handle shop API routes specially - extract store from Referer header
|
# Skip store detection for API routes (admin API, store API have store_id in URL)
|
||||||
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)
|
|
||||||
if StoreContextManager.is_api_request(request):
|
if StoreContextManager.is_api_request(request):
|
||||||
logger.debug(
|
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"},
|
extra={"path": request.url.path, "reason": "api"},
|
||||||
)
|
)
|
||||||
request.state.store = None
|
request.state.store = None
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from main import app
|
|||||||
from tests.integration.middleware.middleware_test_routes import (
|
from tests.integration.middleware.middleware_test_routes import (
|
||||||
admin_router,
|
admin_router,
|
||||||
api_router,
|
api_router,
|
||||||
shop_router,
|
storefront_router,
|
||||||
store_router,
|
store_router,
|
||||||
)
|
)
|
||||||
from tests.integration.middleware.middleware_test_routes import (
|
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(api_router)
|
||||||
app.include_router(admin_router)
|
app.include_router(admin_router)
|
||||||
app.include_router(store_router)
|
app.include_router(store_router)
|
||||||
app.include_router(shop_router)
|
app.include_router(storefront_router)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ IMPORTANT: Routes are organized by prefix to avoid conflicts:
|
|||||||
- /api/middleware-test/* - API context testing
|
- /api/middleware-test/* - API context testing
|
||||||
- /admin/middleware-test/* - Admin context testing
|
- /admin/middleware-test/* - Admin context testing
|
||||||
- /store/middleware-test/* - Store dashboard 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
|
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")
|
@storefront_router.get("/context")
|
||||||
async def test_shop_context(request: Request):
|
async def test_storefront_context(request: Request):
|
||||||
"""Test shop context detection."""
|
"""Test storefront context detection."""
|
||||||
context_type = getattr(request.state, "context_type", None)
|
context_type = getattr(request.state, "context_type", None)
|
||||||
store = getattr(request.state, "store", None)
|
store = getattr(request.state, "store", None)
|
||||||
theme = getattr(request.state, "theme", None)
|
theme = getattr(request.state, "theme", None)
|
||||||
@@ -552,9 +552,9 @@ async def test_shop_context(request: Request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@shop_router.get("/custom-domain-context")
|
@storefront_router.get("/custom-domain-context")
|
||||||
async def test_shop_custom_domain_context(request: Request):
|
async def test_storefront_custom_domain_context(request: Request):
|
||||||
"""Test shop context with custom domain."""
|
"""Test storefront context with custom domain."""
|
||||||
context_type = getattr(request.state, "context_type", None)
|
context_type = getattr(request.state, "context_type", None)
|
||||||
store = getattr(request.state, "store", None)
|
store = getattr(request.state, "store", None)
|
||||||
return {
|
return {
|
||||||
@@ -564,9 +564,9 @@ async def test_shop_custom_domain_context(request: Request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@shop_router.get("/theme")
|
@storefront_router.get("/theme")
|
||||||
async def test_shop_theme(request: Request):
|
async def test_storefront_theme(request: Request):
|
||||||
"""Test theme in shop context."""
|
"""Test theme in storefront context."""
|
||||||
context_type = getattr(request.state, "context_type", None)
|
context_type = getattr(request.state, "context_type", None)
|
||||||
theme = getattr(request.state, "theme", None)
|
theme = getattr(request.state, "theme", None)
|
||||||
colors = theme.get("colors", {}) if theme else {}
|
colors = theme.get("colors", {}) if theme else {}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ Tests cover:
|
|||||||
- Path-based detection (dev mode)
|
- Path-based detection (dev mode)
|
||||||
- Subdomain-based detection (prod mode)
|
- Subdomain-based detection (prod mode)
|
||||||
- Custom domain detection
|
- Custom domain detection
|
||||||
- Legacy /shop/ path support
|
|
||||||
- Priority order of detection methods
|
- Priority order of detection methods
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -103,15 +102,6 @@ class TestFrontendDetectorStorefront:
|
|||||||
)
|
)
|
||||||
assert result == FrontendType.STOREFRONT
|
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
|
@pytest.mark.unit
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class TestRequestContextEnumBackwardCompatibility:
|
|||||||
assert RequestContext.API.value == "api"
|
assert RequestContext.API.value == "api"
|
||||||
assert RequestContext.ADMIN.value == "admin"
|
assert RequestContext.ADMIN.value == "admin"
|
||||||
assert RequestContext.STORE_DASHBOARD.value == "store"
|
assert RequestContext.STORE_DASHBOARD.value == "store"
|
||||||
assert RequestContext.SHOP.value == "shop"
|
assert RequestContext.STOREFRONT.value == "storefront"
|
||||||
assert RequestContext.FALLBACK.value == "fallback"
|
assert RequestContext.FALLBACK.value == "fallback"
|
||||||
|
|
||||||
def test_request_context_types(self):
|
def test_request_context_types(self):
|
||||||
@@ -101,7 +101,7 @@ class TestGetRequestContextBackwardCompatibility:
|
|||||||
assert context == RequestContext.STORE_DASHBOARD
|
assert context == RequestContext.STORE_DASHBOARD
|
||||||
|
|
||||||
def test_get_request_context_maps_storefront(self):
|
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
|
from app.modules.enums import FrontendType
|
||||||
|
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
@@ -113,7 +113,7 @@ class TestGetRequestContextBackwardCompatibility:
|
|||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
context = get_request_context(request)
|
context = get_request_context(request)
|
||||||
|
|
||||||
assert context == RequestContext.SHOP
|
assert context == RequestContext.STOREFRONT
|
||||||
|
|
||||||
def test_get_request_context_maps_platform_to_fallback(self):
|
def test_get_request_context_maps_platform_to_fallback(self):
|
||||||
"""Test get_request_context maps FrontendType.PLATFORM to RequestContext.FALLBACK."""
|
"""Test get_request_context maps FrontendType.PLATFORM to RequestContext.FALLBACK."""
|
||||||
|
|||||||
@@ -102,10 +102,10 @@ class TestStoreContextManager:
|
|||||||
"""Test path-based detection with /store/ prefix."""
|
"""Test path-based detection with /store/ prefix."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {"host": "localhost"}
|
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
|
# Set platform_clean_path to simulate PlatformContextMiddleware output
|
||||||
request.state = Mock()
|
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)
|
context = StoreContextManager.detect_store_context(request)
|
||||||
|
|
||||||
@@ -119,10 +119,10 @@ class TestStoreContextManager:
|
|||||||
"""Test path-based detection with /stores/ prefix."""
|
"""Test path-based detection with /stores/ prefix."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {"host": "localhost"}
|
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
|
# Set platform_clean_path to simulate PlatformContextMiddleware output
|
||||||
request.state = Mock()
|
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)
|
context = StoreContextManager.detect_store_context(request)
|
||||||
|
|
||||||
@@ -310,24 +310,24 @@ class TestStoreContextManager:
|
|||||||
def test_extract_clean_path_from_store_path(self):
|
def test_extract_clean_path_from_store_path(self):
|
||||||
"""Test extracting clean path from /store/ prefix."""
|
"""Test extracting clean path from /store/ prefix."""
|
||||||
request = Mock(spec=Request)
|
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"}
|
store_context = {"detection_method": "path", "path_prefix": "/store/store1"}
|
||||||
|
|
||||||
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
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):
|
def test_extract_clean_path_from_stores_path(self):
|
||||||
"""Test extracting clean path from /stores/ prefix."""
|
"""Test extracting clean path from /stores/ prefix."""
|
||||||
request = Mock(spec=Request)
|
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"}
|
store_context = {"detection_method": "path", "path_prefix": "/stores/store1"}
|
||||||
|
|
||||||
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
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):
|
def test_extract_clean_path_root(self):
|
||||||
"""Test extracting clean path when result is empty (should return /)."""
|
"""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):
|
def test_extract_clean_path_no_path_context(self):
|
||||||
"""Test extracting clean path for non-path detection methods."""
|
"""Test extracting clean path for non-path detection methods."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.url = Mock(path="/shop/products")
|
request.url = Mock(path="/storefront/products")
|
||||||
|
|
||||||
store_context = {"detection_method": "subdomain", "subdomain": "store1"}
|
store_context = {"detection_method": "subdomain", "subdomain": "store1"}
|
||||||
|
|
||||||
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
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):
|
def test_extract_clean_path_no_context(self):
|
||||||
"""Test extracting clean path with no store context."""
|
"""Test extracting clean path with no store context."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.url = Mock(path="/shop/products")
|
request.url = Mock(path="/storefront/products")
|
||||||
|
|
||||||
clean_path = StoreContextManager.extract_clean_path(request, None)
|
clean_path = StoreContextManager.extract_clean_path(request, None)
|
||||||
|
|
||||||
assert clean_path == "/shop/products"
|
assert clean_path == "/storefront/products"
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
# Request Type Detection Tests
|
# Request Type Detection Tests
|
||||||
@@ -392,7 +392,7 @@ class TestStoreContextManager:
|
|||||||
"""Test non-admin request."""
|
"""Test non-admin request."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {"host": "store1.platform.com"}
|
request.headers = {"host": "store1.platform.com"}
|
||||||
request.url = Mock(path="/shop")
|
request.url = Mock(path="/storefront")
|
||||||
|
|
||||||
assert StoreContextManager.is_admin_request(request) is False
|
assert StoreContextManager.is_admin_request(request) is False
|
||||||
|
|
||||||
@@ -406,49 +406,10 @@ class TestStoreContextManager:
|
|||||||
def test_is_not_api_request(self):
|
def test_is_not_api_request(self):
|
||||||
"""Test non-API request."""
|
"""Test non-API request."""
|
||||||
request = Mock(spec=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
|
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
|
# Extract Store From Referer Tests
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
@@ -457,7 +418,7 @@ class TestStoreContextManager:
|
|||||||
"""Test extracting store from referer with /stores/ path."""
|
"""Test extracting store from referer with /stores/ path."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {
|
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)
|
context = StoreContextManager.extract_store_from_referer(request)
|
||||||
@@ -472,7 +433,7 @@ class TestStoreContextManager:
|
|||||||
"""Test extracting store from referer with /store/ path."""
|
"""Test extracting store from referer with /store/ path."""
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {
|
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)
|
context = StoreContextManager.extract_store_from_referer(request)
|
||||||
@@ -486,7 +447,7 @@ class TestStoreContextManager:
|
|||||||
def test_extract_store_from_referer_subdomain(self):
|
def test_extract_store_from_referer_subdomain(self):
|
||||||
"""Test extracting store from referer with subdomain."""
|
"""Test extracting store from referer with subdomain."""
|
||||||
request = Mock(spec=Request)
|
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:
|
with patch("middleware.store_context.settings") as mock_settings:
|
||||||
mock_settings.platform_domain = "platform.com"
|
mock_settings.platform_domain = "platform.com"
|
||||||
@@ -501,7 +462,7 @@ class TestStoreContextManager:
|
|||||||
def test_extract_store_from_referer_custom_domain(self):
|
def test_extract_store_from_referer_custom_domain(self):
|
||||||
"""Test extracting store from referer with custom domain."""
|
"""Test extracting store from referer with custom domain."""
|
||||||
request = Mock(spec=Request)
|
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:
|
with patch("middleware.store_context.settings") as mock_settings:
|
||||||
mock_settings.platform_domain = "platform.com"
|
mock_settings.platform_domain = "platform.com"
|
||||||
@@ -525,7 +486,7 @@ class TestStoreContextManager:
|
|||||||
def test_extract_store_from_referer_origin_header(self):
|
def test_extract_store_from_referer_origin_header(self):
|
||||||
"""Test extracting store from origin header when referer is missing."""
|
"""Test extracting store from origin header when referer is missing."""
|
||||||
request = Mock(spec=Request)
|
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)
|
context = StoreContextManager.extract_store_from_referer(request)
|
||||||
|
|
||||||
@@ -548,7 +509,7 @@ class TestStoreContextManager:
|
|||||||
def test_extract_store_from_referer_ignores_www_subdomain(self):
|
def test_extract_store_from_referer_ignores_www_subdomain(self):
|
||||||
"""Test that www subdomain is not extracted from referer."""
|
"""Test that www subdomain is not extracted from referer."""
|
||||||
request = Mock(spec=Request)
|
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:
|
with patch("middleware.store_context.settings") as mock_settings:
|
||||||
mock_settings.platform_domain = "platform.com"
|
mock_settings.platform_domain = "platform.com"
|
||||||
@@ -560,7 +521,7 @@ class TestStoreContextManager:
|
|||||||
def test_extract_store_from_referer_localhost_not_custom_domain(self):
|
def test_extract_store_from_referer_localhost_not_custom_domain(self):
|
||||||
"""Test that localhost is not treated as custom domain."""
|
"""Test that localhost is not treated as custom domain."""
|
||||||
request = Mock(spec=Request)
|
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:
|
with patch("middleware.store_context.settings") as mock_settings:
|
||||||
mock_settings.platform_domain = "platform.com"
|
mock_settings.platform_domain = "platform.com"
|
||||||
@@ -601,7 +562,7 @@ class TestStoreContextManager:
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path",
|
"path",
|
||||||
[
|
[
|
||||||
"/shop/products",
|
"/storefront/products",
|
||||||
"/admin/dashboard",
|
"/admin/dashboard",
|
||||||
"/api/stores",
|
"/api/stores",
|
||||||
"/about",
|
"/about",
|
||||||
@@ -686,7 +647,7 @@ class TestStoreContextMiddleware:
|
|||||||
|
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {"host": "store1.platform.com"}
|
request.headers = {"host": "store1.platform.com"}
|
||||||
request.url = Mock(path="/shop/products")
|
request.url = Mock(path="/storefront/products")
|
||||||
request.state = Mock()
|
request.state = Mock()
|
||||||
|
|
||||||
call_next = AsyncMock(return_value=Mock())
|
call_next = AsyncMock(return_value=Mock())
|
||||||
@@ -714,7 +675,7 @@ class TestStoreContextMiddleware:
|
|||||||
patch.object(
|
patch.object(
|
||||||
StoreContextManager,
|
StoreContextManager,
|
||||||
"extract_clean_path",
|
"extract_clean_path",
|
||||||
return_value="/shop/products",
|
return_value="/storefront/products",
|
||||||
),
|
),
|
||||||
patch("middleware.store_context.get_db", return_value=iter([mock_db])),
|
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 is mock_store
|
||||||
assert request.state.store_context == store_context
|
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)
|
call_next.assert_called_once_with(request)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -732,7 +693,7 @@ class TestStoreContextMiddleware:
|
|||||||
|
|
||||||
request = Mock(spec=Request)
|
request = Mock(spec=Request)
|
||||||
request.headers = {"host": "nonexistent.platform.com"}
|
request.headers = {"host": "nonexistent.platform.com"}
|
||||||
request.url = Mock(path="/shop")
|
request.url = Mock(path="/storefront")
|
||||||
request.state = Mock()
|
request.state = Mock()
|
||||||
|
|
||||||
call_next = AsyncMock(return_value=Mock())
|
call_next = AsyncMock(return_value=Mock())
|
||||||
@@ -756,7 +717,7 @@ class TestStoreContextMiddleware:
|
|||||||
|
|
||||||
assert request.state.store is None
|
assert request.state.store is None
|
||||||
assert request.state.store_context == store_context
|
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)
|
call_next.assert_called_once_with(request)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -820,146 +781,6 @@ class TestStoreContextMiddleware:
|
|||||||
assert request.state.clean_path == path
|
assert request.state.clean_path == path
|
||||||
call_next.assert_called_once_with(request)
|
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.unit
|
||||||
@pytest.mark.stores
|
@pytest.mark.stores
|
||||||
|
|||||||
Reference in New Issue
Block a user