test(loyalty): add comprehensive test suite for loyalty module
Add tests for the loyalty module Phase 2 implementation: Fixtures (tests/fixtures/loyalty_fixtures.py): - test_loyalty_program: Points-based program with rewards - test_loyalty_program_no_expiration: Program without point expiry - test_loyalty_card: Active customer card - test_loyalty_card_inactive: Card for expiration testing - test_loyalty_transaction: Sample transaction - test_staff_pin: Staff PIN for verification tests Unit Tests (tests/unit/services/test_loyalty_services.py): - ProgramService: Company queries, listing, filtering - CardService: Lookup, enrollment, balance operations - PointsService: Earn, redeem, void, adjust operations - PinService: Creation, verification, lockout Integration Tests (tests/integration/api/v1/loyalty/): - Vendor API: Program settings, card management, PIN operations - Storefront API: Endpoint existence verification Task Tests (tests/integration/tasks/test_loyalty_tasks.py): - Point expiration for inactive cards - Transaction record creation on expiration - No expiration for active cards or zero balances - Voided total tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
182
tests/fixtures/loyalty_fixtures.py
vendored
Normal file
182
tests/fixtures/loyalty_fixtures.py
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
# tests/fixtures/loyalty_fixtures.py
|
||||
"""
|
||||
Loyalty module test fixtures.
|
||||
|
||||
Provides fixtures for:
|
||||
- Loyalty programs (company-based)
|
||||
- Loyalty cards
|
||||
- Transactions
|
||||
- Staff PINs
|
||||
- Authentication tokens for loyalty tests
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from app.modules.loyalty.models import (
|
||||
LoyaltyCard,
|
||||
LoyaltyProgram,
|
||||
LoyaltyTransaction,
|
||||
StaffPin,
|
||||
)
|
||||
from app.modules.loyalty.models.loyalty_program import LoyaltyType
|
||||
from app.modules.loyalty.models.loyalty_transaction import TransactionType
|
||||
from app.modules.tenancy.models import Company, Vendor, VendorUser, VendorUserType
|
||||
from app.modules.customers.models import Customer
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_loyalty_program(db, test_company):
|
||||
"""Create a test loyalty program for a company."""
|
||||
program = LoyaltyProgram(
|
||||
company_id=test_company.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=15,
|
||||
max_daily_stamps=5,
|
||||
require_staff_pin=True,
|
||||
card_name="Test Rewards",
|
||||
card_color="#4F46E5",
|
||||
is_active=True,
|
||||
points_rewards=[
|
||||
{"id": "reward_1", "name": "€5 off", "points_required": 100, "description": "€5 discount"},
|
||||
{"id": "reward_2", "name": "€10 off", "points_required": 200, "description": "€10 discount"},
|
||||
{"id": "reward_3", "name": "€25 off", "points_required": 500, "description": "€25 discount"},
|
||||
],
|
||||
)
|
||||
db.add(program)
|
||||
db.commit()
|
||||
db.refresh(program)
|
||||
return program
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_loyalty_program_no_expiration(db, test_company):
|
||||
"""Create a test loyalty program without point expiration."""
|
||||
# Use different company to avoid unique constraint
|
||||
from app.modules.tenancy.models import Company
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
company = Company(
|
||||
name=f"No Expiry Company {unique_id}",
|
||||
contact_email=f"noexpiry{unique_id}@test.com",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(company)
|
||||
db.flush()
|
||||
|
||||
program = LoyaltyProgram(
|
||||
company_id=company.id,
|
||||
loyalty_type=LoyaltyType.POINTS.value,
|
||||
points_per_euro=1,
|
||||
welcome_bonus_points=0,
|
||||
minimum_redemption_points=50,
|
||||
points_expiration_days=None, # No expiration
|
||||
cooldown_minutes=0,
|
||||
max_daily_stamps=10,
|
||||
require_staff_pin=False,
|
||||
card_name="No Expiry Rewards",
|
||||
is_active=True,
|
||||
)
|
||||
db.add(program)
|
||||
db.commit()
|
||||
db.refresh(program)
|
||||
return program
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_loyalty_card(db, test_loyalty_program, test_customer, test_vendor):
|
||||
"""Create a test loyalty card."""
|
||||
unique_id = str(uuid.uuid4())[:8].upper()
|
||||
card = LoyaltyCard(
|
||||
company_id=test_loyalty_program.company_id,
|
||||
program_id=test_loyalty_program.id,
|
||||
customer_id=test_customer.id,
|
||||
enrolled_at_vendor_id=test_vendor.id,
|
||||
card_number=f"CARD-{unique_id}",
|
||||
customer_email=test_customer.email,
|
||||
customer_phone=test_customer.phone,
|
||||
customer_name=f"{test_customer.first_name} {test_customer.last_name}",
|
||||
points_balance=100,
|
||||
stamps_balance=0,
|
||||
total_points_earned=150,
|
||||
total_points_redeemed=50,
|
||||
is_active=True,
|
||||
last_activity_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(card)
|
||||
db.commit()
|
||||
db.refresh(card)
|
||||
return card
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_loyalty_card_inactive(db, test_loyalty_program, test_vendor):
|
||||
"""Create a test loyalty card that hasn't been used in a long time (for expiration tests)."""
|
||||
unique_id = str(uuid.uuid4())[:8].upper()
|
||||
card = LoyaltyCard(
|
||||
company_id=test_loyalty_program.company_id,
|
||||
program_id=test_loyalty_program.id,
|
||||
customer_id=None,
|
||||
enrolled_at_vendor_id=test_vendor.id,
|
||||
card_number=f"INACTIVE-{unique_id}",
|
||||
customer_email=f"inactive{unique_id}@test.com",
|
||||
customer_name="Inactive Customer",
|
||||
points_balance=500,
|
||||
stamps_balance=0,
|
||||
total_points_earned=500,
|
||||
is_active=True,
|
||||
# Last activity was 400 days ago (beyond 365-day expiration)
|
||||
last_activity_at=datetime.now(UTC) - timedelta(days=400),
|
||||
)
|
||||
db.add(card)
|
||||
db.commit()
|
||||
db.refresh(card)
|
||||
return card
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_loyalty_transaction(db, test_loyalty_card, test_vendor):
|
||||
"""Create a test loyalty transaction."""
|
||||
transaction = LoyaltyTransaction(
|
||||
company_id=test_loyalty_card.company_id,
|
||||
card_id=test_loyalty_card.id,
|
||||
vendor_id=test_vendor.id,
|
||||
transaction_type=TransactionType.POINTS_EARNED.value,
|
||||
points_delta=50,
|
||||
balance_after=150,
|
||||
stamps_delta=0,
|
||||
stamps_balance_after=0,
|
||||
notes="Test purchase",
|
||||
transaction_at=datetime.now(UTC),
|
||||
)
|
||||
db.add(transaction)
|
||||
db.commit()
|
||||
db.refresh(transaction)
|
||||
return transaction
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_staff_pin(db, test_loyalty_program, test_vendor):
|
||||
"""Create a test staff PIN."""
|
||||
from app.modules.loyalty.services.pin_service import pin_service
|
||||
|
||||
unique_id = str(uuid.uuid4())[:8]
|
||||
pin = StaffPin(
|
||||
program_id=test_loyalty_program.id,
|
||||
vendor_id=test_vendor.id,
|
||||
staff_name=f"Test Staff {unique_id}",
|
||||
pin_hash=pin_service._hash_pin("1234"),
|
||||
is_active=True,
|
||||
)
|
||||
db.add(pin)
|
||||
db.commit()
|
||||
db.refresh(pin)
|
||||
return pin
|
||||
Reference in New Issue
Block a user