aboutsummaryrefslogtreecommitdiffstats
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.py95
1 files changed, 95 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_moderation.py b/packages/meshbay-hub/tests/test_moderation.py
new file mode 100644
index 0000000..68e08c9
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_moderation.py
@@ -0,0 +1,95 @@
+"""Tests for moderation — reports + blocklist."""
+
+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
+
+
+FAKE_HASH = "a" * 64 # valid blake3 hex
+
+
+@pytest.fixture
+async def auth_headers(client):
+ sk_ed = Ed25519PrivateKey.generate()
+ sk_x = X25519PrivateKey.generate()
+ 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()),
+ })
+ r = await client.post("/v1/users/login",
+ json={"username": "mod_admin", "password": "modpass99"})
+ return {"Authorization": f"Bearer {r.json()['access_token']}"}
+
+
+@pytest.mark.asyncio
+async def test_report_content_logged(client):
+ r = await client.post("/v1/reports", json={
+ "content_hash": FAKE_HASH, "reason": "illegal"})
+ assert r.status_code == 201
+ data = r.json()
+ assert data["report_count"] == 1
+ assert data["status"] == "logged"
+
+
+@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"})
+ assert r.json()["status"] == "auto_blocked"
+ assert r.json()["report_count"] == 2
+
+
+@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
+
+ # 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.get(f"/v1/blocklist/check?hash={hash3}")
+ assert r.json()["blocked"] is True
+
+
+@pytest.mark.asyncio
+async def test_admin_add_remove_blocklist(client, auth_headers):
+ hash4 = "d" * 64
+
+ r = await client.post("/v1/admin/blocklist",
+ json={"content_hash": hash4, "reason": "csam"},
+ headers=auth_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)
+ assert r.status_code == 200
+
+ r = await client.get(f"/v1/blocklist/check?hash={hash4}")
+ assert r.json()["blocked"] is False
+
+
+@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
+ await client.post("/v1/admin/blocklist",
+ json={"content_hash": hash5, "reason": "test"},
+ headers=auth_headers)
+ r = await client.get("/v1/blocklist")
+ assert r.status_code == 200
+ assert hash5 in r.json()["hashes"]