Repository: magic-wormhole/magic-wormhole

Issue:
Hints status only tracks some hints
https://github.com/magic-wormhole/magic-wormhole/pull/645 adds hints to the status, but only reveals the latest hints -- should probably be a Set() not a List()


Discussion:

Patch:
diff --git a/src/wormhole/_dilation/manager.py b/src/wormhole/_dilation/manager.py
index ec3cd6b6..e88f7dfb 100644
--- a/src/wormhole/_dilation/manager.py
+++ b/src/wormhole/_dilation/manager.py
@@ -450,7 +450,7 @@ def _hint_status(self, hints):
         self._maybe_send_status(
             evolve(
                 self._latest_status,
-                hints=hints,
+                hints=set(hints).union(self._latest_status.hints),
             )
         )
 
diff --git a/src/wormhole/_status.py b/src/wormhole/_status.py
index 209edb66..d19bdbc6 100644
--- a/src/wormhole/_status.py
+++ b/src/wormhole/_status.py
@@ -171,4 +171,4 @@ class DilationStatus:
     peer_connection: PeerConnection = NoPeer()
 
     # available methods to get to peer
-    hints: list[DilationHint] = Factory(list)
+    hints: set[DilationHint] = Factory(set)


Test patch:
diff --git a/src/wormhole/test/dilate/test_manager.py b/src/wormhole/test/dilate/test_manager.py
index d327b2e6..f9bfd62a 100644
--- a/src/wormhole/test/dilate/test_manager.py
+++ b/src/wormhole/test/dilate/test_manager.py
@@ -185,20 +185,41 @@ def test_transit_relay():
 FOLLOWER = "123456abcdef"
 
 
-class ReactorOnlyTime:
+class FakeHost:
+    def __init__(self, port):
+        self.port = port
+
+
+class FakePort:
+    def __init__(self, port):
+        self.port = port
+
+    def getHost(self):
+        return FakeHost(self.port)
+
+
+class ReactorFake:
     """
-    Provide a reactor-like mock that at least has seconds()
+    Provide a reactor-like mock
     """
-    # not ideal, but prior to this the "reactor" below was literally
-    # just "object()"
+    # not ideal, but prior to this the "reactor" in these tests was
+    # literally just "object()"
+
+    def __init__(self):
+        self.listens = []
 
     def seconds(self):
         return 42
 
-    def callLater(self, interval, callable):
+    def callLater(self, interval, callable, *args):
         # should return a DelayedCall
         return mock.Mock()
 
+    def listenTCP(self, *args, **kw):
+        # returns an IListenPort
+        self.listens.append((args, kw))
+        return FakePort(1234)
+
 
 def make_manager(leader=True):
     h = Holder()
@@ -210,7 +231,7 @@ def make_manager(leader=True):
         side = FOLLOWER
     h.key = b"\x00" * 32
     h.relay = None
-    h.reactor = ReactorOnlyTime()
+    h.reactor = ReactorFake()
     h.clock = Clock()
     h.eq = EventualQueue(h.clock)
     term = mock.Mock(side_effect=lambda: True)  # one write per Eventual tick
@@ -655,4 +676,46 @@ def test_unknown_message(observe_errors):
     m.received_dilation_message(dict_to_bytes(dict(type="unknown")))
     observe_errors.flush(UnknownDilationMessageType)
 
+
+def test_status_hints():
+    """
+    Rudimentary test proving that sending two different hint updates
+    results in a final update with both hints
+    """
+    m, h = make_manager()
+
+    # better if we could use Dilator -> dilate() -> etc
+    updates = []
+    def status_update(st):
+        updates.append(st)
+    m._status = status_update
+
+    m.start()
+    m.rx_PLEASE({"side": "fake side"})
+    m.rx_HINTS({"hints": [
+        {
+            "type": "direct-tcp-v1",
+            "hostname": "fake.host",
+            "port": 1,
+        }
+    ]})
+    m.rx_HINTS({"hints": [
+        {
+            "type": "direct-tcp-v1",
+            "hostname": "fake.host",
+            "port": 2,
+        }
+    ]})
+
+    # we've done two (separate!) hint updates, our last status update
+    # should show both of them
+
+    last_hints = [
+        hint.url
+        for hint in updates[-1].hints
+    ]
+    assert "fake.host:1" in last_hints
+    assert "fake.host:2" in last_hints
+
+
 # TODO: test transit relay is used