7ecf0d53bd
checkpoint / checkpoint (push) Failing after 3h14m9s
- Creator Studio: Visual behavior & property inspector, GLB model importer, binary persistence - Trust & Safety: 18+ TOS confirmation (DOB removed), creator guidelines & briefing card on join, in-game reporting HUD - Test Harness: Add --filter, --skip-backend, --skip-godot to run_checkpoint_tests.py
244 lines
8.5 KiB
Python
244 lines
8.5 KiB
Python
"""Integration tests for message reporting and moderation queue:
|
|
- POST /api/chat/messages/{message_id}/report
|
|
- GET /api/moderation/reports
|
|
- POST /api/moderation/reports/{id}/resolve
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import app
|
|
|
|
|
|
def _register(client: TestClient, username: str) -> dict:
|
|
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 {"headers": headers, "id": me.json()["id"], "username": username}
|
|
|
|
|
|
def _send_dm(client: TestClient, sender: dict, recipient: dict, text: str = "hello") -> int:
|
|
resp = client.post(
|
|
f"/api/chat/dm/{recipient['id']}/messages",
|
|
headers=sender["headers"],
|
|
json={"body": text},
|
|
)
|
|
assert resp.status_code == 201, resp.text
|
|
return resp.json()["id"]
|
|
|
|
|
|
class TestReportMessage:
|
|
def test_report_message_success(self):
|
|
client = TestClient(app)
|
|
alice = _register(client, "rep_alice1")
|
|
bob = _register(client, "rep_bob1")
|
|
|
|
msg_id = _send_dm(client, alice, bob, "spam message")
|
|
|
|
resp = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "Contains spam", "category": "spam"},
|
|
)
|
|
assert resp.status_code == 201, resp.text
|
|
data = resp.json()
|
|
assert data["message_id"] == msg_id
|
|
assert data["reporter_id"] == bob["id"]
|
|
assert data["status"] == "open"
|
|
assert "[spam] Contains spam" in data["reason"]
|
|
|
|
def test_duplicate_report_rejected(self):
|
|
client = TestClient(app)
|
|
alice = _register(client, "rep_alice2")
|
|
bob = _register(client, "rep_bob2")
|
|
|
|
msg_id = _send_dm(client, alice, bob, "bad message")
|
|
|
|
resp1 = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "offensive"},
|
|
)
|
|
assert resp1.status_code == 201
|
|
|
|
resp2 = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "offensive again"},
|
|
)
|
|
assert resp2.status_code == 409
|
|
|
|
def test_report_nonexistent_message_404(self):
|
|
client = TestClient(app)
|
|
bob = _register(client, "rep_bob3")
|
|
|
|
resp = client.post(
|
|
"/api/chat/messages/999999/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "ghost"},
|
|
)
|
|
assert resp.status_code == 404
|
|
|
|
def test_report_requires_auth(self):
|
|
client = TestClient(app)
|
|
resp = client.post("/api/chat/messages/1/report", json={"reason": "no auth"})
|
|
assert resp.status_code == 401
|
|
|
|
|
|
class TestModerationQueue:
|
|
def test_non_moderator_cannot_access_queue(self):
|
|
client = TestClient(app)
|
|
regular_user = _register(client, "rep_regular1")
|
|
|
|
resp = client.get("/api/moderation/reports", headers=regular_user["headers"])
|
|
assert resp.status_code == 403
|
|
|
|
def test_moderator_can_list_reports(self):
|
|
client = TestClient(app)
|
|
alice = _register(client, "rep_alice4")
|
|
bob = _register(client, "rep_bob4")
|
|
admin = _register(client, "admin_list")
|
|
|
|
msg_id = _send_dm(client, alice, bob, "something to report")
|
|
client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "needs review"},
|
|
)
|
|
|
|
resp = client.get("/api/moderation/reports", headers=admin["headers"])
|
|
assert resp.status_code == 200, resp.text
|
|
reports = resp.json()
|
|
assert len(reports) >= 1
|
|
assert any(r["message_id"] == msg_id for r in reports)
|
|
|
|
def test_moderator_resolve_dismiss(self):
|
|
client = TestClient(app)
|
|
alice = _register(client, "rep_alice5")
|
|
bob = _register(client, "rep_bob5")
|
|
admin = _register(client, "admin_dismiss")
|
|
|
|
msg_id = _send_dm(client, alice, bob, "benign message")
|
|
rep_resp = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "false alarm"},
|
|
)
|
|
report_id = rep_resp.json()["id"]
|
|
|
|
resolve_resp = client.post(
|
|
f"/api/moderation/reports/{report_id}/resolve",
|
|
headers=admin["headers"],
|
|
json={"action": "dismiss", "notes": "No violation found"},
|
|
)
|
|
assert resolve_resp.status_code == 200, resolve_resp.text
|
|
data = resolve_resp.json()
|
|
assert data["report_id"] == report_id
|
|
assert data["status"] == "dismissed"
|
|
assert data["action_taken"] == "dismiss"
|
|
assert data["moderator_id"] == admin["id"]
|
|
|
|
def test_moderator_resolve_remove(self):
|
|
client = TestClient(app)
|
|
alice = _register(client, "rep_alice6")
|
|
bob = _register(client, "rep_bob6")
|
|
admin = _register(client, "admin_remove")
|
|
|
|
msg_id = _send_dm(client, alice, bob, "offensive content")
|
|
rep_resp = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=bob["headers"],
|
|
json={"reason": "hate speech"},
|
|
)
|
|
report_id = rep_resp.json()["id"]
|
|
|
|
resolve_resp = client.post(
|
|
f"/api/moderation/reports/{report_id}/resolve",
|
|
headers=admin["headers"],
|
|
json={"action": "remove", "notes": "Content removed"},
|
|
)
|
|
assert resolve_resp.status_code == 200
|
|
assert resolve_resp.json()["status"] == "resolved"
|
|
|
|
# The message is removed from conversation history
|
|
hist = client.get(f"/api/chat/dm/{bob['id']}/messages", headers=alice["headers"])
|
|
assert all(m["id"] != msg_id for m in hist.json()["messages"])
|
|
|
|
def test_moderator_resolve_ban(self):
|
|
client = TestClient(app)
|
|
bad_user = _register(client, "bad_actor")
|
|
victim = _register(client, "rep_victim")
|
|
admin_auth = _register(client, "admin_ban")
|
|
|
|
msg_id = _send_dm(client, bad_user, victim, "terrible violation")
|
|
rep_resp = client.post(
|
|
f"/api/chat/messages/{msg_id}/report",
|
|
headers=victim["headers"],
|
|
json={"reason": "malicious user"},
|
|
)
|
|
report_id = rep_resp.json()["id"]
|
|
|
|
resolve_resp = client.post(
|
|
f"/api/moderation/reports/{report_id}/resolve",
|
|
headers=admin_auth["headers"],
|
|
json={"action": "ban", "notes": "Banned malicious user"},
|
|
)
|
|
assert resolve_resp.status_code == 200
|
|
|
|
# Attempting to use bad_user's token fails with 403 inactive account
|
|
bad_me = client.get("/api/users/me", headers=bad_user["headers"])
|
|
assert bad_me.status_code == 403
|
|
|
|
def test_submit_world_and_user_moderation_report(self):
|
|
"""Test submitting general moderation reports for worlds and users."""
|
|
client = TestClient(app)
|
|
reporter = _register(client, "gen_reporter")
|
|
admin = _register(client, "admin_gen_mod")
|
|
|
|
# Report a world
|
|
world_rep = client.post(
|
|
"/api/moderation/reports",
|
|
headers=reporter["headers"],
|
|
json={
|
|
"target_type": "world",
|
|
"target_id": 42,
|
|
"reason": "Violates world guidelines",
|
|
"category": "world_rules",
|
|
},
|
|
)
|
|
assert world_rep.status_code == 201, world_rep.text
|
|
assert "[WORLD:42]" in world_rep.json()["reason"]
|
|
|
|
# Report a user
|
|
user_rep = client.post(
|
|
"/api/moderation/reports",
|
|
headers=reporter["headers"],
|
|
json={
|
|
"target_type": "user",
|
|
"target_id": 99,
|
|
"reason": "Harassment in world",
|
|
"category": "harassment",
|
|
},
|
|
)
|
|
assert user_rep.status_code == 201, user_rep.text
|
|
assert "[USER:99]" in user_rep.json()["reason"]
|
|
|
|
# Admin lists open reports and sees both
|
|
rep_list = client.get("/api/moderation/reports", headers=admin["headers"])
|
|
assert rep_list.status_code == 200
|
|
reasons = [r["reason"] for r in rep_list.json()]
|
|
assert any("[WORLD:42]" in r for r in reasons)
|
|
assert any("[USER:99]" in r for r in reasons)
|
|
|