File size: 4,049 Bytes
cbad453
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa5b4a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
"""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


@pytest.fixture()
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


@pytest.mark.asyncio
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}"


@pytest.mark.asyncio
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"


@pytest.mark.asyncio
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}"


@pytest.mark.asyncio
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}"


@pytest.mark.asyncio
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"


@pytest.mark.asyncio
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"