aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/golden/dispatch.json460
-rw-r--r--packages/meshbay-node/tests/test_chat_purge.py110
2 files changed, 534 insertions, 36 deletions
diff --git a/packages/meshbay-node/tests/golden/dispatch.json b/packages/meshbay-node/tests/golden/dispatch.json
index 44d030a..a39c57d 100644
--- a/packages/meshbay-node/tests/golden/dispatch.json
+++ b/packages/meshbay-node/tests/golden/dispatch.json
@@ -60,6 +60,14 @@
"_admin_exec_chat_link_preview"
]
},
+ "chat_purge": {
+ "audit": [],
+ "log": [],
+ "sent": [],
+ "spawned": [
+ "_admin_exec_chat_purge"
+ ]
+ },
"dir_delete": {
"audit": [],
"log": [],
@@ -2012,7 +2020,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2335,7 +2343,7 @@
"subject": "gggggggggggggggggggggggggggggggg",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2354,7 +2362,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2373,7 +2381,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2392,7 +2400,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2662,7 +2670,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2676,7 +2684,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2690,7 +2698,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2704,7 +2712,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2718,7 +2726,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2732,7 +2740,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2746,7 +2754,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -2760,7 +2768,7 @@
"messages": [],
"req_id": 4242,
"type": "chat_hist_resp",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -3817,6 +3825,386 @@
],
"spawned": []
},
+ "chat_purge | challenged | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | challenged | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | challenged | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | challenged | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | fresh | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | fresh | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | fresh | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | fresh | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | member | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "No authorized key for this",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | member | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "No authorized key for this",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | member | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "No authorized key for this",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | member | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "No authorized key for this",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | operator | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "group_id": "gggggggggggggggggggggggggggggggg",
+ "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=",
+ "nonce": "<volatile>",
+ "op": "chat_purge",
+ "op_id": "<volatile>",
+ "req_id": 4242,
+ "subject": "gggggggggggggggggggggggggggggggg",
+ "ts": "<volatile>",
+ "type": "admin_challenge",
+ "v": "6.1"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | operator | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "group_id": "gggggggggggggggggggggggggggggggg",
+ "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=",
+ "nonce": "<volatile>",
+ "op": "chat_purge",
+ "op_id": "<volatile>",
+ "req_id": 4242,
+ "subject": "gggggggggggggggggggggggggggggggg",
+ "ts": "<volatile>",
+ "type": "admin_challenge",
+ "v": "6.1"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | operator | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "group_id": "gggggggggggggggggggggggggggggggg",
+ "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=",
+ "nonce": "<volatile>",
+ "op": "chat_purge",
+ "op_id": "<volatile>",
+ "req_id": 4242,
+ "subject": "gggggggggggggggggggggggggggggggg",
+ "ts": "<volatile>",
+ "type": "admin_challenge",
+ "v": "6.1"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge | operator | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "group_id": "gggggggggggggggggggggggggggggggg",
+ "node_pk": "iojj3XQJ8ZX9UtstPLpdcspnCb8dlBIb83SIAbQPb1w=",
+ "nonce": "<volatile>",
+ "op": "chat_purge",
+ "op_id": "<volatile>",
+ "req_id": 4242,
+ "subject": "gggggggggggggggggggggggggggggggg",
+ "ts": "<volatile>",
+ "type": "admin_challenge",
+ "v": "6.1"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | challenged | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | challenged | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | challenged | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | challenged | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | fresh | bare": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | fresh | lists": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | fresh | numbers": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | fresh | strings": {
+ "audit": [],
+ "log": [],
+ "sent": [
+ {
+ "detail": "Handshake required",
+ "req_id": 4242,
+ "type": "error"
+ }
+ ],
+ "spawned": []
+ },
+ "chat_purge_ack | member | bare": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | member | lists": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | member | numbers": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | member | strings": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | operator | bare": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | operator | lists": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | operator | numbers": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
+ "chat_purge_ack | operator | strings": {
+ "audit": [],
+ "log": [
+ "WARNING Unknown MNP message type on DataChannel: %s"
+ ],
+ "sent": [],
+ "spawned": []
+ },
"client_diag | challenged | bare": {
"audit": [],
"log": [],
@@ -9771,7 +10159,7 @@
"subject": "link:gggggggggggggggggggggggggggggggg",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -11870,7 +12258,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -11889,7 +12277,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -11908,7 +12296,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13009,7 +13397,7 @@
"req_id": 4242,
"token": null,
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13024,7 +13412,7 @@
"x"
],
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13037,7 +13425,7 @@
"req_id": 4242,
"token": 7,
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13050,7 +13438,7 @@
"req_id": 4242,
"token": "x",
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13063,7 +13451,7 @@
"req_id": 4242,
"token": null,
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13078,7 +13466,7 @@
"x"
],
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13091,7 +13479,7 @@
"req_id": 4242,
"token": 7,
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13104,7 +13492,7 @@
"req_id": 4242,
"token": "x",
"type": "pong",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13439,7 +13827,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13458,7 +13846,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13477,7 +13865,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13812,7 +14200,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13831,7 +14219,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -13850,7 +14238,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -14185,7 +14573,7 @@
"subject": "['x']",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -14204,7 +14592,7 @@
"subject": "7",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -14223,7 +14611,7 @@
"subject": "x",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -16850,7 +17238,7 @@
"subject": "{\"language\":null,\"token\":null}",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
@@ -16893,7 +17281,7 @@
"subject": "{\"language\":\"x\",\"token\":\"sha256:2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881\"}",
"ts": "<volatile>",
"type": "admin_challenge",
- "v": "6.0"
+ "v": "6.1"
}
],
"spawned": []
diff --git a/packages/meshbay-node/tests/test_chat_purge.py b/packages/meshbay-node/tests/test_chat_purge.py
new file mode 100644
index 0000000..37bd954
--- /dev/null
+++ b/packages/meshbay-node/tests/test_chat_purge.py
@@ -0,0 +1,110 @@
+"""
+The operator's chat purge (`chat_purge`, MNP 6.1): every stored message of the
+group goes, signed like every operator instruction, and every connected member
+is told so their open chat empties.
+
+Driven through `_do_admin_response`, as in `test_admin_ops_mnp.py`: the node
+rebuilds the transcript from the operation it holds, and skipping that would
+test nothing.
+"""
+
+import base64
+
+import pytest
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from meshbay_common.adminop import OP_CHAT_PURGE, admin_transcript
+from meshbay_node import ops
+from meshbay_node.chat import FORMAT_SEALED_V1, ChatStore
+from meshbay_node.roster import open_roster
+from test_admin_ops_mnp import GROUP, _drain, _last, _session
+
+
+@pytest.fixture
+async def roster(tmp_path):
+ r = await open_roster(tmp_path)
+ yield r
+ await r.close()
+
+
+async def _with_chat(tmp_path, roster, *, operator=True, messages=2):
+ session = await _session(tmp_path, roster, operator=operator)
+ store = ChatStore(tmp_path / "chat.db")
+ await store.open()
+ for i in range(messages):
+ await store.save_message(
+ "grenet", 0, b"ct", format=FORMAT_SEALED_V1, epoch=1,
+ device=b"d" * 32, nonce=bytes([i]) * 12, sig=b"s" * 64)
+ session.state["groups_ctx"][GROUP]["chat_store"] = store
+ # A second member connected to the group, to see the broadcast arrive.
+ other = []
+ session.state["groups_ctx"][GROUP]["_peers"] = {
+ "grenet": session, "bob": type("Peer", (), {"_send": other.append})()}
+ return session, store, other
+
+
+def _sign(session, challenge, sk=None):
+ transcript = admin_transcript(
+ op=OP_CHAT_PURGE, node_pk_b64=session._node_pk_b64(), group_id=GROUP,
+ subject=challenge["subject"], nonce=base64.b64decode(challenge["nonce"]),
+ ts=challenge["ts"])
+ sig = (sk or session.sk_op).sign(transcript)
+ session._do_admin_response({"op_id": challenge["op_id"],
+ "signature": base64.b64encode(sig).decode()})
+
+
+async def test_a_signed_purge_empties_the_chat_and_tells_the_group(tmp_path, roster):
+ session, store, other = await _with_chat(tmp_path, roster)
+
+ session._do_chat_purge({})
+ challenge = _last(session)
+ assert challenge["type"] == "admin_challenge"
+ assert challenge["op"] == OP_CHAT_PURGE and challenge["subject"] == GROUP
+ _sign(session, challenge)
+ await _drain(session)
+
+ assert await store.message_count() == 0
+ assert other[-1]["type"] == "chat_purge_ack" and other[-1]["removed"] == 2
+ assert _last(session)["type"] == "chat_purge_ack"
+ await store.close()
+
+
+async def test_the_purge_names_the_connection_group_and_no_other(tmp_path, roster):
+ """A group named in the message is not the one purged: the ack goes to this
+ connection's group, and so does the operation."""
+ session, store, _ = await _with_chat(tmp_path, roster)
+
+ session._do_chat_purge({"group_id": "h" * 32})
+
+ assert _last(session)["subject"] == GROUP
+ await store.close()
+
+
+async def test_a_signature_from_a_non_operator_key_purges_nothing(tmp_path, roster):
+ session, store, other = await _with_chat(tmp_path, roster)
+
+ session._do_chat_purge({})
+ _sign(session, _last(session), sk=Ed25519PrivateKey.generate())
+ await _drain(session)
+
+ assert _last(session)["type"] == "error"
+ assert await store.message_count() == 2
+ assert other == []
+ await store.close()
+
+
+async def test_no_challenge_without_an_operator(tmp_path, roster):
+ session, store, _ = await _with_chat(tmp_path, roster, operator=False)
+
+ session._do_chat_purge({})
+
+ assert _last(session)["type"] == "error"
+ assert session._admin_ops == {}
+ await store.close()
+
+
+async def test_a_group_without_a_chat_store_says_so(tmp_path, roster):
+ session = await _session(tmp_path, roster, operator=True)
+
+ with pytest.raises(ops.OpError) as e:
+ await ops.purge_chat(session.state, GROUP)
+ assert e.value.status == 404