test: add loyalty module tests for today's bug fixes
Some checks failed
CI / ruff (push) Successful in 11s
CI / dependency-scanning (push) Successful in 28s
CI / docs (push) Has been skipped
CI / pytest (push) Failing after 46m26s
CI / validate (push) Successful in 23s
CI / deploy (push) Has been skipped

Covers card lookup route ordering, func.replace normalization, customer
name in transactions, self-enrollment creation, and earn points endpoint.
54 tests total (was 1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 23:28:22 +01:00
parent 92a434530f
commit 3df75e2e78
4 changed files with 745 additions and 2 deletions

View File

@@ -0,0 +1,215 @@
# app/modules/loyalty/tests/integration/test_store_api.py
"""
Integration tests for store loyalty API endpoints.
Tests the endpoints fixed today:
- GET /cards/lookup (route ordering fix)
- GET /cards/{card_id} (card detail)
- GET /transactions (customer_name in response)
- POST /points/earn (endpoint path rename)
Authentication: Uses real JWT tokens via store login endpoint.
"""
from datetime import UTC, datetime
import pytest
from app.modules.loyalty.models import LoyaltyTransaction
from app.modules.loyalty.models.loyalty_transaction import TransactionType
BASE = "/api/v1/store/loyalty"
# ============================================================================
# Card Lookup Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestCardLookup:
"""Tests for GET /cards/lookup endpoint."""
def test_lookup_by_card_number(
self, client, loyalty_store_headers, loyalty_store_setup
):
"""Find card by card number."""
card = loyalty_store_setup["card"]
response = client.get(
f"{BASE}/cards/lookup",
params={"q": card.card_number},
headers=loyalty_store_headers,
)
assert response.status_code == 200
data = response.json()
assert data["card_number"] == card.card_number
def test_lookup_by_email(
self, client, loyalty_store_headers, loyalty_store_setup
):
"""Find card by customer email."""
customer = loyalty_store_setup["customer"]
response = client.get(
f"{BASE}/cards/lookup",
params={"q": customer.email},
headers=loyalty_store_headers,
)
assert response.status_code == 200
data = response.json()
assert data["customer_email"] == customer.email
def test_lookup_not_found(self, client, loyalty_store_headers):
"""Non-existent search returns 404."""
response = client.get(
f"{BASE}/cards/lookup",
params={"q": "nonexistent@nowhere.com"},
headers=loyalty_store_headers,
)
assert response.status_code == 404
def test_lookup_route_not_captured_by_card_id(
self, client, loyalty_store_headers
):
"""GET /cards/lookup is NOT matched by /cards/{card_id} (route ordering fix)."""
response = client.get(
f"{BASE}/cards/lookup",
params={"q": "test"},
headers=loyalty_store_headers,
)
# Should be 404 (not found) not 422 (validation error from parsing "lookup" as int)
assert response.status_code != 422
# ============================================================================
# Card Detail Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestCardDetail:
"""Tests for GET /cards/{card_id} endpoint."""
def test_get_card_detail(
self, client, loyalty_store_headers, loyalty_store_setup
):
"""Card detail includes customer_name."""
card = loyalty_store_setup["card"]
customer = loyalty_store_setup["customer"]
response = client.get(
f"{BASE}/cards/{card.id}",
headers=loyalty_store_headers,
)
assert response.status_code == 200
data = response.json()
assert data["card_number"] == card.card_number
assert data["customer_name"] == f"{customer.first_name} {customer.last_name}"
def test_get_card_not_found(self, client, loyalty_store_headers):
"""Non-existent card returns 404."""
response = client.get(
f"{BASE}/cards/999999",
headers=loyalty_store_headers,
)
assert response.status_code == 404
# ============================================================================
# Store Transactions Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestStoreTransactions:
"""Tests for GET /transactions endpoint."""
def test_list_transactions(
self, client, loyalty_store_headers, loyalty_store_setup, db
):
"""Transactions endpoint returns data."""
card = loyalty_store_setup["card"]
store = loyalty_store_setup["store"]
# Create a transaction
tx = LoyaltyTransaction(
merchant_id=card.merchant_id,
card_id=card.id,
store_id=store.id,
transaction_type=TransactionType.POINTS_EARNED.value,
points_delta=50,
points_balance_after=150,
transaction_at=datetime.now(UTC),
)
db.add(tx)
db.commit()
response = client.get(
f"{BASE}/transactions",
headers=loyalty_store_headers,
)
assert response.status_code == 200
data = response.json()
assert data["total"] >= 1
assert len(data["transactions"]) >= 1
def test_transactions_include_customer_name(
self, client, loyalty_store_headers, loyalty_store_setup, db
):
"""Transaction responses include customer_name (not 'Unknown')."""
card = loyalty_store_setup["card"]
customer = loyalty_store_setup["customer"]
store = loyalty_store_setup["store"]
tx = LoyaltyTransaction(
merchant_id=card.merchant_id,
card_id=card.id,
store_id=store.id,
transaction_type=TransactionType.POINTS_EARNED.value,
points_delta=25,
transaction_at=datetime.now(UTC),
)
db.add(tx)
db.commit()
response = client.get(
f"{BASE}/transactions",
headers=loyalty_store_headers,
)
assert response.status_code == 200
transactions = response.json()["transactions"]
assert len(transactions) >= 1
# Customer name should be populated, not None
assert transactions[0]["customer_name"] == f"{customer.first_name} {customer.last_name}"
# ============================================================================
# Earn Points Tests
# ============================================================================
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.loyalty
class TestEarnPoints:
"""Tests for POST /points/earn endpoint."""
def test_earn_points_endpoint_exists(
self, client, loyalty_store_headers, loyalty_store_setup
):
"""POST /points/earn returns success (not 404)."""
card = loyalty_store_setup["card"]
response = client.post(
f"{BASE}/points/earn",
json={
"card_id": card.id,
"purchase_amount_cents": 1000,
},
headers=loyalty_store_headers,
)
# Should not be 404 (endpoint exists after rename)
assert response.status_code != 404