Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
921 lines
34 KiB
Python
921 lines
34 KiB
Python
# tests/unit/middleware/test_store_context.py
|
|
"""
|
|
Comprehensive unit tests for StoreContextMiddleware and StoreContextManager.
|
|
|
|
Tests cover:
|
|
- Store detection from custom domains, subdomains, and path-based routing
|
|
- Database lookup and store validation
|
|
- Path extraction and cleanup
|
|
- Admin and API request detection
|
|
- Static file request detection
|
|
- Edge cases and error handling
|
|
"""
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
from fastapi import Request
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.frontend_detector import FrontendDetector
|
|
from app.modules.tenancy.exceptions import StoreNotFoundException
|
|
from middleware.store_context import (
|
|
StoreContextManager,
|
|
StoreContextMiddleware,
|
|
get_current_store,
|
|
require_store_context,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.stores
|
|
class TestStoreContextManager:
|
|
"""Test suite for StoreContextManager static methods."""
|
|
|
|
# ========================================================================
|
|
# Store Context Detection Tests
|
|
# ========================================================================
|
|
|
|
def test_detect_custom_domain(self):
|
|
"""Test custom domain detection."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "customdomain1.com"}
|
|
request.url = Mock(path="/")
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "custom_domain"
|
|
assert context["domain"] == "customdomain1.com"
|
|
assert context["host"] == "customdomain1.com"
|
|
|
|
def test_detect_custom_domain_with_port(self):
|
|
"""Test custom domain detection with port number."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "customdomain1.com:8000"}
|
|
request.url = Mock(path="/")
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "custom_domain"
|
|
assert context["domain"] == "customdomain1.com"
|
|
assert context["host"] == "customdomain1.com"
|
|
|
|
def test_detect_subdomain(self):
|
|
"""Test subdomain detection."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "store1.platform.com"}
|
|
request.url = Mock(path="/")
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "subdomain"
|
|
assert context["subdomain"] == "store1"
|
|
assert context["host"] == "store1.platform.com"
|
|
|
|
def test_detect_subdomain_with_port(self):
|
|
"""Test subdomain detection with port number."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "store1.platform.com:8000"}
|
|
request.url = Mock(path="/")
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "subdomain"
|
|
assert context["subdomain"] == "store1"
|
|
|
|
def test_detect_path_store_singular(self):
|
|
"""Test path-based detection with /store/ prefix."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
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/storefront"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "path"
|
|
assert context["subdomain"] == "store1"
|
|
assert context["path_prefix"] == "/store/store1"
|
|
assert context["full_prefix"] == "/store/"
|
|
|
|
def test_detect_path_stores_plural(self):
|
|
"""Test path-based detection with /stores/ prefix."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
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/storefront"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "path"
|
|
assert context["subdomain"] == "store1"
|
|
assert context["path_prefix"] == "/stores/store1"
|
|
assert context["full_prefix"] == "/stores/"
|
|
|
|
def test_detect_no_store_context(self):
|
|
"""Test when no store context can be detected."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/random/path")
|
|
# Set platform_clean_path to None to use url.path
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_ignore_admin_subdomain(self):
|
|
"""Test that admin subdomain is not detected as store."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "admin.platform.com"}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_ignore_www_subdomain(self):
|
|
"""Test that www subdomain is not detected as store."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "www.platform.com"}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_ignore_api_subdomain(self):
|
|
"""Test that api subdomain is not detected as store."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "api.platform.com"}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_ignore_localhost(self):
|
|
"""Test that localhost is not detected as custom domain."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
# ========================================================================
|
|
# Store Database Lookup Tests
|
|
# ========================================================================
|
|
|
|
def test_get_store_from_custom_domain_context(self):
|
|
"""Test getting store from custom domain context."""
|
|
mock_db = Mock(spec=Session)
|
|
mock_store_domain = Mock()
|
|
mock_store = Mock()
|
|
mock_store.is_active = True
|
|
mock_store_domain.store = mock_store
|
|
|
|
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_store_domain
|
|
|
|
context = {"detection_method": "custom_domain", "domain": "customdomain1.com"}
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is mock_store
|
|
assert store.is_active is True
|
|
|
|
def test_get_store_from_custom_domain_inactive_store(self):
|
|
"""Test getting inactive store from custom domain context."""
|
|
mock_db = Mock(spec=Session)
|
|
mock_store_domain = Mock()
|
|
mock_store = Mock()
|
|
mock_store.is_active = False
|
|
mock_store_domain.store = mock_store
|
|
|
|
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_store_domain
|
|
|
|
context = {"detection_method": "custom_domain", "domain": "customdomain1.com"}
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is None
|
|
|
|
def test_get_store_from_custom_domain_not_found(self):
|
|
"""Test custom domain not found in database."""
|
|
mock_db = Mock(spec=Session)
|
|
# Ensure all query chain variants return None for .first()
|
|
# (primary StoreDomain lookup and MerchantDomain fallback)
|
|
query_mock = mock_db.query.return_value
|
|
query_mock.filter.return_value.first.return_value = None
|
|
query_mock.filter.return_value.filter.return_value.first.return_value = None
|
|
query_mock.filter.return_value.filter.return_value.filter.return_value.first.return_value = None
|
|
query_mock.filter.return_value.order_by.return_value.first.return_value = None
|
|
|
|
context = {"detection_method": "custom_domain", "domain": "nonexistent.com"}
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is None
|
|
|
|
def test_get_store_from_subdomain_context(self):
|
|
"""Test getting store from subdomain context."""
|
|
mock_db = Mock(spec=Session)
|
|
mock_store = Mock()
|
|
mock_store.is_active = True
|
|
|
|
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_store
|
|
|
|
context = {"detection_method": "subdomain", "subdomain": "store1"}
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is mock_store
|
|
|
|
def test_get_store_from_path_context(self):
|
|
"""Test getting store from path context."""
|
|
mock_db = Mock(spec=Session)
|
|
mock_store = Mock()
|
|
mock_store.is_active = True
|
|
|
|
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_store
|
|
|
|
context = {"detection_method": "path", "subdomain": "store1"}
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is mock_store
|
|
|
|
def test_get_store_with_no_context(self):
|
|
"""Test getting store with no context."""
|
|
mock_db = Mock(spec=Session)
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, None)
|
|
|
|
assert store is None
|
|
|
|
def test_get_store_subdomain_case_insensitive(self):
|
|
"""Test subdomain lookup is case-insensitive."""
|
|
mock_db = Mock(spec=Session)
|
|
mock_store = Mock()
|
|
mock_store.is_active = True
|
|
|
|
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_store
|
|
|
|
context = {"detection_method": "subdomain", "subdomain": "STORE1"} # Uppercase
|
|
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is mock_store
|
|
|
|
# ========================================================================
|
|
# Path Extraction Tests
|
|
# ========================================================================
|
|
|
|
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/storefront/products")
|
|
|
|
store_context = {"detection_method": "path", "path_prefix": "/store/store1"}
|
|
|
|
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
|
|
|
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/storefront/products")
|
|
|
|
store_context = {"detection_method": "path", "path_prefix": "/stores/store1"}
|
|
|
|
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
|
|
|
assert clean_path == "/storefront/products"
|
|
|
|
def test_extract_clean_path_root(self):
|
|
"""Test extracting clean path when result is empty (should return /)."""
|
|
request = Mock(spec=Request)
|
|
request.url = Mock(path="/store/store1")
|
|
|
|
store_context = {"detection_method": "path", "path_prefix": "/store/store1"}
|
|
|
|
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
|
|
|
assert clean_path == "/"
|
|
|
|
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="/storefront/products")
|
|
|
|
store_context = {"detection_method": "subdomain", "subdomain": "store1"}
|
|
|
|
clean_path = StoreContextManager.extract_clean_path(request, store_context)
|
|
|
|
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="/storefront/products")
|
|
|
|
clean_path = StoreContextManager.extract_clean_path(request, None)
|
|
|
|
assert clean_path == "/storefront/products"
|
|
|
|
# ========================================================================
|
|
# Request Type Detection Tests
|
|
# ========================================================================
|
|
|
|
def test_is_admin_request_admin_subdomain(self):
|
|
"""Test admin request detection from subdomain."""
|
|
assert FrontendDetector.is_admin("admin.platform.com", "/dashboard") is True
|
|
|
|
def test_is_admin_request_admin_path(self):
|
|
"""Test admin request detection from path."""
|
|
assert FrontendDetector.is_admin("localhost", "/admin/dashboard") is True
|
|
|
|
def test_is_admin_request_with_port(self):
|
|
"""Test admin request detection with port number."""
|
|
assert FrontendDetector.is_admin("admin.localhost:8000", "/dashboard") is True
|
|
|
|
def test_is_not_admin_request(self):
|
|
"""Test non-admin request."""
|
|
assert FrontendDetector.is_admin("store1.platform.com", "/storefront") is False
|
|
|
|
def test_is_api_request(self):
|
|
"""Test API request detection."""
|
|
request = Mock(spec=Request)
|
|
request.url = Mock(path="/api/v1/stores")
|
|
|
|
assert StoreContextManager.is_api_request(request) is True
|
|
|
|
def test_is_not_api_request(self):
|
|
"""Test non-API request."""
|
|
request = Mock(spec=Request)
|
|
request.url = Mock(path="/storefront/products")
|
|
|
|
assert StoreContextManager.is_api_request(request) is False
|
|
|
|
# ========================================================================
|
|
# Extract Store From Referer Tests
|
|
# ========================================================================
|
|
|
|
def test_extract_store_from_referer_path_stores(self):
|
|
"""Test extracting store from referer with /stores/ path."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {
|
|
"referer": "http://localhost:8000/stores/orion/storefront/products"
|
|
}
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is not None
|
|
assert context["subdomain"] == "orion"
|
|
assert context["detection_method"] == "path"
|
|
assert context["path_prefix"] == "/stores/orion"
|
|
assert context["full_prefix"] == "/stores/"
|
|
|
|
def test_extract_store_from_referer_path_store(self):
|
|
"""Test extracting store from referer with /store/ path."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {
|
|
"referer": "http://localhost:8000/store/myshop/storefront/products"
|
|
}
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is not None
|
|
assert context["subdomain"] == "myshop"
|
|
assert context["detection_method"] == "path"
|
|
assert context["path_prefix"] == "/store/myshop"
|
|
assert context["full_prefix"] == "/store/"
|
|
|
|
def test_extract_store_from_referer_subdomain(self):
|
|
"""Test extracting store from referer with subdomain."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"referer": "http://orion.platform.com/storefront/products"}
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is not None
|
|
assert context["subdomain"] == "orion"
|
|
assert context["detection_method"] == "referer_subdomain"
|
|
assert context["host"] == "orion.platform.com"
|
|
|
|
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/storefront/products"}
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is not None
|
|
assert context["domain"] == "my-custom-shop.com"
|
|
assert context["detection_method"] == "referer_custom_domain"
|
|
assert context["host"] == "my-custom-shop.com"
|
|
|
|
def test_extract_store_from_referer_no_header(self):
|
|
"""Test extracting store when no referer header present."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {}
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is None
|
|
|
|
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/storefront"}
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is not None
|
|
assert context["subdomain"] == "testshop"
|
|
|
|
def test_extract_store_from_referer_ignores_admin_subdomain(self):
|
|
"""Test that admin subdomain is not extracted from referer."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"referer": "http://admin.platform.com/dashboard"}
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
# admin subdomain should not be detected as store
|
|
assert context is None
|
|
|
|
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/storefront"}
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is None
|
|
|
|
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/storefront"}
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.extract_store_from_referer(request)
|
|
|
|
assert context is None
|
|
|
|
# ========================================================================
|
|
# Static File Detection Tests
|
|
# ========================================================================
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"/static/css/style.css",
|
|
"/static/js/app.js",
|
|
"/media/images/product.png",
|
|
"/assets/logo.svg",
|
|
"/.well-known/security.txt",
|
|
"/favicon.ico",
|
|
"/image.jpg",
|
|
"/style.css",
|
|
"/app.webmanifest",
|
|
"/static/", # Path starting with /static/ but no extension
|
|
"/media/uploads", # Path starting with /media/ but no extension
|
|
"/subfolder/favicon.ico", # favicon.ico in subfolder
|
|
"/favicon.ico.bak", # Contains favicon.ico but doesn't end with static extension (hits line 226)
|
|
],
|
|
)
|
|
def test_is_static_file_request(self, path):
|
|
"""Test static file detection for various paths and extensions."""
|
|
request = Mock(spec=Request)
|
|
request.url = Mock(path=path)
|
|
|
|
assert StoreContextManager.is_static_file_request(request) is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"/storefront/products",
|
|
"/admin/dashboard",
|
|
"/api/stores",
|
|
"/about",
|
|
],
|
|
)
|
|
def test_is_not_static_file_request(self, path):
|
|
"""Test non-static file paths."""
|
|
request = Mock(spec=Request)
|
|
request.url = Mock(path=path)
|
|
|
|
assert StoreContextManager.is_static_file_request(request) is False
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.stores
|
|
class TestStoreContextMiddleware:
|
|
"""Test suite for StoreContextMiddleware."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_skips_admin_request(self):
|
|
"""Test middleware skips store detection for admin requests."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "admin.platform.com"}
|
|
request.url = Mock(path="/admin/dashboard")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
with patch.object(FrontendDetector, "is_admin", return_value=True):
|
|
await middleware.dispatch(request, call_next)
|
|
|
|
assert request.state.store is None
|
|
assert request.state.store_context is None
|
|
assert request.state.clean_path == "/admin/dashboard"
|
|
call_next.assert_called_once_with(request)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_skips_api_request(self):
|
|
"""Test middleware skips store detection for API requests."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/api/v1/stores")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
with patch.object(StoreContextManager, "is_api_request", return_value=True):
|
|
await middleware.dispatch(request, call_next)
|
|
|
|
assert request.state.store is None
|
|
assert request.state.store_context is None
|
|
call_next.assert_called_once_with(request)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_skips_static_file_request(self):
|
|
"""Test middleware skips store detection for static files."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/static/css/style.css")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
with patch.object(
|
|
StoreContextManager, "is_static_file_request", return_value=True
|
|
):
|
|
await middleware.dispatch(request, call_next)
|
|
|
|
assert request.state.store is None
|
|
call_next.assert_called_once_with(request)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_detects_and_sets_store(self):
|
|
"""Test middleware successfully detects and sets store."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "store1.platform.com"}
|
|
request.url = Mock(path="/storefront/products")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
mock_store = Mock()
|
|
mock_store.id = 1
|
|
mock_store.name = "Test Store"
|
|
mock_store.subdomain = "store1"
|
|
|
|
store_context = {"detection_method": "subdomain", "subdomain": "store1"}
|
|
|
|
mock_db = MagicMock()
|
|
|
|
with (
|
|
patch.object(
|
|
StoreContextManager,
|
|
"detect_store_context",
|
|
return_value=store_context,
|
|
),
|
|
patch.object(
|
|
StoreContextManager,
|
|
"get_store_from_context",
|
|
return_value=mock_store,
|
|
),
|
|
patch.object(
|
|
StoreContextManager,
|
|
"extract_clean_path",
|
|
return_value="/storefront/products",
|
|
),
|
|
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 == "/storefront/products"
|
|
call_next.assert_called_once_with(request)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_store_not_found(self):
|
|
"""Test middleware when store context detected but store not in database."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "nonexistent.platform.com"}
|
|
request.url = Mock(path="/storefront")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
store_context = {"detection_method": "subdomain", "subdomain": "nonexistent"}
|
|
|
|
mock_db = MagicMock()
|
|
|
|
with (
|
|
patch.object(
|
|
StoreContextManager,
|
|
"detect_store_context",
|
|
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 == "/storefront"
|
|
call_next.assert_called_once_with(request)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_middleware_no_store_context(self):
|
|
"""Test middleware when no store context detected."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/random/path")
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
with patch.object(
|
|
StoreContextManager, "detect_store_context", 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 == "/random/path"
|
|
call_next.assert_called_once_with(request)
|
|
|
|
# ========================================================================
|
|
# System Path Skipping Tests
|
|
# ========================================================================
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"path",
|
|
[
|
|
"/",
|
|
"/health",
|
|
"/docs",
|
|
"/redoc",
|
|
"/openapi.json",
|
|
],
|
|
)
|
|
async def test_middleware_skips_system_paths(self, path):
|
|
"""Test middleware skips store detection for system paths."""
|
|
middleware = StoreContextMiddleware(app=None)
|
|
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path=path)
|
|
request.state = Mock()
|
|
|
|
call_next = AsyncMock(return_value=Mock())
|
|
|
|
with (
|
|
patch.object(FrontendDetector, "is_admin", return_value=False),
|
|
patch.object(
|
|
StoreContextManager, "is_static_file_request", return_value=False
|
|
),
|
|
):
|
|
await middleware.dispatch(request, call_next)
|
|
|
|
assert request.state.store is None
|
|
assert request.state.store_context is None
|
|
assert request.state.clean_path == path
|
|
call_next.assert_called_once_with(request)
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.stores
|
|
class TestHelperFunctions:
|
|
"""Test suite for helper functions."""
|
|
|
|
def test_get_current_store_exists(self):
|
|
"""Test getting current store when it exists."""
|
|
request = Mock(spec=Request)
|
|
mock_store = Mock()
|
|
request.state.store = mock_store
|
|
|
|
store = get_current_store(request)
|
|
|
|
assert store is mock_store
|
|
|
|
def test_get_current_store_not_exists(self):
|
|
"""Test getting current store when it doesn't exist."""
|
|
request = Mock(spec=Request)
|
|
request.state = Mock(spec=[]) # store attribute doesn't exist
|
|
|
|
store = get_current_store(request)
|
|
|
|
assert store is None
|
|
|
|
def test_require_store_context_success(self):
|
|
"""Test require_store_context dependency with store present."""
|
|
request = Mock(spec=Request)
|
|
mock_store = Mock()
|
|
request.state.store = mock_store
|
|
|
|
dependency = require_store_context()
|
|
result = dependency(request)
|
|
|
|
assert result is mock_store
|
|
|
|
def test_require_store_context_failure(self):
|
|
"""Test require_store_context dependency raises StoreNotFoundException when no store."""
|
|
request = Mock(spec=Request)
|
|
request.state.store = None
|
|
|
|
dependency = require_store_context()
|
|
|
|
with pytest.raises(StoreNotFoundException) as exc_info:
|
|
dependency(request)
|
|
|
|
assert exc_info.value.status_code == 404
|
|
assert "Store" in exc_info.value.message
|
|
assert "not found" in exc_info.value.message
|
|
|
|
|
|
@pytest.mark.unit
|
|
@pytest.mark.stores
|
|
class TestEdgeCases:
|
|
"""Test suite for edge cases and error scenarios."""
|
|
|
|
def test_detect_store_context_empty_host(self):
|
|
"""Test store detection with empty host header."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": ""}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_detect_store_context_missing_host(self):
|
|
"""Test store detection with missing host header."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {}
|
|
request.url = Mock(path="/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = None
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is None
|
|
|
|
def test_detect_store_path_with_trailing_slash(self):
|
|
"""Test path detection with trailing slash."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/store/store1/")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = "/store/store1/"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "path"
|
|
assert context["subdomain"] == "store1"
|
|
|
|
def test_detect_store_path_without_trailing_slash(self):
|
|
"""Test path detection without trailing slash."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "localhost"}
|
|
request.url = Mock(path="/store/store1")
|
|
request.state = Mock()
|
|
request.state.platform_clean_path = "/store/store1"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
assert context["detection_method"] == "path"
|
|
assert context["subdomain"] == "store1"
|
|
|
|
def test_detect_store_complex_subdomain(self):
|
|
"""Test detection with multiple subdomain levels."""
|
|
request = Mock(spec=Request)
|
|
request.headers = {"host": "shop.store1.platform.com"}
|
|
request.url = Mock(path="/")
|
|
|
|
with patch("middleware.store_context.settings") as mock_settings:
|
|
mock_settings.platform_domain = "platform.com"
|
|
|
|
context = StoreContextManager.detect_store_context(request)
|
|
|
|
assert context is not None
|
|
# Should detect 'shop' as subdomain since it's the first part
|
|
assert context["detection_method"] == "subdomain"
|
|
assert context["subdomain"] == "shop"
|
|
|
|
def test_get_store_logs_warning_when_not_found_subdomain(self):
|
|
"""Test that warning is logged when store is not found by subdomain."""
|
|
mock_db = Mock()
|
|
# Mock the complete query chain properly - need to mock filter twice and then first
|
|
mock_query = mock_db.query.return_value
|
|
mock_filter1 = mock_query.filter.return_value
|
|
mock_filter2 = mock_filter1.filter.return_value
|
|
mock_filter2.first.return_value = None
|
|
|
|
context = {"subdomain": "nonexistent", "detection_method": "subdomain"}
|
|
|
|
with patch("middleware.store_context.logger") as mock_logger:
|
|
store = StoreContextManager.get_store_from_context(mock_db, context)
|
|
|
|
assert store is None
|
|
# Verify warning was logged
|
|
mock_logger.warning.assert_called()
|
|
warning_message = str(mock_logger.warning.call_args)
|
|
assert (
|
|
"No active store found for subdomain" in warning_message
|
|
and "nonexistent" in warning_message
|
|
)
|