feat(loyalty): restructure program CRUD by interface
Some checks failed
Some checks failed
Move program CRUD from store to merchant/admin interfaces. Store becomes view-only for program config while merchant gets full CRUD and admin gets override capabilities. Merchant portal: - New API endpoints (GET/POST/PATCH/DELETE /program) - New settings page with create/edit/delete form - Overview page now has Create/Edit Program buttons - Settings menu item added to sidebar Admin portal: - New CRUD endpoints (create for merchant, update, delete) - New activate/deactivate program endpoints - Programs list has edit and toggle buttons per row - Merchant detail has create/delete/toggle program actions Store portal: - Removed POST/PATCH /program endpoints (now read-only) - Removed settings page route and template - Terminal, cards, stats, enroll unchanged Tests: 112 passed (58 new) covering merchant API, admin CRUD, store endpoint removal, and program service unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,11 +9,14 @@ from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.api.deps import get_current_merchant_api, get_merchant_for_current_user
|
||||
from app.modules.loyalty.models import LoyaltyCard, LoyaltyProgram
|
||||
from app.modules.loyalty.models.loyalty_program import LoyaltyType
|
||||
from app.modules.tenancy.models import Merchant, Platform, Store, User
|
||||
from app.modules.tenancy.models.store import StoreUser
|
||||
from app.modules.tenancy.models.store_platform import StorePlatform
|
||||
from main import app
|
||||
from models.schema.auth import UserContext
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -166,6 +169,95 @@ def loyalty_store_setup(db, loyalty_platform):
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loyalty_merchant_setup(db, loyalty_platform):
|
||||
"""
|
||||
Merchant-only setup for loyalty integration tests (no program).
|
||||
|
||||
Creates: User -> Merchant (no program yet).
|
||||
Use this for testing program creation via merchant API.
|
||||
"""
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
auth = AuthManager()
|
||||
uid = uuid.uuid4().hex[:8]
|
||||
|
||||
owner = User(
|
||||
email=f"merchowner_{uid}@test.com",
|
||||
username=f"merchowner_{uid}",
|
||||
hashed_password=auth.hash_password("merchpass123"),
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
is_email_verified=True,
|
||||
)
|
||||
db.add(owner)
|
||||
db.commit()
|
||||
db.refresh(owner)
|
||||
|
||||
merchant = Merchant(
|
||||
name=f"Loyalty Merchant {uid}",
|
||||
owner_user_id=owner.id,
|
||||
contact_email=owner.email,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
db.add(merchant)
|
||||
db.commit()
|
||||
db.refresh(merchant)
|
||||
|
||||
return {
|
||||
"owner": owner,
|
||||
"merchant": merchant,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loyalty_merchant_headers(loyalty_store_setup):
|
||||
"""
|
||||
Override auth dependencies to return merchant/user for the merchant owner.
|
||||
Uses the full loyalty_store_setup which includes a program.
|
||||
"""
|
||||
owner = loyalty_store_setup["owner"]
|
||||
merchant = loyalty_store_setup["merchant"]
|
||||
|
||||
user_context = UserContext(
|
||||
id=owner.id,
|
||||
email=owner.email,
|
||||
username=owner.username,
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
app.dependency_overrides[get_merchant_for_current_user] = lambda: merchant
|
||||
app.dependency_overrides[get_current_merchant_api] = lambda: user_context
|
||||
yield {"Authorization": "Bearer fake-token"}
|
||||
app.dependency_overrides.pop(get_merchant_for_current_user, None)
|
||||
app.dependency_overrides.pop(get_current_merchant_api, None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loyalty_merchant_headers_no_program(loyalty_merchant_setup):
|
||||
"""
|
||||
Override auth dependencies for a merchant that has no program yet.
|
||||
"""
|
||||
owner = loyalty_merchant_setup["owner"]
|
||||
merchant = loyalty_merchant_setup["merchant"]
|
||||
|
||||
user_context = UserContext(
|
||||
id=owner.id,
|
||||
email=owner.email,
|
||||
username=owner.username,
|
||||
role="merchant_owner",
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
app.dependency_overrides[get_merchant_for_current_user] = lambda: merchant
|
||||
app.dependency_overrides[get_current_merchant_api] = lambda: user_context
|
||||
yield {"Authorization": "Bearer fake-token"}
|
||||
app.dependency_overrides.pop(get_merchant_for_current_user, None)
|
||||
app.dependency_overrides.pop(get_current_merchant_api, None)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def loyalty_store_headers(client, loyalty_store_setup):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user