Files
orion/tests/unit/middleware/test_vendor_context.py

746 lines
27 KiB
Python

# tests/unit/middleware/test_vendor_context.py
"""
Comprehensive unit tests for VendorContextMiddleware and VendorContextManager.
Tests cover:
- Vendor detection from custom domains, subdomains, and path-based routing
- Database lookup and vendor validation
- Path extraction and cleanup
- Admin and API request detection
- Static file request detection
- Edge cases and error handling
"""
import pytest
from unittest.mock import Mock, MagicMock, patch, AsyncMock
from fastapi import Request, HTTPException
from sqlalchemy.orm import Session
from middleware.vendor_context import (
VendorContextManager,
VendorContextMiddleware,
get_current_vendor,
require_vendor_context,
)
@pytest.mark.unit
@pytest.mark.vendors
class TestVendorContextManager:
"""Test suite for VendorContextManager static methods."""
# ========================================================================
# Vendor 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.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_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.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_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": "vendor1.platform.com"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "subdomain"
assert context["subdomain"] == "vendor1"
assert context["host"] == "vendor1.platform.com"
def test_detect_subdomain_with_port(self):
"""Test subdomain detection with port number."""
request = Mock(spec=Request)
request.headers = {"host": "vendor1.platform.com:8000"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "subdomain"
assert context["subdomain"] == "vendor1"
def test_detect_path_vendor_singular(self):
"""Test path-based detection with /vendor/ prefix."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/vendor/vendor1/shop")
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "path"
assert context["subdomain"] == "vendor1"
assert context["path_prefix"] == "/vendor/vendor1"
assert context["full_prefix"] == "/vendor/"
def test_detect_path_vendors_plural(self):
"""Test path-based detection with /vendors/ prefix."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/vendors/vendor1/shop")
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "path"
assert context["subdomain"] == "vendor1"
assert context["path_prefix"] == "/vendors/vendor1"
assert context["full_prefix"] == "/vendors/"
def test_detect_no_vendor_context(self):
"""Test when no vendor context can be detected."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/random/path")
context = VendorContextManager.detect_vendor_context(request)
assert context is None
def test_ignore_admin_subdomain(self):
"""Test that admin subdomain is not detected as vendor."""
request = Mock(spec=Request)
request.headers = {"host": "admin.platform.com"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_context(request)
assert context is None
def test_ignore_www_subdomain(self):
"""Test that www subdomain is not detected as vendor."""
request = Mock(spec=Request)
request.headers = {"host": "www.platform.com"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_context(request)
assert context is None
def test_ignore_api_subdomain(self):
"""Test that api subdomain is not detected as vendor."""
request = Mock(spec=Request)
request.headers = {"host": "api.platform.com"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_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="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_context(request)
assert context is None
# ========================================================================
# Vendor Database Lookup Tests
# ========================================================================
def test_get_vendor_from_custom_domain_context(self):
"""Test getting vendor from custom domain context."""
mock_db = Mock(spec=Session)
mock_vendor_domain = Mock()
mock_vendor = Mock()
mock_vendor.is_active = True
mock_vendor_domain.vendor = mock_vendor
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor_domain
context = {
"detection_method": "custom_domain",
"domain": "customdomain1.com"
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is mock_vendor
assert vendor.is_active is True
def test_get_vendor_from_custom_domain_inactive_vendor(self):
"""Test getting inactive vendor from custom domain context."""
mock_db = Mock(spec=Session)
mock_vendor_domain = Mock()
mock_vendor = Mock()
mock_vendor.is_active = False
mock_vendor_domain.vendor = mock_vendor
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor_domain
context = {
"detection_method": "custom_domain",
"domain": "customdomain1.com"
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is None
def test_get_vendor_from_custom_domain_not_found(self):
"""Test custom domain not found in database."""
mock_db = Mock(spec=Session)
mock_db.query.return_value.filter.return_value.filter.return_value.filter.return_value.first.return_value = None
context = {
"detection_method": "custom_domain",
"domain": "nonexistent.com"
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is None
def test_get_vendor_from_subdomain_context(self):
"""Test getting vendor from subdomain context."""
mock_db = Mock(spec=Session)
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {
"detection_method": "subdomain",
"subdomain": "vendor1"
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is mock_vendor
def test_get_vendor_from_path_context(self):
"""Test getting vendor from path context."""
mock_db = Mock(spec=Session)
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {
"detection_method": "path",
"subdomain": "vendor1"
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is mock_vendor
def test_get_vendor_with_no_context(self):
"""Test getting vendor with no context."""
mock_db = Mock(spec=Session)
vendor = VendorContextManager.get_vendor_from_context(mock_db, None)
assert vendor is None
def test_get_vendor_subdomain_case_insensitive(self):
"""Test subdomain lookup is case-insensitive."""
mock_db = Mock(spec=Session)
mock_vendor = Mock()
mock_vendor.is_active = True
mock_db.query.return_value.filter.return_value.filter.return_value.first.return_value = mock_vendor
context = {
"detection_method": "subdomain",
"subdomain": "VENDOR1" # Uppercase
}
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is mock_vendor
# ========================================================================
# Path Extraction Tests
# ========================================================================
def test_extract_clean_path_from_vendor_path(self):
"""Test extracting clean path from /vendor/ prefix."""
request = Mock(spec=Request)
request.url = Mock(path="/vendor/vendor1/shop/products")
vendor_context = {
"detection_method": "path",
"path_prefix": "/vendor/vendor1"
}
clean_path = VendorContextManager.extract_clean_path(request, vendor_context)
assert clean_path == "/shop/products"
def test_extract_clean_path_from_vendors_path(self):
"""Test extracting clean path from /vendors/ prefix."""
request = Mock(spec=Request)
request.url = Mock(path="/vendors/vendor1/shop/products")
vendor_context = {
"detection_method": "path",
"path_prefix": "/vendors/vendor1"
}
clean_path = VendorContextManager.extract_clean_path(request, vendor_context)
assert clean_path == "/shop/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="/vendor/vendor1")
vendor_context = {
"detection_method": "path",
"path_prefix": "/vendor/vendor1"
}
clean_path = VendorContextManager.extract_clean_path(request, vendor_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="/shop/products")
vendor_context = {
"detection_method": "subdomain",
"subdomain": "vendor1"
}
clean_path = VendorContextManager.extract_clean_path(request, vendor_context)
assert clean_path == "/shop/products"
def test_extract_clean_path_no_context(self):
"""Test extracting clean path with no vendor context."""
request = Mock(spec=Request)
request.url = Mock(path="/shop/products")
clean_path = VendorContextManager.extract_clean_path(request, None)
assert clean_path == "/shop/products"
# ========================================================================
# Request Type Detection Tests
# ========================================================================
def test_is_admin_request_admin_subdomain(self):
"""Test admin request detection from subdomain."""
request = Mock(spec=Request)
request.headers = {"host": "admin.platform.com"}
request.url = Mock(path="/dashboard")
assert VendorContextManager.is_admin_request(request) is True
def test_is_admin_request_admin_path(self):
"""Test admin request detection from path."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/admin/dashboard")
assert VendorContextManager.is_admin_request(request) is True
def test_is_admin_request_with_port(self):
"""Test admin request detection with port number."""
request = Mock(spec=Request)
request.headers = {"host": "admin.localhost:8000"}
request.url = Mock(path="/dashboard")
assert VendorContextManager.is_admin_request(request) is True
def test_is_not_admin_request(self):
"""Test non-admin request."""
request = Mock(spec=Request)
request.headers = {"host": "vendor1.platform.com"}
request.url = Mock(path="/shop")
assert VendorContextManager.is_admin_request(request) is False
def test_is_api_request(self):
"""Test API request detection."""
request = Mock(spec=Request)
request.url = Mock(path="/api/v1/vendors")
assert VendorContextManager.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="/shop/products")
assert VendorContextManager.is_api_request(request) is False
# ========================================================================
# 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 VendorContextManager.is_static_file_request(request) is True
@pytest.mark.parametrize("path", [
"/shop/products",
"/admin/dashboard",
"/api/vendors",
"/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 VendorContextManager.is_static_file_request(request) is False
@pytest.mark.unit
@pytest.mark.vendors
class TestVendorContextMiddleware:
"""Test suite for VendorContextMiddleware."""
@pytest.mark.asyncio
async def test_middleware_skips_admin_request(self):
"""Test middleware skips vendor detection for admin requests."""
middleware = VendorContextMiddleware(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(VendorContextManager, 'is_admin_request', return_value=True):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None
assert request.state.vendor_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 vendor detection for API requests."""
middleware = VendorContextMiddleware(app=None)
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/api/v1/vendors")
request.state = Mock()
call_next = AsyncMock(return_value=Mock())
with patch.object(VendorContextManager, 'is_api_request', return_value=True):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None
assert request.state.vendor_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 vendor detection for static files."""
middleware = VendorContextMiddleware(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(VendorContextManager, 'is_static_file_request', return_value=True):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None
call_next.assert_called_once_with(request)
@pytest.mark.asyncio
async def test_middleware_detects_and_sets_vendor(self):
"""Test middleware successfully detects and sets vendor."""
middleware = VendorContextMiddleware(app=None)
request = Mock(spec=Request)
request.headers = {"host": "vendor1.platform.com"}
request.url = Mock(path="/shop/products")
request.state = Mock()
call_next = AsyncMock(return_value=Mock())
mock_vendor = Mock()
mock_vendor.id = 1
mock_vendor.name = "Test Vendor"
mock_vendor.subdomain = "vendor1"
vendor_context = {
"detection_method": "subdomain",
"subdomain": "vendor1"
}
mock_db = MagicMock()
with patch.object(VendorContextManager, 'detect_vendor_context', return_value=vendor_context), \
patch.object(VendorContextManager, 'get_vendor_from_context', return_value=mock_vendor), \
patch.object(VendorContextManager, 'extract_clean_path', return_value="/shop/products"), \
patch('middleware.vendor_context.get_db', return_value=iter([mock_db])):
await middleware.dispatch(request, call_next)
assert request.state.vendor is mock_vendor
assert request.state.vendor_context == vendor_context
assert request.state.clean_path == "/shop/products"
call_next.assert_called_once_with(request)
@pytest.mark.asyncio
async def test_middleware_vendor_not_found(self):
"""Test middleware when vendor context detected but vendor not in database."""
middleware = VendorContextMiddleware(app=None)
request = Mock(spec=Request)
request.headers = {"host": "nonexistent.platform.com"}
request.url = Mock(path="/shop")
request.state = Mock()
call_next = AsyncMock(return_value=Mock())
vendor_context = {
"detection_method": "subdomain",
"subdomain": "nonexistent"
}
mock_db = MagicMock()
with patch.object(VendorContextManager, 'detect_vendor_context', return_value=vendor_context), \
patch.object(VendorContextManager, 'get_vendor_from_context', return_value=None), \
patch('middleware.vendor_context.get_db', return_value=iter([mock_db])):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None
assert request.state.vendor_context == vendor_context
assert request.state.clean_path == "/shop"
call_next.assert_called_once_with(request)
@pytest.mark.asyncio
async def test_middleware_no_vendor_context(self):
"""Test middleware when no vendor context detected."""
middleware = VendorContextMiddleware(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(VendorContextManager, 'detect_vendor_context', return_value=None):
await middleware.dispatch(request, call_next)
assert request.state.vendor is None
assert request.state.vendor_context is None
assert request.state.clean_path == "/random/path"
call_next.assert_called_once_with(request)
@pytest.mark.unit
@pytest.mark.vendors
class TestHelperFunctions:
"""Test suite for helper functions."""
def test_get_current_vendor_exists(self):
"""Test getting current vendor when it exists."""
request = Mock(spec=Request)
mock_vendor = Mock()
request.state.vendor = mock_vendor
vendor = get_current_vendor(request)
assert vendor is mock_vendor
def test_get_current_vendor_not_exists(self):
"""Test getting current vendor when it doesn't exist."""
request = Mock(spec=Request)
request.state = Mock(spec=[]) # vendor attribute doesn't exist
vendor = get_current_vendor(request)
assert vendor is None
def test_require_vendor_context_success(self):
"""Test require_vendor_context dependency with vendor present."""
request = Mock(spec=Request)
mock_vendor = Mock()
request.state.vendor = mock_vendor
dependency = require_vendor_context()
result = dependency(request)
assert result is mock_vendor
def test_require_vendor_context_failure(self):
"""Test require_vendor_context dependency raises HTTPException when no vendor."""
request = Mock(spec=Request)
request.state.vendor = None
dependency = require_vendor_context()
with pytest.raises(HTTPException) as exc_info:
dependency(request)
assert exc_info.value.status_code == 404
assert "Vendor not found" in exc_info.value.detail
@pytest.mark.unit
@pytest.mark.vendors
class TestEdgeCases:
"""Test suite for edge cases and error scenarios."""
def test_detect_vendor_context_empty_host(self):
"""Test vendor detection with empty host header."""
request = Mock(spec=Request)
request.headers = {"host": ""}
request.url = Mock(path="/")
context = VendorContextManager.detect_vendor_context(request)
assert context is None
def test_detect_vendor_context_missing_host(self):
"""Test vendor detection with missing host header."""
request = Mock(spec=Request)
request.headers = {}
request.url = Mock(path="/")
context = VendorContextManager.detect_vendor_context(request)
assert context is None
def test_detect_vendor_path_with_trailing_slash(self):
"""Test path detection with trailing slash."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/vendor/vendor1/")
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "path"
assert context["subdomain"] == "vendor1"
def test_detect_vendor_path_without_trailing_slash(self):
"""Test path detection without trailing slash."""
request = Mock(spec=Request)
request.headers = {"host": "localhost"}
request.url = Mock(path="/vendor/vendor1")
context = VendorContextManager.detect_vendor_context(request)
assert context is not None
assert context["detection_method"] == "path"
assert context["subdomain"] == "vendor1"
def test_detect_vendor_complex_subdomain(self):
"""Test detection with multiple subdomain levels."""
request = Mock(spec=Request)
request.headers = {"host": "shop.vendor1.platform.com"}
request.url = Mock(path="/")
with patch('middleware.vendor_context.settings') as mock_settings:
mock_settings.platform_domain = "platform.com"
context = VendorContextManager.detect_vendor_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_vendor_logs_warning_when_not_found_subdomain(self):
"""Test that warning is logged when vendor 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.vendor_context.logger') as mock_logger:
vendor = VendorContextManager.get_vendor_from_context(mock_db, context)
assert vendor is None
# Verify warning was logged
mock_logger.warning.assert_called()
warning_message = str(mock_logger.warning.call_args)
assert "No active vendor found for subdomain" in warning_message and "nonexistent" in warning_message