Files
modbison/backend/tests/test_payments.py
T

124 lines
4.1 KiB
Python

"""Integration tests for Premium Purchase and PaymentProvider.
docs/planning/economy.md US-2 (task 575fd14b5360).
"""
import pytest
from fastapi.testclient import TestClient
from app.main import app
def _register(client: TestClient, username: str) -> tuple[int, dict[str, str]]:
resp = client.post(
"/api/auth/register",
json={
"username": username,
"email": f"{username}@example.com",
"password": "secret123",
"date_of_birth": "1990-01-01",
},
)
assert resp.status_code == 201, resp.text
token = resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}
me = client.get("/api/users/me", headers=headers)
assert me.status_code == 200, me.text
return me.json()["id"], headers
def test_payments_unauthorized():
client = TestClient(app)
resp = client.post("/api/payments/purchase", json={"credits": 500})
assert resp.status_code == 401
resp = client.post("/api/payments/confirm", json={"session_id": "sess_123"})
assert resp.status_code == 401
def test_purchase_rejects_arbitrary_amounts():
client = TestClient(app)
_, headers = _register(client, "pay_user_1")
# 42 credits is not an allowed pack
resp = client.post("/api/payments/purchase", json={"credits": 42}, headers=headers)
assert resp.status_code == 422
# Negative / 0 credits
resp = client.post("/api/payments/purchase", json={"credits": -100}, headers=headers)
assert resp.status_code == 422
def test_purchase_and_confirm_flow():
client = TestClient(app)
uid, headers = _register(client, "pay_user_2")
# 1. Check initial balance
w_resp = client.get("/api/wallet", headers=headers)
assert w_resp.status_code == 200
assert w_resp.json()["balance"] == 0
# 2. Purchase session for 500 credits
p_resp = client.post(
"/api/payments/purchase",
json={"credits": 500},
headers={"Idempotency-Key": "sess_req_1", **headers},
)
assert p_resp.status_code == 201, p_resp.text
p_data = p_resp.json()
assert p_data["credits"] == 500
assert p_data["session_id"].startswith("sess_")
session_id = p_data["session_id"]
# Balance is still 0 before confirmation
w_resp = client.get("/api/wallet", headers=headers)
assert w_resp.json()["balance"] == 0
# 3. Confirm purchase session
c_resp = client.post(
"/api/payments/confirm",
json={"session_id": session_id},
headers=headers,
)
assert c_resp.status_code == 200, c_resp.text
c_data = c_resp.json()
assert c_data["confirmed"] is True
assert c_data["credits"] == 500
assert c_data["new_balance"] == 500
assert c_data["provider_ref"] != ""
# 4. Idempotent re-confirm must not double-credit
c_resp2 = client.post(
"/api/payments/confirm",
json={"session_id": session_id},
headers=headers,
)
assert c_resp2.status_code == 200
assert c_resp2.json()["new_balance"] == 500
# Check transactions in ledger: exactly 1 transaction
tx_resp = client.get("/api/wallet/transactions", headers=headers)
assert tx_resp.status_code == 200
tx_data = tx_resp.json()
assert tx_data["total"] == 1
assert tx_data["transactions"][0]["amount"] == 500
assert tx_data["transactions"][0]["type"] == "credit_purchase"
def test_confirm_invalid_or_wrong_user_fails():
client = TestClient(app)
uid1, headers1 = _register(client, "pay_user_3a")
uid2, headers2 = _register(client, "pay_user_3b")
# User 1 creates a session
p_resp = client.post("/api/payments/purchase", json={"credits": 100}, headers=headers1)
session_id = p_resp.json()["session_id"]
# User 2 tries to confirm User 1's session -> must fail 400
bad_resp = client.post("/api/payments/confirm", json={"session_id": session_id}, headers=headers2)
assert bad_resp.status_code == 400
# Non-existent session -> must fail 400
bad_resp2 = client.post("/api/payments/confirm", json={"session_id": "sess_nonexistent"}, headers=headers1)
assert bad_resp2.status_code == 400