"""Integration tests for Marketplace Purchase Service, Escrow, and Fee Splitting. docs/planning/economy.md US-4 (task bc90d3f481b4). """ import pytest from fastapi.testclient import TestClient from app.main import app from app.services.purchase import purchase_service 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_fee_split_calculation(): # 30% platform fee fee, net = purchase_service.compute_fee_split(100) assert fee == 30 assert net == 70 fee, net = purchase_service.compute_fee_split(250) assert fee == 75 assert net == 175 def test_marketplace_purchase_atomic_flow(): client = TestClient(app) seller_id, seller_headers = _register(client, "purchase_seller_1") buyer_id, buyer_headers = _register(client, "purchase_buyer_1") # 1. Seller creates catalog item and lists it for 200 Credits item_resp = client.post( "/api/marketplace/items", headers=seller_headers, json={ "name": "Dragon Armor", "description": "Legendary chestplate", "item_type": "avatar_item", }, ) assert item_resp.status_code == 201, item_resp.text item_id = item_resp.json()["id"] list_resp = client.post( "/api/marketplace/listings", headers=seller_headers, json={ "catalog_item_id": item_id, "price": 200, }, ) assert list_resp.status_code == 201, list_resp.text listing_id = list_resp.json()["id"] # 2. Seller cannot purchase own listing self_buy = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=seller_headers, ) assert self_buy.status_code == 400 assert "own listing" in self_buy.text # 3. Buyer attempts purchase without enough credits -> 400 poor_buy = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=buyer_headers, ) assert poor_buy.status_code == 400 assert "Insufficient funds" in poor_buy.text # 4. Buyer purchases 500 Credits via payments API sess_resp = client.post( "/api/payments/purchase", headers=buyer_headers, json={"credits": 500}, ) assert sess_resp.status_code == 201, sess_resp.text sess_id = sess_resp.json()["session_id"] conf_resp = client.post( "/api/payments/confirm", headers=buyer_headers, json={"session_id": sess_id}, ) assert conf_resp.status_code == 200, conf_resp.text assert conf_resp.json()["new_balance"] == 500 # 5. Buyer purchases listing for 200 Credits buy_headers = dict(buyer_headers) buy_headers["Idempotency-Key"] = "buy-test-key-1" buy_resp = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=buy_headers, ) assert buy_resp.status_code == 201, buy_resp.text purchase_data = buy_resp.json() assert purchase_data["price"] == 200 assert purchase_data["platform_fee"] == 60 assert purchase_data["seller_net"] == 140 assert purchase_data["status"] == "pending_escrow" assert purchase_data["buyer_id"] == buyer_id assert purchase_data["seller_id"] == seller_id purchase_id = purchase_data["id"] # 6. Check buyer wallet balance: 500 - 200 = 300 w_resp = client.get("/api/wallet", headers=buyer_headers) assert w_resp.status_code == 200 assert w_resp.json()["balance"] == 300 # 7. Check buyer has granted item in inventory inv_resp = client.get("/api/marketplace/items", headers=buyer_headers, params={"mine": "true"}) assert inv_resp.status_code == 200 my_items = inv_resp.json() assert any(i["name"] == "Dragon Armor" and i["owner_id"] == buyer_id for i in my_items) # 8. Listing is now sold and cannot be purchased again re_buy = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=buyer_headers, ) assert re_buy.status_code == 400 assert "not active" in re_buy.text # 9. Seller's creator balance is pending escrow cb_resp = client.get("/api/marketplace/creator/balance", headers=seller_headers) assert cb_resp.status_code == 200, cb_resp.text cb = cb_resp.json() assert cb["pending"] == 140 assert cb["available"] == 0 # 10. List purchases p_resp = client.get("/api/marketplace/purchases", headers=buyer_headers) assert p_resp.status_code == 200 assert any(p["id"] == purchase_id for p in p_resp.json()) # 11. Escrow release rel_resp = client.post( f"/api/marketplace/purchases/{purchase_id}/release", headers=seller_headers, ) assert rel_resp.status_code == 200, rel_resp.text assert rel_resp.json()["status"] == "completed" # Check seller's creator balance now shows available = 140 cb_resp2 = client.get("/api/marketplace/creator/balance", headers=seller_headers) assert cb_resp2.status_code == 200 cb2 = cb_resp2.json() assert cb2["pending"] == 0 assert cb2["available"] == 140 assert cb2["lifetime_earned"] == 140 def test_marketplace_refund(): client = TestClient(app) seller_id, seller_headers = _register(client, "refund_seller_1") buyer_id, buyer_headers = _register(client, "refund_buyer_1") # Item & Listing item_resp = client.post( "/api/marketplace/items", headers=seller_headers, json={"name": "Magic Ring", "item_type": "avatar_item"}, ) item_id = item_resp.json()["id"] list_resp = client.post( "/api/marketplace/listings", headers=seller_headers, json={"catalog_item_id": item_id, "price": 100}, ) listing_id = list_resp.json()["id"] # Fund buyer s_resp = client.post("/api/payments/purchase", headers=buyer_headers, json={"credits": 500}) assert s_resp.status_code == 201, s_resp.text client.post("/api/payments/confirm", headers=buyer_headers, json={"session_id": s_resp.json()["session_id"]}) # Purchase buy_resp = client.post(f"/api/marketplace/listings/{listing_id}/purchase", headers=buyer_headers) assert buy_resp.status_code == 201 purchase_id = buy_resp.json()["id"] # Buyer balance is 400 w1 = client.get("/api/wallet", headers=buyer_headers).json() assert w1["balance"] == 400 # Seller pending is 70 cb1 = client.get("/api/marketplace/creator/balance", headers=seller_headers).json() assert cb1["pending"] == 70 # Refund ref_resp = client.post( f"/api/marketplace/purchases/{purchase_id}/refund", headers=seller_headers, json={"reason": "Buyer dissatisfaction"}, ) assert ref_resp.status_code == 200, ref_resp.text assert ref_resp.json()["status"] == "refunded" # Buyer balance refunded back to 500 w2 = client.get("/api/wallet", headers=buyer_headers).json() assert w2["balance"] == 500 # Seller pending reset to 0 cb2 = client.get("/api/marketplace/creator/balance", headers=seller_headers).json() assert cb2["pending"] == 0