115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
"""Phase 7.2 — Point Engine tests."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from app.events.publisher import get_event_publisher
|
|
from app.events.types import LoyaltyEventType
|
|
from app.tests.conftest import TENANT_A, tenant_headers
|
|
|
|
|
|
async def _open_account(client, *, suffix="a"):
|
|
program = await client.post(
|
|
"/api/v1/programs",
|
|
json={"code": f"pts{suffix}", "name": f"Points {suffix}"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert program.status_code == 201, program.text
|
|
program_id = program.json()["id"]
|
|
member = await client.post(
|
|
"/api/v1/members",
|
|
json={"program_id": program_id, "display_name": "P", "enroll": True},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
member_id = member.json()["id"]
|
|
account = await client.post(
|
|
"/api/v1/point-accounts",
|
|
json={"program_id": program_id, "member_id": member_id},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert account.status_code == 201, account.text
|
|
return account.json()["id"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_earn_redeem_balance_and_idempotency(client):
|
|
account_id = await _open_account(client)
|
|
earn = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/earn",
|
|
json={"amount": 100, "reason": "purchase", "idempotency_key": "e1"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert earn.status_code == 200, earn.text
|
|
assert earn.json()["balance_after"] == 100
|
|
|
|
again = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/earn",
|
|
json={"amount": 100, "reason": "purchase", "idempotency_key": "e1"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert again.status_code == 200
|
|
assert again.json()["id"] == earn.json()["id"]
|
|
|
|
bal = await client.get(
|
|
f"/api/v1/point-accounts/{account_id}/balance",
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert bal.status_code == 200
|
|
assert bal.json()["balance"] == 100
|
|
assert "balance" in bal.json()
|
|
|
|
redeem = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/redeem",
|
|
json={"amount": 40, "reason": "reward"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert redeem.status_code == 200
|
|
assert redeem.json()["balance_after"] == 60
|
|
|
|
over = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/redeem",
|
|
json={"amount": 1000},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert over.status_code == 409
|
|
assert over.json()["error"]["code"] == "insufficient_points"
|
|
|
|
events = {e.event_type for e in get_event_publisher().published}
|
|
assert LoyaltyEventType.POINTS_EARNED.value in events
|
|
assert LoyaltyEventType.POINTS_REDEEMED.value in events
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_adjust_expire_and_ledger_list(client):
|
|
account_id = await _open_account(client, suffix="adj")
|
|
await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/earn",
|
|
json={"amount": 50},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
adj = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/adjust",
|
|
json={"amount": -10, "reason": "correction"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert adj.status_code == 200
|
|
assert adj.json()["balance_after"] == 40
|
|
|
|
exp = await client.post(
|
|
f"/api/v1/point-accounts/{account_id}/expire-points",
|
|
json={"amount": 5, "reason": "aged"},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert exp.status_code == 200
|
|
assert exp.json()["balance_after"] == 35
|
|
|
|
page = await client.get(
|
|
f"/api/v1/point-accounts/{account_id}/ledger",
|
|
params={"limit": 10},
|
|
headers=tenant_headers(TENANT_A),
|
|
)
|
|
assert page.status_code == 200
|
|
body = page.json()
|
|
assert body["total"] >= 3
|
|
assert len(body["items"]) >= 3
|