feat: production routing support for subdomain and custom domain modes
Some checks failed
CI / ruff (push) Successful in 10s
CI / pytest (push) Failing after 45m18s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 30s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

Double-mount store routes at /store/* and /store/{store_code}/* so the
same handlers work in dev path-based, prod path-based, prod subdomain,
and prod custom-domain modes.  Wire StorePlatform.custom_subdomain into
StoreContextMiddleware for per-platform subdomain overrides.  Add admin
custom-domain management UI, fix stale /shop/ reset link, add
/merchants/ to reserved paths, and server-render window.STORE_CODE for
JS that previously parsed the URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 00:15:06 +01:00
parent 6a82d7c12d
commit ce5b54f27b
36 changed files with 822 additions and 151 deletions

View File

@@ -0,0 +1,119 @@
# tests/unit/api/test_get_resolved_store_code.py
"""
Unit tests for get_resolved_store_code dependency.
Tests the store code resolution logic that supports both:
- Path-based routing: /store/{store_code}/dashboard (store_code in URL)
- Subdomain/custom domain: /store/dashboard (store resolved by middleware)
"""
from unittest.mock import MagicMock, Mock
import pytest
from fastapi import HTTPException
from app.api.deps import get_resolved_store_code
@pytest.mark.unit
@pytest.mark.stores
class TestGetResolvedStoreCode:
"""Test suite for get_resolved_store_code dependency."""
@pytest.mark.asyncio
async def test_returns_store_code_from_path_params(self):
"""When store_code is in path params (path-based routing), return it."""
request = Mock()
request.path_params = {"store_code": "ACME"}
request.state = MagicMock()
result = await get_resolved_store_code(request)
assert result == "ACME"
@pytest.mark.asyncio
async def test_returns_store_code_from_middleware_state(self):
"""When no path param but middleware resolved store, return store.store_code."""
mock_store = Mock()
mock_store.store_code = "ORION"
request = Mock()
request.path_params = {}
request.state = MagicMock()
request.state.store = mock_store
result = await get_resolved_store_code(request)
assert result == "ORION"
@pytest.mark.asyncio
async def test_path_param_takes_priority_over_middleware(self):
"""Path param should be preferred even if middleware also resolved a store."""
mock_store = Mock()
mock_store.store_code = "MIDDLEWARE_STORE"
request = Mock()
request.path_params = {"store_code": "PATH_STORE"}
request.state = MagicMock()
request.state.store = mock_store
result = await get_resolved_store_code(request)
assert result == "PATH_STORE"
@pytest.mark.asyncio
async def test_raises_404_when_no_store_found(self):
"""When neither path param nor middleware provides store, raise 404."""
request = Mock()
request.path_params = {}
request.state = MagicMock()
request.state.store = None
with pytest.raises(HTTPException) as exc_info:
await get_resolved_store_code(request)
assert exc_info.value.status_code == 404
assert "Store not found" in str(exc_info.value.detail)
@pytest.mark.asyncio
async def test_empty_string_path_param_falls_through(self):
"""Empty string path param should fall through to middleware."""
mock_store = Mock()
mock_store.store_code = "FALLBACK"
request = Mock()
request.path_params = {"store_code": ""}
request.state = MagicMock()
request.state.store = mock_store
result = await get_resolved_store_code(request)
assert result == "FALLBACK"
@pytest.mark.asyncio
async def test_no_path_params_key_falls_through(self):
"""When path_params doesn't contain store_code, fall through to middleware."""
mock_store = Mock()
mock_store.store_code = "WIDGET"
request = Mock()
request.path_params = {"order_id": "123"}
request.state = MagicMock()
request.state.store = mock_store
result = await get_resolved_store_code(request)
assert result == "WIDGET"
@pytest.mark.asyncio
async def test_raises_404_when_no_state_store_attribute(self):
"""When request.state has no store attribute at all, raise 404."""
request = Mock()
request.path_params = {}
# Simulate missing attribute
request.state = MagicMock(spec=[])
with pytest.raises(HTTPException) as exc_info:
await get_resolved_store_code(request)
assert exc_info.value.status_code == 404