diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_daemon.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_daemon.py | 77 |
1 files changed, 61 insertions, 16 deletions
diff --git a/packages/meshbay-node/tests/test_daemon.py b/packages/meshbay-node/tests/test_daemon.py index 1c5a07e..60ef11f 100644 --- a/packages/meshbay-node/tests/test_daemon.py +++ b/packages/meshbay-node/tests/test_daemon.py @@ -21,7 +21,6 @@ from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, Keys from meshbay_node.daemon import NodeDaemon from meshbay_node.indexer import DirectoryIndexer - def _mock_keystore_keys(sk_ed): """Create a mock keystore with real Ed25519 + X25519 key material.""" sk_x = X25519PrivateKey.generate() @@ -35,23 +34,33 @@ def _mock_keystore_keys(sk_ed): mock_keys.pk_x25519_b64 = base64.b64encode(pk_x_raw).decode() return mock_keys +def _free_port() -> int: + """ + A port nobody else in the session is on. + + These tests start the real admin UI server. Hardcoding 28000 made them fail + with EADDRINUSE whenever another test file had a node running — which is why + the full suite failed while each file passed on its own. + """ + import socket + with socket.socket() as s: + s.bind(("127.0.0.1", 0)) + return s.getsockname()[1] + @pytest.fixture def sk_hub(): return Ed25519PrivateKey.generate() - @pytest.fixture def hub_pk_pem(sk_hub): return sk_hub.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) - @pytest.fixture def gek(): return generate_gek() - @pytest.fixture def shared_dir(tmp_path): d = tmp_path / "shared" @@ -60,26 +69,22 @@ def shared_dir(tmp_path): (d / "hello.txt").write_bytes(b"hello daemon test " * 50) return d - @pytest.fixture def node_config(tmp_path, shared_dir): return Config( hub=HubConfig(url="http://localhost:9999", username="testuser"), - node=NodeConfig(port=29000, quic_port=29010, http_port=29001, ui_port=28000), + node=NodeConfig(quic_port=_free_port(), ui_port=_free_port()), groups=[GroupConfig( id="g" * 32, name="test-group", shared_dir=str(shared_dir), visibility="private", - port=29000, quic_port=29010, - http_port=29001, )], keystore=KeystoreConfig(path=tmp_path / "keystore.enc"), data_dir=tmp_path / "data", ) - @pytest.mark.asyncio async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem): """Daemon creates ChatStore for each group and shuts down cleanly.""" @@ -130,7 +135,14 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem) assert daemon._chat_stores[group_id]._db is not None if daemon._webrtc: - assert "chat_store" in daemon._webrtc._ctx + # Finding H1: chat_store must live in the per-group context, never on + # the shared transport context. Hoisting the first group's store + # transport-wide sent every group's chat to one database and served it + # back to members of every other group. + assert "chat_store" not in daemon._webrtc._ctx + groups_ctx = daemon._webrtc._ctx["groups"] + assert groups_ctx[group_id]["chat_store"] is daemon._chat_stores[group_id] + assert "hub_ws" in daemon._webrtc._ctx assert "node_user_id" in daemon._webrtc._ctx assert daemon._webrtc._ctx["node_user_id"] == "user123" @@ -146,7 +158,6 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem) for store in daemon._chat_stores.values(): assert store._db is None - @pytest.mark.asyncio async def test_daemon_no_groups_exits(tmp_path): """Daemon with no valid groups exits cleanly.""" @@ -188,19 +199,18 @@ async def test_daemon_no_groups_exits(tmp_path): assert len(daemon._chat_stores) == 0 - @pytest.mark.asyncio async def test_daemon_index_change_pushes_to_peers(tmp_path, shared_dir, gek, hub_pk_pem): """Index change callback pushes updated index to WebRTC peers.""" config = Config( hub=HubConfig(url="http://localhost:9999", username="testuser"), - node=NodeConfig(port=29000, quic_port=29010, http_port=29001, ui_port=28000), + node=NodeConfig(quic_port=_free_port(), ui_port=_free_port()), groups=[GroupConfig( id="a" * 32, name="test-group", shared_dir=str(shared_dir), visibility="private", - port=29000, quic_port=29010, http_port=29001, + quic_port=29010, )], keystore=KeystoreConfig(path=tmp_path / "keystore.enc"), data_dir=tmp_path / "data", @@ -232,10 +242,45 @@ async def test_daemon_index_change_pushes_to_peers(tmp_path, shared_dir, gek, hu assert msg["group_id"] == "a" * 32 assert len(msg["entries"]) == indexer.index.count + # Finding H7: this group is private, so its content hashes must NOT be + # registered with the hub. The test previously asserted the opposite — + # publishing a fingerprint of every private file was treated as expected + # behaviour. Index push to members is unaffected (asserted above). + await asyncio.sleep(0.1) + daemon._hub.register_swarm.assert_not_called() + +@pytest.mark.asyncio +async def test_daemon_index_change_registers_swarm_for_public_group( + tmp_path, shared_dir, gek, hub_pk_pem): + """Public groups still register content hashes with the hub swarm (H7).""" + config = Config( + hub=HubConfig(url="http://localhost:9999", username="testuser"), + node=NodeConfig(quic_port=_free_port(), ui_port=_free_port()), + groups=[GroupConfig( + id="a" * 32, + name="public-group", + shared_dir=str(shared_dir), + visibility="public", + quic_port=29010, + )], + keystore=KeystoreConfig(path=tmp_path / "keystore.enc"), + data_dir=tmp_path / "data", + ) + daemon = NodeDaemon(config) + daemon._hub = AsyncMock() + daemon._hub.register_swarm = AsyncMock(return_value=2) + daemon._state["endpoint_hint"] = "node123" + + indexer = DirectoryIndexer( + root=shared_dir, group_id="a" * 32, + sk_node=Ed25519PrivateKey.generate(), gek=gek) + await indexer.initial_scan() + + await daemon._on_index_change(indexer) + await asyncio.sleep(0.1) daemon._hub.register_swarm.assert_called_once() - call_args = daemon._hub.register_swarm.call_args - assert len(call_args[0][0]) == indexer.index.count + assert len(daemon._hub.register_swarm.call_args[0][0]) == indexer.index.count @pytest.mark.asyncio |