Files
orion/app/modules/loyalty/tests/conftest.py
Samir Boulahtit 6b46a78e72
Some checks failed
CI / ruff (push) Successful in 10s
CI / pytest (push) Failing after 45m49s
CI / validate (push) Successful in 23s
CI / dependency-scanning (push) Successful in 29s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat(loyalty): restructure program CRUD by interface
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>
2026-02-25 13:32:20 +01:00

277 lines
7.4 KiB
Python

# app/modules/loyalty/tests/conftest.py
"""
Module-specific fixtures for loyalty tests.
Core fixtures (db, client, etc.) are inherited from the root conftest.py.
"""
import uuid
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
def loyalty_platform(db):
"""Create a platform for loyalty store tests."""
platform = Platform(
code=f"loyalty_{uuid.uuid4().hex[:8]}",
name="Loyalty Test Platform",
is_active=True,
)
db.add(platform)
db.commit()
db.refresh(platform)
return platform
@pytest.fixture
def loyalty_store_setup(db, loyalty_platform):
"""
Full store setup for loyalty integration tests.
Creates: User -> Merchant -> Store -> StoreUser -> StorePlatform
+ LoyaltyProgram + LoyaltyCard (with customer)
Returns dict with all objects for easy access.
"""
from app.modules.customers.models.customer import Customer
from middleware.auth import AuthManager
auth = AuthManager()
uid = uuid.uuid4().hex[:8]
# Owner user
owner = User(
email=f"loyaltyowner_{uid}@test.com",
username=f"loyaltyowner_{uid}",
hashed_password=auth.hash_password("storepass123"),
role="merchant_owner",
is_active=True,
is_email_verified=True,
)
db.add(owner)
db.commit()
db.refresh(owner)
# Merchant
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)
# Store
store = Store(
merchant_id=merchant.id,
store_code=f"LOYTEST_{uid.upper()}",
subdomain=f"loytest{uid}",
name=f"Loyalty Test Store {uid}",
is_active=True,
is_verified=True,
)
db.add(store)
db.commit()
db.refresh(store)
# StoreUser
store_user = StoreUser(
store_id=store.id,
user_id=owner.id,
is_active=True,
)
db.add(store_user)
db.commit()
# StorePlatform
sp = StorePlatform(
store_id=store.id,
platform_id=loyalty_platform.id,
)
db.add(sp)
db.commit()
# Customer
customer = Customer(
email=f"customer_{uid}@test.com",
first_name="Test",
last_name="Customer",
hashed_password="!test!unused", # noqa: SEC-001
customer_number=f"CUST-{uid.upper()}",
store_id=store.id,
is_active=True,
)
db.add(customer)
db.commit()
db.refresh(customer)
# Loyalty Program
program = LoyaltyProgram(
merchant_id=merchant.id,
loyalty_type=LoyaltyType.POINTS.value,
points_per_euro=10,
welcome_bonus_points=50,
minimum_redemption_points=100,
minimum_purchase_cents=0,
points_expiration_days=365,
cooldown_minutes=0,
max_daily_stamps=10,
require_staff_pin=False,
card_name="Test Rewards",
card_color="#4F46E5",
is_active=True,
points_rewards=[
{"id": "reward_1", "name": "€5 off", "points_required": 100, "is_active": True},
],
)
db.add(program)
db.commit()
db.refresh(program)
# Loyalty Card
card = LoyaltyCard(
merchant_id=merchant.id,
program_id=program.id,
customer_id=customer.id,
enrolled_at_store_id=store.id,
card_number=f"TESTCARD-{uid.upper()}",
points_balance=100,
total_points_earned=150,
points_redeemed=50,
is_active=True,
last_activity_at=datetime.now(UTC),
)
db.add(card)
db.commit()
db.refresh(card)
return {
"owner": owner,
"merchant": merchant,
"store": store,
"platform": loyalty_platform,
"customer": customer,
"program": program,
"card": card,
}
@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):
"""
Get real JWT auth headers by logging in via store auth endpoint.
"""
owner = loyalty_store_setup["owner"]
response = client.post(
"/api/v1/store/auth/login",
json={
"email_or_username": owner.username,
"password": "storepass123",
},
)
assert response.status_code == 200, f"Store login failed: {response.text}"
token = response.json()["access_token"]
return {"Authorization": f"Bearer {token}"}