summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_moderation.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_moderation.py')
-rw-r--r--packages/meshbay-hub/tests/test_moderation.py132
1 files changed, 87 insertions, 45 deletions
diff --git a/packages/meshbay-hub/tests/test_moderation.py b/packages/meshbay-hub/tests/test_moderation.py
index 93d23cd..6848929 100644
--- a/packages/meshbay-hub/tests/test_moderation.py
+++ b/packages/meshbay-hub/tests/test_moderation.py
@@ -1,34 +1,58 @@
-"""Tests for moderation — reports + blocklist."""
+"""Tests for moderation — reports + blocklist.
+
+Reporting requires a signed-in account (it used to be anonymous, which made it a
+network-wide censorship primitive), the auto-block threshold counts *distinct
+reporting accounts*, and the whole flow is refused when the hub has public groups
+switched off.
+"""
import pytest
-from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
-from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
-from meshbay_common.crypto import pk_to_b64
from meshbay_hub.api.deps import set_admin_usernames
-
FAKE_HASH = "a" * 64 # valid blake3 hex
-@pytest.fixture
-async def auth_headers(client):
- sk_ed = Ed25519PrivateKey.generate()
- sk_x = X25519PrivateKey.generate()
+async def _register_and_login(client, username: str) -> dict:
await client.post("/v1/users/register", json={
- "username": "mod_admin", "email": "m@t.com", "password": "modpass99",
- "pk_user_ed25519": pk_to_b64(sk_ed.public_key()),
- "pk_user_x25519": pk_to_b64(sk_x.public_key()),
+ "username": username, "email": f"{username}@t.com",
+ "password": "reporter99pw",
})
r = await client.post("/v1/users/login",
- json={"username": "mod_admin", "password": "modpass99"})
- set_admin_usernames(["mod_admin"])
+ json={"username": username, "password": "reporter99pw"})
return {"Authorization": f"Bearer {r.json()['access_token']}"}
+@pytest.fixture
+async def reporter(client):
+ return await _register_and_login(client, "reporter_one")
+
+
+@pytest.fixture
+async def admin_headers(client):
+ headers = await _register_and_login(client, "mod_admin")
+ set_admin_usernames(["mod_admin"])
+ return headers
+
+
@pytest.mark.asyncio
-async def test_report_content_logged(client):
+async def test_report_requires_auth(client):
+ # No credentials at all — FastAPI rejects the missing header before the body.
r = await client.post("/v1/reports", json={
"content_hash": FAKE_HASH, "reason": "illegal"})
+ assert r.status_code in (401, 422)
+
+ # A bogus token is a clean 401.
+ r = await client.post("/v1/reports",
+ json={"content_hash": FAKE_HASH, "reason": "illegal"},
+ headers={"Authorization": "Bearer not-a-real-token"})
+ assert r.status_code == 401
+
+
+@pytest.mark.asyncio
+async def test_report_content_logged(client, reporter):
+ r = await client.post("/v1/reports",
+ json={"content_hash": FAKE_HASH, "reason": "illegal"},
+ headers=reporter)
assert r.status_code == 201
data = r.json()
assert data["report_count"] == 1
@@ -36,43 +60,68 @@ async def test_report_content_logged(client):
@pytest.mark.asyncio
-async def test_auto_block_on_threshold(client):
- """Second report triggers auto-block."""
- hash2 = "b" * 64
- await client.post("/v1/reports", json={"content_hash": hash2, "reason": "spam"})
- r = await client.post("/v1/reports", json={"content_hash": hash2, "reason": "spam"})
+async def test_same_reporter_cannot_walk_the_threshold(client, reporter):
+ h = "b" * 64
+ for _ in range(5):
+ r = await client.post("/v1/reports",
+ json={"content_hash": h, "reason": "spam"},
+ headers=reporter)
+ assert r.json()["report_count"] == 1
+ assert r.json()["status"] == "already_reported"
+
+ check = await client.get(f"/v1/blocklist/check?hash={h}")
+ assert check.json()["blocked"] is False
+
+
+@pytest.mark.asyncio
+async def test_auto_block_on_distinct_reporters(client):
+ h = "c" * 64
+ for i in range(3):
+ headers = await _register_and_login(client, f"rep_{i}")
+ r = await client.post("/v1/reports",
+ json={"content_hash": h, "reason": "illegal"},
+ headers=headers)
assert r.json()["status"] == "auto_blocked"
- assert r.json()["report_count"] == 2
+ assert r.json()["report_count"] == 3
+
+ check = await client.get(f"/v1/blocklist/check?hash={h}")
+ assert check.json()["blocked"] is True
@pytest.mark.asyncio
-async def test_blocklist_check(client):
- hash3 = "c" * 64
- # Not blocked yet
- r = await client.get(f"/v1/blocklist/check?hash={hash3}")
- assert r.json()["blocked"] is False
+async def test_reports_refused_when_public_groups_disabled(client, reporter, admin_headers):
+ await client.patch("/v1/admin/settings",
+ json={"allow_public_groups": False},
+ headers=admin_headers)
- # Report twice to auto-block
- await client.post("/v1/reports", json={"content_hash": hash3, "reason": "illegal"})
- await client.post("/v1/reports", json={"content_hash": hash3, "reason": "illegal"})
+ r = await client.post("/v1/reports",
+ json={"content_hash": "d" * 64, "reason": "illegal"},
+ headers=reporter)
+ assert r.status_code == 403
- r = await client.get(f"/v1/blocklist/check?hash={hash3}")
- assert r.json()["blocked"] is True
+
+@pytest.mark.asyncio
+async def test_invalid_hash_rejected(client, reporter):
+ r = await client.post("/v1/reports",
+ json={"content_hash": "not-a-valid-blake3-hash",
+ "reason": "test"},
+ headers=reporter)
+ assert r.status_code == 422
@pytest.mark.asyncio
-async def test_admin_add_remove_blocklist(client, auth_headers):
- hash4 = "d" * 64
+async def test_admin_add_remove_blocklist(client, admin_headers):
+ hash4 = "e" * 64
r = await client.post("/v1/admin/blocklist",
json={"content_hash": hash4, "reason": "csam"},
- headers=auth_headers)
+ headers=admin_headers)
assert r.status_code == 201
r = await client.get(f"/v1/blocklist/check?hash={hash4}")
assert r.json()["blocked"] is True
- r = await client.delete(f"/v1/admin/blocklist/{hash4}", headers=auth_headers)
+ r = await client.delete(f"/v1/admin/blocklist/{hash4}", headers=admin_headers)
assert r.status_code == 200
r = await client.get(f"/v1/blocklist/check?hash={hash4}")
@@ -80,18 +129,11 @@ async def test_admin_add_remove_blocklist(client, auth_headers):
@pytest.mark.asyncio
-async def test_invalid_hash_rejected(client):
- r = await client.post("/v1/reports", json={
- "content_hash": "not-a-valid-blake3-hash", "reason": "test"})
- assert r.status_code == 422
-
-
-@pytest.mark.asyncio
-async def test_full_blocklist(client, auth_headers):
- hash5 = "e" * 64
+async def test_full_blocklist(client, admin_headers):
+ hash5 = "f" * 64
await client.post("/v1/admin/blocklist",
json={"content_hash": hash5, "reason": "test"},
- headers=auth_headers)
+ headers=admin_headers)
r = await client.get("/v1/blocklist")
assert r.status_code == 200
assert hash5 in r.json()["hashes"]