feat: complete dynamic menu system across all frontends
All checks were successful
CI / ruff (push) Successful in 11s
CI / pytest (push) Successful in 44m40s
CI / validate (push) Successful in 22s
CI / dependency-scanning (push) Successful in 28s
CI / docs (push) Successful in 39s
CI / deploy (push) Successful in 49s

- Add "Merchant Frontend" tab to admin menu-config page
- Merchant render endpoint now respects AdminMenuConfig visibility
  via get_merchant_primary_platform_id() platform resolution
- New store menu render endpoint (GET /store/core/menu/render/store)
  with platform-scoped visibility and store_code interpolation
- Store sidebar migrated from hardcoded Jinja2 macros to dynamic
  Alpine.js x-for rendering with loading skeleton and fallback
- Store init-alpine.js: add loadMenuConfig(), expandSectionForCurrentPage()
- Include store page route fixes, login template updates, and tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 02:14:42 +01:00
parent be248222bc
commit 506171503d
14 changed files with 1364 additions and 158 deletions

View File

@@ -4,7 +4,6 @@ Marketplace Store Page Routes (HTML rendering).
Store pages for marketplace management:
- Onboarding wizard
- Dashboard
- Marketplace imports
- Letzshop integration
"""
@@ -16,7 +15,9 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_store_from_cookie_or_header, get_db
from app.modules.core.utils.page_context import get_store_context
from app.modules.marketplace.services.onboarding_service import OnboardingService
from app.modules.service import module_service
from app.modules.tenancy.models import User
from app.modules.tenancy.models.store_platform import StorePlatform
from app.templates_config import templates
router = APIRouter()
@@ -39,14 +40,27 @@ async def store_onboarding_page(
"""
Render store onboarding wizard.
Mandatory 4-step wizard that must be completed before accessing dashboard:
4-step wizard for marketplace setup:
1. Merchant Profile Setup
2. Letzshop API Configuration
3. Product & Order Import Configuration
4. Order Sync (historical import)
If onboarding is already completed, redirects to dashboard.
If marketplace module is not enabled for this store's platform,
redirects to dashboard. If onboarding is already completed,
redirects to dashboard.
"""
sp = (
db.query(StorePlatform)
.filter(StorePlatform.store_id == current_user.token_store_id)
.first()
)
if not sp or not module_service.is_module_enabled(db, sp.platform_id, "marketplace"):
return RedirectResponse(
url=f"/store/{store_code}/dashboard",
status_code=302,
)
onboarding_service = OnboardingService(db)
if onboarding_service.is_completed(current_user.token_store_id):
return RedirectResponse(
@@ -60,44 +74,6 @@ async def store_onboarding_page(
)
# ============================================================================
# STORE DASHBOARD
# ============================================================================
@router.get(
"/{store_code}/dashboard", response_class=HTMLResponse, include_in_schema=False
)
async def store_dashboard_page(
request: Request,
store_code: str = Path(..., description="Store code"),
current_user: User = Depends(get_current_store_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
Render store dashboard.
Redirects to onboarding if not completed.
JavaScript will:
- Load store info via API
- Load dashboard stats via API
- Load recent orders via API
- Handle all interactivity
"""
onboarding_service = OnboardingService(db)
if not onboarding_service.is_completed(current_user.token_store_id):
return RedirectResponse(
url=f"/store/{store_code}/onboarding",
status_code=302,
)
return templates.TemplateResponse(
"core/store/dashboard.html",
get_store_context(request, db, current_user, store_code),
)
# ============================================================================
# MARKETPLACE IMPORTS
# ============================================================================
@@ -115,7 +91,15 @@ async def store_marketplace_page(
"""
Render marketplace import page.
JavaScript loads import jobs and products via API.
Redirects to onboarding if not completed.
"""
onboarding_service = OnboardingService(db)
if not onboarding_service.is_completed(current_user.token_store_id):
return RedirectResponse(
url=f"/store/{store_code}/onboarding",
status_code=302,
)
return templates.TemplateResponse(
"marketplace/store/marketplace.html",
get_store_context(request, db, current_user, store_code),
@@ -139,7 +123,15 @@ async def store_letzshop_page(
"""
Render Letzshop integration page.
JavaScript loads orders, credentials status, and handles fulfillment operations.
Redirects to onboarding if not completed.
"""
onboarding_service = OnboardingService(db)
if not onboarding_service.is_completed(current_user.token_store_id):
return RedirectResponse(
url=f"/store/{store_code}/onboarding",
status_code=302,
)
return templates.TemplateResponse(
"marketplace/store/letzshop.html",
get_store_context(request, db, current_user, store_code),

View File

@@ -0,0 +1,388 @@
# app/modules/marketplace/tests/integration/test_store_page_routes.py
"""
Integration tests for marketplace store page routes.
Tests the onboarding, marketplace, and letzshop page routes at:
GET /store/{store_code}/onboarding
GET /store/{store_code}/marketplace
GET /store/{store_code}/letzshop
Verifies:
- Onboarding page redirects to dashboard on non-marketplace platforms
- Onboarding page redirects to dashboard when already completed
- Onboarding page renders when marketplace enabled and not completed
- Marketplace/Letzshop pages redirect to onboarding when not completed
- Marketplace/Letzshop pages render when onboarding is completed
"""
import uuid
from datetime import UTC, datetime
import pytest
from app.api.deps import get_current_store_from_cookie_or_header
from app.modules.marketplace.models import OnboardingStatus, StoreOnboarding
from app.modules.tenancy.models import Merchant, Platform, Store, User
from app.modules.tenancy.models.platform_module import PlatformModule
from app.modules.tenancy.models.store_platform import StorePlatform
from main import app
from models.schema.auth import UserContext
# ============================================================================
# Fixtures
# ============================================================================
@pytest.fixture
def mp_admin(db):
"""Create an admin user for enabling modules."""
from middleware.auth import AuthManager
auth = AuthManager()
user = User(
email=f"mpadmin_{uuid.uuid4().hex[:8]}@test.com",
username=f"mpadmin_{uuid.uuid4().hex[:8]}",
hashed_password=auth.hash_password("pass123"),
role="super_admin",
is_active=True,
)
db.add(user)
db.commit()
db.refresh(user)
return user
@pytest.fixture
def mp_owner(db):
"""Create a store owner user."""
from middleware.auth import AuthManager
auth = AuthManager()
user = User(
email=f"mpowner_{uuid.uuid4().hex[:8]}@test.com",
username=f"mpowner_{uuid.uuid4().hex[:8]}",
hashed_password=auth.hash_password("pass123"),
role="merchant_owner",
is_active=True,
)
db.add(user)
db.commit()
db.refresh(user)
return user
@pytest.fixture
def mp_platform_no_marketplace(db):
"""Create a platform without marketplace module enabled."""
platform = Platform(
code=f"mpnm_{uuid.uuid4().hex[:8]}",
name="No Marketplace Platform",
is_active=True,
)
db.add(platform)
db.commit()
db.refresh(platform)
return platform
@pytest.fixture
def mp_platform_with_marketplace(db, mp_admin):
"""Create a platform with marketplace module enabled."""
platform = Platform(
code=f"mpmk_{uuid.uuid4().hex[:8]}",
name="Marketplace Platform",
is_active=True,
)
db.add(platform)
db.flush()
pm = PlatformModule(
platform_id=platform.id,
module_code="marketplace",
is_enabled=True,
enabled_at=datetime.now(UTC),
enabled_by_user_id=mp_admin.id,
config={},
)
db.add(pm)
db.commit()
db.refresh(platform)
return platform
@pytest.fixture
def mp_merchant(db, mp_owner):
"""Create a merchant."""
merchant = Merchant(
name="MP Test Merchant",
owner_user_id=mp_owner.id,
contact_email=mp_owner.email,
is_active=True,
is_verified=True,
)
db.add(merchant)
db.commit()
db.refresh(merchant)
return merchant
@pytest.fixture
def mp_store(db, mp_merchant):
"""Create a store."""
uid = uuid.uuid4().hex[:8]
store = Store(
merchant_id=mp_merchant.id,
store_code=f"MPSTORE_{uid.upper()}",
subdomain=f"mpstore{uid.lower()}",
name="MP Test Store",
is_active=True,
is_verified=True,
)
db.add(store)
db.commit()
db.refresh(store)
return store
@pytest.fixture
def mp_store_no_marketplace(db, mp_store, mp_platform_no_marketplace):
"""Link store to a platform without marketplace."""
sp = StorePlatform(
store_id=mp_store.id,
platform_id=mp_platform_no_marketplace.id,
is_active=True,
)
db.add(sp)
db.commit()
db.refresh(sp)
return sp
@pytest.fixture
def mp_store_with_marketplace(db, mp_store, mp_platform_with_marketplace):
"""Link store to a platform with marketplace enabled."""
sp = StorePlatform(
store_id=mp_store.id,
platform_id=mp_platform_with_marketplace.id,
is_active=True,
)
db.add(sp)
db.commit()
db.refresh(sp)
return sp
@pytest.fixture
def mp_onboarding_not_completed(db, mp_store):
"""Create an incomplete onboarding record for the store."""
onboarding = StoreOnboarding(
store_id=mp_store.id,
status=OnboardingStatus.IN_PROGRESS.value,
)
db.add(onboarding)
db.commit()
db.refresh(onboarding)
return onboarding
@pytest.fixture
def mp_onboarding_completed(db, mp_store):
"""Create a completed onboarding record for the store."""
onboarding = StoreOnboarding(
store_id=mp_store.id,
status=OnboardingStatus.COMPLETED.value,
step_merchant_profile_completed=True,
step_letzshop_api_completed=True,
step_product_import_completed=True,
step_order_sync_completed=True,
)
db.add(onboarding)
db.commit()
db.refresh(onboarding)
return onboarding
@pytest.fixture
def mp_auth(mp_owner, mp_store):
"""Override auth dependency for store cookie/header auth."""
user_context = UserContext(
id=mp_owner.id,
email=mp_owner.email,
username=mp_owner.username,
role="merchant_owner",
is_active=True,
token_store_id=mp_store.id,
token_store_code=mp_store.store_code,
)
def _override():
return user_context
app.dependency_overrides[get_current_store_from_cookie_or_header] = _override
yield {"Authorization": "Bearer fake-token"}
app.dependency_overrides.pop(get_current_store_from_cookie_or_header, None)
# ============================================================================
# Onboarding page tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.marketplace
class TestOnboardingPageRoutes:
"""Tests for GET /store/{store_code}/onboarding."""
def test_redirects_to_dashboard_on_non_marketplace_platform(
self, client, db, mp_auth, mp_store, mp_store_no_marketplace
):
"""Onboarding page redirects to dashboard on platform without marketplace."""
response = client.get(
f"/store/{mp_store.subdomain}/onboarding",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/dashboard" in response.headers["location"]
def test_redirects_to_dashboard_when_onboarding_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_completed,
):
"""Onboarding page redirects to dashboard when already completed."""
response = client.get(
f"/store/{mp_store.subdomain}/onboarding",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/dashboard" in response.headers["location"]
def test_renders_onboarding_when_not_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_not_completed,
):
"""Onboarding page renders wizard when marketplace enabled and not completed."""
response = client.get(
f"/store/{mp_store.subdomain}/onboarding",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 200
def test_renders_onboarding_when_no_onboarding_record(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
):
"""Onboarding page renders wizard when marketplace enabled and no record exists."""
# No mp_onboarding_* fixture — is_completed() returns False for missing record
response = client.get(
f"/store/{mp_store.subdomain}/onboarding",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 200
def test_requires_auth(self, client):
"""Returns 401 without auth."""
app.dependency_overrides.pop(get_current_store_from_cookie_or_header, None)
response = client.get(
"/store/anystore/onboarding",
follow_redirects=False,
)
assert response.status_code == 401
# ============================================================================
# Marketplace page tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.marketplace
class TestMarketplacePageRoutes:
"""Tests for GET /store/{store_code}/marketplace."""
def test_redirects_to_onboarding_when_not_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_not_completed,
):
"""Marketplace page redirects to onboarding when not completed."""
response = client.get(
f"/store/{mp_store.subdomain}/marketplace",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/onboarding" in response.headers["location"]
def test_renders_when_onboarding_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_completed,
):
"""Marketplace page renders when onboarding is completed."""
response = client.get(
f"/store/{mp_store.subdomain}/marketplace",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 200
def test_redirects_when_no_onboarding_record(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
):
"""Marketplace page redirects when no onboarding record (not completed)."""
response = client.get(
f"/store/{mp_store.subdomain}/marketplace",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/onboarding" in response.headers["location"]
# ============================================================================
# Letzshop page tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.marketplace
class TestLetzshopPageRoutes:
"""Tests for GET /store/{store_code}/letzshop."""
def test_redirects_to_onboarding_when_not_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_not_completed,
):
"""Letzshop page redirects to onboarding when not completed."""
response = client.get(
f"/store/{mp_store.subdomain}/letzshop",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/onboarding" in response.headers["location"]
def test_renders_when_onboarding_completed(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
mp_onboarding_completed,
):
"""Letzshop page renders when onboarding is completed."""
response = client.get(
f"/store/{mp_store.subdomain}/letzshop",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 200
def test_redirects_when_no_onboarding_record(
self, client, db, mp_auth, mp_store, mp_store_with_marketplace,
):
"""Letzshop page redirects when no onboarding record (not completed)."""
response = client.get(
f"/store/{mp_store.subdomain}/letzshop",
headers=mp_auth,
follow_redirects=False,
)
assert response.status_code == 302
assert f"/store/{mp_store.subdomain}/onboarding" in response.headers["location"]

View File

@@ -0,0 +1,298 @@
# app/modules/marketplace/tests/unit/test_store_page_routes.py
"""
Unit tests for marketplace store page routes.
Tests the onboarding, marketplace, and letzshop page route logic:
- Onboarding page guards (marketplace module check, completion redirect)
- Marketplace page onboarding gate
- Letzshop page onboarding gate
"""
from unittest.mock import MagicMock, patch
import pytest
from fastapi import Request
from fastapi.responses import RedirectResponse
from app.modules.marketplace.routes.pages.store import (
store_letzshop_page,
store_marketplace_page,
store_onboarding_page,
)
from models.schema.auth import UserContext
def _make_user_context(store_id: int = 1, store_code: str = "teststore") -> UserContext:
"""Create a UserContext for testing."""
return UserContext(
id=1,
email="test@test.com",
username="testuser",
role="merchant_owner",
is_active=True,
token_store_id=store_id,
token_store_code=store_code,
)
def _make_request() -> MagicMock:
"""Create a mock Request."""
return MagicMock(spec=Request)
# ============================================================================
# ONBOARDING PAGE
# ============================================================================
@pytest.mark.unit
@pytest.mark.marketplace
@pytest.mark.asyncio
class TestOnboardingPageGuard:
"""Test that onboarding page redirects to dashboard when marketplace is not enabled."""
@patch("app.modules.marketplace.routes.pages.store.module_service")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_redirects_to_dashboard_when_no_store_platform(
self, mock_templates, mock_ctx, mock_module_service, db
):
"""Redirect to dashboard if store has no StorePlatform record."""
user = _make_user_context(store_id=99999)
response = await store_onboarding_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert isinstance(response, RedirectResponse)
assert response.status_code == 302
assert "/store/teststore/dashboard" in response.headers["location"]
@patch("app.modules.marketplace.routes.pages.store.module_service")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_redirects_to_dashboard_when_marketplace_not_enabled(
self, mock_templates, mock_ctx, mock_module_service, db, test_store
):
"""Redirect to dashboard if marketplace module is not enabled on platform."""
from app.modules.tenancy.models import Platform
from app.modules.tenancy.models.store_platform import StorePlatform
platform = Platform(code="nomarket", name="No Marketplace", is_active=True)
db.add(platform)
db.flush()
sp = StorePlatform(store_id=test_store.id, platform_id=platform.id, is_active=True)
db.add(sp)
db.commit()
mock_module_service.is_module_enabled.return_value = False
user = _make_user_context(store_id=test_store.id)
response = await store_onboarding_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert isinstance(response, RedirectResponse)
assert response.status_code == 302
assert "/store/teststore/dashboard" in response.headers["location"]
mock_module_service.is_module_enabled.assert_called_once_with(
db, platform.id, "marketplace"
)
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.module_service")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_redirects_to_dashboard_when_onboarding_completed(
self, mock_templates, mock_ctx, mock_module_service, mock_onboarding_cls, db, test_store
):
"""Redirect to dashboard if onboarding is already completed."""
from app.modules.tenancy.models import Platform
from app.modules.tenancy.models.store_platform import StorePlatform
platform = Platform(code="mktplace", name="Marketplace", is_active=True)
db.add(platform)
db.flush()
sp = StorePlatform(store_id=test_store.id, platform_id=platform.id, is_active=True)
db.add(sp)
db.commit()
mock_module_service.is_module_enabled.return_value = True
mock_svc = MagicMock()
mock_svc.is_completed.return_value = True
mock_onboarding_cls.return_value = mock_svc
user = _make_user_context(store_id=test_store.id)
response = await store_onboarding_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert isinstance(response, RedirectResponse)
assert response.status_code == 302
assert "/store/teststore/dashboard" in response.headers["location"]
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.module_service")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_renders_onboarding_when_marketplace_enabled_and_not_completed(
self, mock_templates, mock_ctx, mock_module_service, mock_onboarding_cls, db, test_store
):
"""Render onboarding wizard when marketplace enabled and onboarding not completed."""
from app.modules.tenancy.models import Platform
from app.modules.tenancy.models.store_platform import StorePlatform
platform = Platform(code="mktplace2", name="Marketplace 2", is_active=True)
db.add(platform)
db.flush()
sp = StorePlatform(store_id=test_store.id, platform_id=platform.id, is_active=True)
db.add(sp)
db.commit()
mock_module_service.is_module_enabled.return_value = True
mock_svc = MagicMock()
mock_svc.is_completed.return_value = False
mock_onboarding_cls.return_value = mock_svc
mock_ctx.return_value = {"request": _make_request()}
mock_templates.TemplateResponse.return_value = "rendered"
user = _make_user_context(store_id=test_store.id)
response = await store_onboarding_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert response == "rendered"
mock_templates.TemplateResponse.assert_called_once()
template_name = mock_templates.TemplateResponse.call_args[0][0]
assert template_name == "marketplace/store/onboarding.html"
# ============================================================================
# MARKETPLACE PAGE
# ============================================================================
@pytest.mark.unit
@pytest.mark.marketplace
@pytest.mark.asyncio
class TestMarketplacePageOnboardingGate:
"""Test that marketplace page redirects to onboarding when not completed."""
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_redirects_to_onboarding_when_not_completed(
self, mock_templates, mock_ctx, mock_onboarding_cls, db
):
"""Redirect to onboarding if onboarding not completed."""
mock_svc = MagicMock()
mock_svc.is_completed.return_value = False
mock_onboarding_cls.return_value = mock_svc
user = _make_user_context()
response = await store_marketplace_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert isinstance(response, RedirectResponse)
assert response.status_code == 302
assert "/store/teststore/onboarding" in response.headers["location"]
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_renders_marketplace_when_onboarding_completed(
self, mock_templates, mock_ctx, mock_onboarding_cls, db
):
"""Render marketplace page if onboarding is completed."""
mock_svc = MagicMock()
mock_svc.is_completed.return_value = True
mock_onboarding_cls.return_value = mock_svc
mock_ctx.return_value = {"request": _make_request()}
mock_templates.TemplateResponse.return_value = "rendered"
user = _make_user_context()
response = await store_marketplace_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert response == "rendered"
mock_templates.TemplateResponse.assert_called_once()
template_name = mock_templates.TemplateResponse.call_args[0][0]
assert template_name == "marketplace/store/marketplace.html"
# ============================================================================
# LETZSHOP PAGE
# ============================================================================
@pytest.mark.unit
@pytest.mark.marketplace
@pytest.mark.asyncio
class TestLetzshopPageOnboardingGate:
"""Test that letzshop page redirects to onboarding when not completed."""
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_redirects_to_onboarding_when_not_completed(
self, mock_templates, mock_ctx, mock_onboarding_cls, db
):
"""Redirect to onboarding if onboarding not completed."""
mock_svc = MagicMock()
mock_svc.is_completed.return_value = False
mock_onboarding_cls.return_value = mock_svc
user = _make_user_context()
response = await store_letzshop_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert isinstance(response, RedirectResponse)
assert response.status_code == 302
assert "/store/teststore/onboarding" in response.headers["location"]
@patch("app.modules.marketplace.routes.pages.store.OnboardingService")
@patch("app.modules.marketplace.routes.pages.store.get_store_context")
@patch("app.modules.marketplace.routes.pages.store.templates")
async def test_renders_letzshop_when_onboarding_completed(
self, mock_templates, mock_ctx, mock_onboarding_cls, db
):
"""Render letzshop page if onboarding is completed."""
mock_svc = MagicMock()
mock_svc.is_completed.return_value = True
mock_onboarding_cls.return_value = mock_svc
mock_ctx.return_value = {"request": _make_request()}
mock_templates.TemplateResponse.return_value = "rendered"
user = _make_user_context()
response = await store_letzshop_page(
request=_make_request(),
store_code="teststore",
current_user=user,
db=db,
)
assert response == "rendered"
mock_templates.TemplateResponse.assert_called_once()
template_name = mock_templates.TemplateResponse.call_args[0][0]
assert template_name == "marketplace/store/letzshop.html"