191 lines
6.4 KiB
Python
191 lines
6.4 KiB
Python
"""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
|