Spaces:
Running on Zero
Running on Zero
| """Integration test: cross-node chat delivery (M10). | |
| Verifies that chat.send from node A to node B: | |
| - calls chat.deliver on B's bus | |
| - B stores the message | |
| - chat.history on B shows the message | |
| - delivery status is "delivered" (not "queued" or "direct") | |
| Uses in-process InMemoryTransport so no relay/HTTP needed. | |
| """ | |
| from __future__ import annotations | |
| import pytest | |
| from hearthnet.bus import InMemoryTransport | |
| def two_nodes(): | |
| from hearthnet.node import HearthNode | |
| net = InMemoryTransport() | |
| alice = HearthNode("node-alice", "Alice", "ed25519:test", transport=net) | |
| alice.install_demo_services() | |
| net.register(alice.bus) | |
| bob = HearthNode("node-bob", "Bob", "ed25519:test", transport=net) | |
| bob.install_demo_services() | |
| net.register(bob.bus) | |
| return alice, bob | |
| async def test_chat_send_cross_node_delivers(two_nodes): | |
| alice, bob = two_nodes | |
| r = await alice.bus.call( | |
| "chat.send", (1, 0), {"input": {"recipient": "node-bob", "body": "Hello Bob!"}} | |
| ) | |
| status = r.get("output", {}).get("delivered") | |
| assert status == "delivered", f"Expected 'delivered', got {status!r}" | |
| async def test_chat_history_shows_received_message(two_nodes): | |
| alice, bob = two_nodes | |
| await alice.bus.call( | |
| "chat.send", (1, 0), {"input": {"recipient": "node-bob", "body": "Hi from Alice"}} | |
| ) | |
| r = await bob.bus.call("chat.history", (1, 0), {"input": {"peer": "node-alice"}}) | |
| msgs = r.get("output", {}).get("messages", []) | |
| assert len(msgs) == 1, f"Expected 1 message in Bob's history, got {len(msgs)}" | |
| assert msgs[0]["body"] == "Hi from Alice" | |
| assert msgs[0]["from"] == "node-alice" | |
| async def test_self_send_returns_direct(two_nodes): | |
| alice, _ = two_nodes | |
| r = await alice.bus.call( | |
| "chat.send", (1, 0), {"input": {"recipient": "node-alice", "body": "hi me"}} | |
| ) | |
| status = r.get("output", {}).get("delivered") | |
| assert status == "direct", f"Expected 'direct' for self-send, got {status!r}" | |
| async def test_send_to_unknown_node_returns_queued(two_nodes): | |
| alice, _ = two_nodes | |
| r = await alice.bus.call( | |
| "chat.send", (1, 0), {"input": {"recipient": "node-nobody", "body": "hello?"}} | |
| ) | |
| status = r.get("output", {}).get("delivered") | |
| assert status == "queued", f"Expected 'queued' for unknown node, got {status!r}" | |
| async def test_manually_wired_chat_service_delivers(): | |
| """Regression: a ChatService registered manually (as app.py / the HF Space | |
| entry point does) must receive a ``bus=`` reference, otherwise | |
| ``_deliver_remote`` short-circuits to ``"queued"`` before attempting | |
| delivery. Mirrors the app.py wiring to guard against that regression. | |
| """ | |
| from hearthnet.node import HearthNode | |
| from hearthnet.services.chat.service import ChatService | |
| net = InMemoryTransport() | |
| alice = HearthNode("node-alice", "Alice", "ed25519:test", transport=net) | |
| # Wire chat exactly like app.py: explicit bus= argument is required. | |
| alice.bus.register_service(ChatService(alice.node_id, bus=alice.bus)) | |
| net.register(alice.bus) | |
| bob = HearthNode("node-bob", "Bob", "ed25519:test", transport=net) | |
| bob.bus.register_service(ChatService(bob.node_id, bus=bob.bus)) | |
| net.register(bob.bus) | |
| r = await alice.bus.call( | |
| "chat.send", (1, 0), {"input": {"recipient": "node-bob", "body": "wired"}} | |
| ) | |
| assert r.get("output", {}).get("delivered") == "delivered" | |
| async def test_chat_service_without_bus_cannot_deliver(): | |
| """A ChatService constructed without a bus reference can only queue — | |
| documents the failure mode the app.py wiring fix prevents. | |
| """ | |
| from hearthnet.services.chat.service import ChatService | |
| svc = ChatService("node-alice") # no bus= | |
| status = await svc._deliver_remote( | |
| {"to": "node-bob", "from": "node-alice", "body": "x", "event_id": "e1"} | |
| ) | |
| assert status == "queued" | |