test: add loyalty module tests for today's bug fixes
Some checks failed
Some checks failed
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:
215
app/modules/loyalty/tests/integration/test_store_api.py
Normal file
215
app/modules/loyalty/tests/integration/test_store_api.py
Normal 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
|
||||
Reference in New Issue
Block a user