feat: production launch — email audit, team invites, security headers, router fixes
- Fix loyalty & monitoring router bugs (_get_router → named routers) - Implement team invitation email with send_template + seed templates (en/fr/de) - Add SecurityHeadersMiddleware (nosniff, HSTS, referrer-policy, permissions-policy) - Build email audit admin page: service, schemas, API, page route, menu, i18n, HTML, JS - Clean stale TODO in platform-menu-config.js - Add 67 tests (unit + integration) covering all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -270,9 +270,9 @@ def get_loyalty_module_with_routers() -> ModuleDefinition:
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
loyalty_module.router = _get_router()
|
||||
loyalty_module.admin_router = _get_admin_router()
|
||||
loyalty_module.merchant_router = _get_merchant_router()
|
||||
loyalty_module.router = _get_router()
|
||||
loyalty_module.store_router = _get_store_router()
|
||||
loyalty_module.platform_router = _get_platform_router()
|
||||
loyalty_module.storefront_router = _get_storefront_router()
|
||||
return loyalty_module
|
||||
|
||||
77
app/modules/loyalty/tests/unit/test_definition.py
Normal file
77
app/modules/loyalty/tests/unit/test_definition.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# app/modules/loyalty/tests/unit/test_definition.py
|
||||
"""Unit tests for loyalty module definition and router attachment."""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.loyalty.definition import (
|
||||
get_loyalty_module_with_routers,
|
||||
loyalty_module,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.loyalty
|
||||
class TestLoyaltyModuleDefinition:
|
||||
"""Tests for loyalty module definition."""
|
||||
|
||||
def test_module_code(self):
|
||||
"""Module code is 'loyalty'."""
|
||||
assert loyalty_module.code == "loyalty"
|
||||
|
||||
def test_module_is_not_core(self):
|
||||
"""Loyalty is not a core module."""
|
||||
assert loyalty_module.is_core is False
|
||||
|
||||
def test_module_is_self_contained(self):
|
||||
"""Loyalty is a self-contained module."""
|
||||
assert loyalty_module.is_self_contained is True
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.loyalty
|
||||
class TestLoyaltyRouterAttachment:
|
||||
"""Tests for get_loyalty_module_with_routers()."""
|
||||
|
||||
def test_admin_router_attached(self):
|
||||
"""Admin router is attached (not generic 'router')."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
assert module.admin_router is not None
|
||||
assert hasattr(module.admin_router, "routes")
|
||||
|
||||
def test_store_router_attached(self):
|
||||
"""Store router is attached."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
assert module.store_router is not None
|
||||
assert hasattr(module.store_router, "routes")
|
||||
|
||||
def test_merchant_router_attached(self):
|
||||
"""Merchant router is attached."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
assert module.merchant_router is not None
|
||||
assert hasattr(module.merchant_router, "routes")
|
||||
|
||||
def test_platform_router_attached(self):
|
||||
"""Platform router is attached."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
assert module.platform_router is not None
|
||||
assert hasattr(module.platform_router, "routes")
|
||||
|
||||
def test_storefront_router_attached(self):
|
||||
"""Storefront router is attached."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
assert module.storefront_router is not None
|
||||
assert hasattr(module.storefront_router, "routes")
|
||||
|
||||
def test_no_generic_router(self):
|
||||
"""The old buggy '.router' attribute should not be set."""
|
||||
module = get_loyalty_module_with_routers()
|
||||
# router attr may exist as None from ModuleDefinition defaults,
|
||||
# but should not be a real APIRouter object
|
||||
router_val = getattr(module, "router", None)
|
||||
if router_val is not None:
|
||||
# If it exists, it should not be an APIRouter (that was the bug)
|
||||
from fastapi import APIRouter
|
||||
assert not isinstance(router_val, APIRouter), (
|
||||
"module.router should not be an APIRouter — "
|
||||
"use admin_router/store_router instead"
|
||||
)
|
||||
Reference in New Issue
Block a user