"""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