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>
156 lines
5.1 KiB
Python
156 lines
5.1 KiB
Python
# tests/integration/tasks/test_loyalty_tasks.py
|
|
"""
|
|
Integration tests for Loyalty background tasks.
|
|
|
|
Tests cover:
|
|
- Point expiration task
|
|
- Wallet sync task
|
|
"""
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
|
|
import pytest
|
|
|
|
from app.modules.loyalty.models import LoyaltyCard, LoyaltyTransaction
|
|
from app.modules.loyalty.models.loyalty_transaction import TransactionType
|
|
from app.modules.loyalty.tasks.point_expiration import (
|
|
_expire_points_for_program,
|
|
_process_point_expiration,
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
|
@pytest.mark.task
|
|
class TestPointExpirationTask:
|
|
"""Tests for point expiration background task."""
|
|
|
|
def test_expire_points_for_inactive_card(
|
|
self, db, test_loyalty_program, test_loyalty_card_inactive
|
|
):
|
|
"""Test that points expire for inactive cards."""
|
|
initial_balance = test_loyalty_card_inactive.points_balance
|
|
assert initial_balance > 0
|
|
|
|
# Run expiration for the program
|
|
cards_processed, points_expired = _expire_points_for_program(
|
|
db, test_loyalty_program
|
|
)
|
|
db.commit()
|
|
|
|
# Refresh the card
|
|
db.refresh(test_loyalty_card_inactive)
|
|
|
|
assert cards_processed == 1
|
|
assert points_expired == initial_balance
|
|
assert test_loyalty_card_inactive.points_balance == 0
|
|
|
|
def test_expire_points_creates_transaction(
|
|
self, db, test_loyalty_program, test_loyalty_card_inactive
|
|
):
|
|
"""Test that expiration creates a transaction record."""
|
|
initial_balance = test_loyalty_card_inactive.points_balance
|
|
|
|
_expire_points_for_program(db, test_loyalty_program)
|
|
db.commit()
|
|
|
|
# Check for expiration transaction
|
|
transaction = (
|
|
db.query(LoyaltyTransaction)
|
|
.filter(
|
|
LoyaltyTransaction.card_id == test_loyalty_card_inactive.id,
|
|
LoyaltyTransaction.transaction_type == TransactionType.POINTS_EXPIRED.value,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
assert transaction is not None
|
|
assert transaction.points_delta == -initial_balance
|
|
assert transaction.balance_after == 0
|
|
assert "expired" in transaction.notes.lower()
|
|
|
|
def test_no_expiration_for_active_cards(
|
|
self, db, test_loyalty_program, test_loyalty_card
|
|
):
|
|
"""Test that active cards are not expired."""
|
|
# Ensure card has recent activity
|
|
test_loyalty_card.last_activity_at = datetime.now(UTC)
|
|
db.commit()
|
|
|
|
initial_balance = test_loyalty_card.points_balance
|
|
|
|
cards_processed, points_expired = _expire_points_for_program(
|
|
db, test_loyalty_program
|
|
)
|
|
db.commit()
|
|
|
|
db.refresh(test_loyalty_card)
|
|
|
|
# Active card should not be affected
|
|
assert test_loyalty_card.points_balance == initial_balance
|
|
|
|
def test_no_expiration_for_zero_balance_cards(
|
|
self, db, test_loyalty_program, test_loyalty_card_inactive
|
|
):
|
|
"""Test that cards with zero balance are not processed."""
|
|
test_loyalty_card_inactive.points_balance = 0
|
|
db.commit()
|
|
|
|
cards_processed, points_expired = _expire_points_for_program(
|
|
db, test_loyalty_program
|
|
)
|
|
db.commit()
|
|
|
|
assert cards_processed == 0
|
|
assert points_expired == 0
|
|
|
|
def test_no_expiration_when_not_configured(
|
|
self, db, test_loyalty_program_no_expiration
|
|
):
|
|
"""Test that no expiration occurs when not configured."""
|
|
# Create a card with old activity for this program
|
|
card = LoyaltyCard(
|
|
company_id=test_loyalty_program_no_expiration.company_id,
|
|
program_id=test_loyalty_program_no_expiration.id,
|
|
card_number="NO-EXPIRY-CARD",
|
|
customer_email="noexpiry@test.com",
|
|
points_balance=1000,
|
|
is_active=True,
|
|
last_activity_at=datetime.now(UTC) - timedelta(days=1000),
|
|
)
|
|
db.add(card)
|
|
db.commit()
|
|
|
|
cards_processed, points_expired = _expire_points_for_program(
|
|
db, test_loyalty_program_no_expiration
|
|
)
|
|
db.commit()
|
|
|
|
db.refresh(card)
|
|
|
|
# Should not expire because program has no expiration configured
|
|
assert cards_processed == 0
|
|
assert points_expired == 0
|
|
assert card.points_balance == 1000
|
|
|
|
def test_process_all_programs(self, db, test_loyalty_program, test_loyalty_card_inactive):
|
|
"""Test processing all programs."""
|
|
result = _process_point_expiration(db)
|
|
db.commit()
|
|
|
|
assert result["status"] == "success"
|
|
assert result["programs_processed"] >= 1
|
|
|
|
def test_expiration_updates_voided_total(
|
|
self, db, test_loyalty_program, test_loyalty_card_inactive
|
|
):
|
|
"""Test that expiration updates total_points_voided."""
|
|
initial_balance = test_loyalty_card_inactive.points_balance
|
|
initial_voided = test_loyalty_card_inactive.total_points_voided or 0
|
|
|
|
_expire_points_for_program(db, test_loyalty_program)
|
|
db.commit()
|
|
|
|
db.refresh(test_loyalty_card_inactive)
|
|
|
|
assert test_loyalty_card_inactive.total_points_voided == initial_voided + initial_balance
|