diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-09 05:17:28 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-09 05:17:28 +0200 |
| commit | 42556800d103ede20b4e97f2d91d20bbc0000c1e (patch) | |
| tree | 1a392221995f4e8092bd20fb3acdd95a28c1d1f1 /packages/meshbay-hub/tests/test_moderation.py | |
| parent | 1734c668406c66e2be63e0c6999b4b2af2f60808 (diff) | |
| download | meshbay-42556800d103ede20b4e97f2d91d20bbc0000c1e.tar.gz | |
feat(hub): add moderation — content blocklist + reports — 5.9
DB: ContentReport + ContentBlocklist tables.
POST /v1/reports: public endpoint, auto-blocks after 2 reports.
GET /v1/blocklist/check: node sync check before serving public content.
GET /v1/blocklist: full list for node startup sync.
GET|POST|DELETE /v1/admin/blocklist: admin management.
6/6 tests. Full suite: 59/59.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests/test_moderation.py')
| -rw-r--r-- | packages/meshbay-hub/tests/test_moderation.py | 95 |
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"] |