"""Integration tests for Marketplace Moderation, Content Flags, and Age Gate Enforcement. docs/planning/economy.md US-3, Task 9 (task 4ba24a178c1b). """ from datetime import date import pytest from fastapi.testclient import TestClient from app.main import app def _register( client: TestClient, username: str, dob: str = "1990-01-01" ) -> tuple[int, dict[str, str]]: resp = client.post( "/api/auth/register", json={ "username": username, "email": f"{username}@example.com", "password": "secret123", "date_of_birth": dob, }, ) 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 async def _set_user_dob(user_id: int, dob: date): from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine from sqlalchemy.pool import NullPool from app.config import settings from app.models.user import User engine = create_async_engine(settings.DATABASE_URL, poolclass=NullPool) session_factory = async_sessionmaker(engine, expire_on_commit=False) async with session_factory() as session: user = await session.get(User, user_id) assert user is not None user.date_of_birth = dob await session.commit() await engine.dispose() @pytest.mark.asyncio async def test_marketplace_age_gate_enforcement(): client = TestClient(app) seller_id, seller_headers = _register(client, "mature_seller_1") # Register minor and set DOB in DB to 2015 minor_id, minor_headers = _register(client, "minor_buyer_1") await _set_user_dob(minor_id, date(2015, 6, 1)) # Adult user adult_id, adult_headers = _register(client, "adult_buyer_1") # Fund minor and adult with 500 Credits for h in (minor_headers, adult_headers): s_resp = client.post("/api/payments/purchase", headers=h, json={"credits": 500}) client.post("/api/payments/confirm", headers=h, json={"session_id": s_resp.json()["session_id"]}) # Seller creates mature item item_resp = client.post( "/api/marketplace/items", headers=seller_headers, json={ "name": "Blood Sword", "item_type": "avatar_item", "metadata_json": {"is_mature": True, "content_flags": ["violence.gore"]}, }, ) assert item_resp.status_code == 201 item_id = item_resp.json()["id"] list_resp = client.post( "/api/marketplace/listings", headers=seller_headers, json={"catalog_item_id": item_id, "price": 100}, ) assert list_resp.status_code == 201 listing_id = list_resp.json()["id"] # 1. Minor attempts to purchase mature item -> 403 Forbidden minor_buy = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=minor_headers, ) assert minor_buy.status_code == 403 assert "MATURE_CONTENT_REQUIRES_VERIFICATION" in minor_buy.text # 2. Adult purchases mature item -> 201 Created adult_buy = client.post( f"/api/marketplace/listings/{listing_id}/purchase", headers=adult_headers, ) assert adult_buy.status_code == 201, adult_buy.text assert adult_buy.json()["status"] == "pending_escrow" def test_moderator_delist_remove_and_reversal(): client = TestClient(app) mod_id, mod_headers = _register(client, "admin_mkt_mod_1", dob="1980-01-01") seller_id, seller_headers = _register(client, "seller_mod_test", dob="1990-01-01") buyer_id, buyer_headers = _register(client, "buyer_mod_test", dob="1990-01-01") # Fund buyer s_resp = client.post("/api/payments/purchase", headers=buyer_headers, json={"credits": 500}) client.post("/api/payments/confirm", headers=buyer_headers, json={"session_id": s_resp.json()["session_id"]}) # Create listing 1 i1 = client.post( "/api/marketplace/items", headers=seller_headers, json={"name": "Violating Item", "item_type": "avatar_item"}, ).json() l1 = client.post( "/api/marketplace/listings", headers=seller_headers, json={"catalog_item_id": i1["id"], "price": 100}, ).json() l1_id = l1["id"] # Non-moderator cannot moderate listing -> 403 unauth = client.post( f"/api/marketplace/listings/{l1_id}/moderate", headers=buyer_headers, json={"action": "remove", "reason": "Copyright infringement"}, ) assert unauth.status_code == 403 # Moderator removes listing mod_resp = client.post( f"/api/marketplace/listings/{l1_id}/moderate", headers=mod_headers, json={"action": "remove", "reason": "Copyright infringement"}, ) assert mod_resp.status_code == 200, mod_resp.text assert mod_resp.json()["status"] == "removed_by_moderation" # Removed listing cannot be purchased buy_fail = client.post( f"/api/marketplace/listings/{l1_id}/purchase", headers=buyer_headers, ) assert buy_fail.status_code == 400 # Create listing 2 and purchase it i2 = client.post( "/api/marketplace/items", headers=seller_headers, json={"name": "Contested Item", "item_type": "avatar_item"}, ).json() l2 = client.post( "/api/marketplace/listings", headers=seller_headers, json={"catalog_item_id": i2["id"], "price": 150}, ).json() p2 = client.post( f"/api/marketplace/listings/{l2['id']}/purchase", headers=buyer_headers, ).json() p2_id = p2["id"] # Buyer balance is 350 w = client.get("/api/wallet", headers=buyer_headers).json() assert w["balance"] == 350 # Non-moderator cannot reverse purchase -> 403 unauth_rev = client.post( f"/api/marketplace/purchases/{p2_id}/reverse", headers=buyer_headers, json={"reason": "Buyer regret"}, ) assert unauth_rev.status_code == 403 # Moderator reverses purchase rev_resp = client.post( f"/api/marketplace/purchases/{p2_id}/reverse", headers=mod_headers, json={"reason": "Fraudulent listing detected"}, ) assert rev_resp.status_code == 200, rev_resp.text assert rev_resp.json()["status"] == "reversed" # Buyer balance restored to 500 w_after = client.get("/api/wallet", headers=buyer_headers).json() assert w_after["balance"] == 500