(ns ird.p2p.db.mdns-test
"Two real Helia/OrbitDB nodes that are never told about each other,
always on: both open with :mdns? true and no :dial, find each other
through mDNS alone, exchange heads and replicate sealed records; then one
restarts (same :path, same peer id) and mDNS alone brings it back. If your
environment cannot bind loopback tcp, set IRD_P2P_DB_NO_NODE=1 to skip."
(:require [cljs.test :refer [deftest is testing]]
[cljs.core.async :as async :refer [<!]]
[ird.p2p.db.core :as db]
[ird.p2p.db.test-util :as tu :refer [deftest-async]]))
(deftest-async two-nodes-meet-through-mdns-alone
{:timeout-ms 90000}
(if (tu/skip-nodes?)
(is true "IRD_P2P_DB_NO_NODE set — skipping the real mDNS test")
(let [dir (tu/tmp-dir "ird-p2p-db-mdns")
secret (db/random-secret)
a (<! (db/open {:secret secret :app-salt "test/v1" :name "lan" :mdns? true}))
_ (is (not (tu/error? a)) (some-> a ex-message))
r1 (<! (db/append a {:text "before B exists" :n 1}))
_ (is (not (tu/error? r1)) (some-> r1 ex-message))
b (<! (db/open {:secret secret :app-salt "test/v1" :name "lan" :mdns? true :path dir}))
_ (is (not (tu/error? b)) (some-> b ex-message))
ca (tu/collector (db/entries-stream a))
cb (tu/collector (db/entries-stream b))
pa (tu/collector (db/peers-stream a))
pb (tu/collector (db/peers-stream b))]
(is (not= (db/peer-id a) (db/peer-id b)))
(testing "discovery: nobody dialed anybody"
(is (some? (<! (tu/wait-for pb #(and (= :peer-joined (:type %)) (= (db/peer-id a) (:peer %))) 30000)))
"B found A through mDNS and exchanged heads")
(is (some? (<! (tu/wait-for pa #(and (= :peer-joined (:type %)) (= (db/peer-id b) (:peer %))) 30000)))
"A found B through mDNS and exchanged heads"))
(testing "replication through the discovered connection"
(is (some? (<! (tu/wait-for cb #(= 1 (get-in % [:payload :n])) 20000))) "B caught up with A's history")
(let [r2 (<! (db/append a {:text "live" :n 2}))]
(is (not (tu/error? r2)) (some-> r2 ex-message))
(is (some? (<! (tu/wait-for cb #(= 2 (get-in % [:payload :n])) 20000))) "B received A's live record")
(is (= (:hash r2) (:hash (last @cb)))))
(let [r3 (<! (db/append b {:text "and back" :n 3}))]
(is (not (tu/error? r3)) (some-> r3 ex-message))
(is (some? (<! (tu/wait-for ca #(= 3 (get-in % [:payload :n])) 20000))) "A received B's record"))
(let [ea (<! (db/entries a)) eb (<! (db/entries b))]
(is (= [1 2 3] (mapv #(get-in % [:payload :n]) ea)))
(is (= ea eb))))
(testing "a restarted peer — already known, same peer id — is found and dialed again"
(let [pid (db/peer-id b)
_ (<! (db/close b))
r4 (<! (db/append a {:text "while B was away" :n 4}))
_ (is (not (tu/error? r4)) (some-> r4 ex-message))
b2 (<! (db/open {:secret secret :app-salt "test/v1" :name "lan" :mdns? true :path dir}))
_ (is (not (tu/error? b2)) (some-> b2 ex-message))
cb2 (tu/collector (db/entries-stream b2))
pb2 (tu/collector (db/peers-stream b2))]
(is (= pid (db/peer-id b2)))
(is (some? (<! (tu/wait-for pb2 #(and (= :peer-joined (:type %)) (= (db/peer-id a) (:peer %))) 30000)))
"mDNS alone reconnected them")
(is (some? (<! (tu/wait-for cb2 #(= 4 (get-in % [:payload :n])) 20000))) "B caught up with what it missed")
(is (= (<! (db/entries a)) (<! (db/entries b2))))
(is (not-any? #(= :sync-error (:type %)) @pb2))
(<! (db/close b2))))
(is (not-any? #(= :sync-error (:type %)) (concat @pa @pb)))
(<! (db/close a))
(tu/rm-rf dir))))