feat: complete dynamic menu system across all frontends
All checks were successful
All checks were successful
- 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:
@@ -0,0 +1,200 @@
|
||||
# app/modules/core/tests/integration/test_store_dashboard_routes.py
|
||||
"""
|
||||
Integration tests for store dashboard page route.
|
||||
|
||||
Tests the store dashboard page at:
|
||||
GET /store/{store_code}/dashboard
|
||||
|
||||
Verifies:
|
||||
- Dashboard renders without any onboarding check
|
||||
- Dashboard requires authentication
|
||||
- Dashboard is served from the core module (no marketplace dependency)
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.deps import get_current_store_from_cookie_or_header
|
||||
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||
from main import app
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
# ============================================================================
|
||||
# Fixtures
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sd_owner(db):
|
||||
"""Create a store owner user."""
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
auth = AuthManager()
|
||||
user = User(
|
||||
email=f"sdowner_{uuid.uuid4().hex[:8]}@test.com",
|
||||
username=f"sdowner_{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 sd_platform(db):
|
||||
"""Create a platform without marketplace enabled."""
|
||||
platform = Platform(
|
||||
code=f"sdp_{uuid.uuid4().hex[:8]}",
|
||||
name="Loyalty Only Platform",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(platform)
|
||||
db.commit()
|
||||
db.refresh(platform)
|
||||
return platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sd_merchant(db, sd_owner):
|
||||
"""Create a merchant."""
|
||||
merchant = Merchant(
|
||||
name="Dashboard Test Merchant",
|
||||
owner_user_id=sd_owner.id,
|
||||
contact_email=sd_owner.email,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(merchant)
|
||||
db.commit()
|
||||
db.refresh(merchant)
|
||||
return merchant
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sd_store(db, sd_merchant):
|
||||
"""Create a store."""
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
store = Store(
|
||||
merchant_id=sd_merchant.id,
|
||||
store_code=f"SDSTORE_{uid.upper()}",
|
||||
subdomain=f"sdstore{uid.lower()}",
|
||||
name="Dashboard Test Store",
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(store)
|
||||
db.commit()
|
||||
db.refresh(store)
|
||||
return store
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sd_store_platform(db, sd_store, sd_platform):
|
||||
"""Link store to a loyalty-only platform (no marketplace)."""
|
||||
sp = StorePlatform(
|
||||
store_id=sd_store.id,
|
||||
platform_id=sd_platform.id,
|
||||
is_active=True,
|
||||
)
|
||||
db.add(sp)
|
||||
db.commit()
|
||||
db.refresh(sp)
|
||||
return sp
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sd_auth(sd_owner, sd_store):
|
||||
"""Override auth dependency for store cookie/header auth."""
|
||||
user_context = UserContext(
|
||||
id=sd_owner.id,
|
||||
email=sd_owner.email,
|
||||
username=sd_owner.username,
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
token_store_id=sd_store.id,
|
||||
token_store_code=sd_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)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.core
|
||||
class TestStoreDashboardPage:
|
||||
"""Tests for GET /store/{store_code}/dashboard."""
|
||||
|
||||
def test_dashboard_renders_for_authenticated_store_user(
|
||||
self, client, db, sd_auth, sd_store, sd_store_platform
|
||||
):
|
||||
"""Dashboard page returns 200 for authenticated store user."""
|
||||
response = client.get(
|
||||
f"/store/{sd_store.subdomain}/dashboard",
|
||||
headers=sd_auth,
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_dashboard_renders_without_onboarding_check(
|
||||
self, client, db, sd_auth, sd_store, sd_store_platform
|
||||
):
|
||||
"""Dashboard loads without any onboarding redirect — no marketplace dependency.
|
||||
|
||||
On a loyalty-only platform, there's no StoreOnboarding record,
|
||||
yet the dashboard should still render successfully.
|
||||
"""
|
||||
response = client.get(
|
||||
f"/store/{sd_store.subdomain}/dashboard",
|
||||
headers=sd_auth,
|
||||
follow_redirects=False,
|
||||
)
|
||||
# Should NOT redirect to onboarding
|
||||
assert response.status_code == 200
|
||||
assert "location" not in response.headers
|
||||
|
||||
def test_dashboard_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/dashboard",
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.core
|
||||
class TestStoreDashboardNoMarketplace:
|
||||
"""Tests verifying dashboard has no marketplace module dependency."""
|
||||
|
||||
def test_dashboard_has_no_onboarding_import(self):
|
||||
"""Core store routes module does not import OnboardingService."""
|
||||
import app.modules.core.routes.pages.store as core_store_module
|
||||
|
||||
with open(core_store_module.__file__) as f:
|
||||
source = f.read()
|
||||
assert "OnboardingService" not in source
|
||||
assert "onboarding" not in source.lower()
|
||||
|
||||
def test_dashboard_has_no_marketplace_import(self):
|
||||
"""Core store routes module does not import from marketplace."""
|
||||
import app.modules.core.routes.pages.store as core_store_module
|
||||
|
||||
with open(core_store_module.__file__) as f:
|
||||
source = f.read()
|
||||
assert "app.modules.marketplace" not in source
|
||||
Reference in New Issue
Block a user