TorbatYar/backend/services/loyalty/app/tests/test_phase72.py
Mortezakoohjani 071c484530 Ship Loyalty phases 7.2-7.6 (points through wallet) with production deploy.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 21:27:22 +03:30

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