(ns ird.p2p.live.smoke-test
"Live smoke tests against a real ird p2p app, mirroring the Swift SDK's
LiveSmokeTests. Gated on IRD_P2P_HOST (the app hostname) and
IRD_P2P_ORIGIN (an origin its allowlist admits); every test skips itself
silently when they are not set. Read-mostly: it fetches the descriptor,
joins a room from two peers over the relay, exchanges one message, opens
a real data channel between two peers (node-datachannel through the
app's signaling and TURN) and round-trips 50,000 bytes over it, and —
if the app has a mailbox — round-trips one deposit.
Run with `npm run test:live`."
(:require [cljs.test :refer [deftest is]]
[cljs.core.async :as async :refer [<!]]
[ird.p2p :as p2p]
[ird.p2p.crypto :as crypto]
[ird.p2p.descriptor :as desc]
[ird.p2p.impl.bytes :as bytes]
[ird.p2p.impl.wrtc :as wrtc]
[ird.p2p.mailbox]
[ird.p2p.test-util :as tu :refer [deftest-async]]))
(def host (tu/env "IRD_P2P_HOST"))
(def origin (tu/env "IRD_P2P_ORIGIN"))
(def enabled? (some? host))
(def app-salt "ird-cljs-live/v1")
(def low "00000000000000000000000000000001")
(def high "0f1e2d3c4b5a69788796a5b4c3d2e1f0")
(defn- app [] (p2p/make-app {:host host :app-salt app-salt :origin origin}))
(defn- direct-app [] (p2p/make-app {:host host :app-salt app-salt :origin origin :pc-factory (wrtc/wrtc-factory)}))
(deftest-async descriptor-join-and-relay-message
{:timeout-ms 40000}
(if-not enabled?
(is true "IRD_P2P_HOST not set — skipping the live smoke test")
(let [a (app)
d (<! (p2p/fetch-descriptor a))]
(is (false? (:is-fallback d)) "the descriptor is served, not synthesized")
(is (= "1" (:version d)))
(is (= "ap2p-signal" (:protocol d)))
(is (= (str "wss://" host "/ws") (get-in d [:endpoints :ws])))
;; two peers join over the relay and exchange a message (data channels off)
(let [secret (crypto/random-secret)
ra (<! (p2p/join a secret {:peer-id low :options {:prefer-data-channel? false}}))
ca (tu/collector (p2p/events ra))]
(is (not (tu/error? ra)) (some-> ra ex-message))
(is (some? (<! (tu/wait-for ca #(and (= :status (:type %)) (= :joined (get-in % [:status :state]))) 10000))))
(let [rb (<! (p2p/join a secret {:peer-id high :options {:prefer-data-channel? false}}))
cb (tu/collector (p2p/events rb))]
(is (some? (<! (tu/wait-for ca #(= {:type :peer-ready :peer high :via :relayed} %) 15000))))
(is (some? (<! (tu/wait-for cb #(= {:type :peer-ready :peer low :via :relayed} %) 15000))))
(is (true? (<! (p2p/send ra high (p2p/json {:text "live"})))))
(is (some? (<! (tu/wait-for cb #(= "live" (get-in % [:message :value :text])) 15000))))
(<! (p2p/leave rb))
(is (some? (<! (tu/wait-for ca #(= {:type :peer-left :peer high} %) 10000))))
(<! (p2p/leave ra)))))))
(deftest-async direct-data-channel-over-the-internet
{:timeout-ms 90000}
(if-not enabled?
(is true "IRD_P2P_HOST not set — skipping the live data-channel test")
;; data-channel preference is on by default once a real :pc-factory is set
(let [a (direct-app)
secret (crypto/random-secret)
ra (<! (p2p/join a secret {:peer-id low}))
ca (tu/collector (p2p/events ra))]
(is (not (tu/error? ra)) (some-> ra ex-message))
(let [rb (<! (p2p/join a secret {:peer-id high}))
cb (tu/collector (p2p/events rb))]
(is (not (tu/error? rb)) (some-> rb ex-message))
(is (some? (<! (tu/wait-for ca #(= {:type :peer-ready :peer high :via :direct} %) 30000))))
(is (some? (<! (tu/wait-for cb #(= {:type :peer-ready :peer low :via :direct} %) 30000))))
;; 50,000 bytes patterned like the Swift smoke test's (0..<50_000).map { $0 % 253 }
(let [big (bytes/bytes->b64 (map #(mod % 253) (range 50000)))]
(is (true? (<! (p2p/send ra high (p2p/data big)))))
(is (some? (<! (tu/wait-for cb #(and (= low (:from %)) (= (p2p/data big) (:message %))) 20000)))))
(<! (p2p/leave ra))
(<! (p2p/leave rb))))))
(deftest-async mailbox-round-trip
{:timeout-ms 40000}
(if-not enabled?
(is true "IRD_P2P_HOST not set — skipping the live mailbox test")
(let [a (app)
d (<! (p2p/fetch-descriptor a))]
(if-not (desc/mailbox-enabled? d)
(is true "the app has no mailbox — skipping the mailbox round trip")
(let [mbx (<! (p2p/open-mailbox a (crypto/random-secret)))]
(is (not (tu/error? mbx)) (some-> mbx ex-message))
(let [r (<! (ird.p2p.mailbox/deposit mbx (p2p/json {:text "offline"})))]
(is (not (tu/error? r)) (some-> r ex-message))
(is (false? (:deduped r)))
(let [{:keys [items]} (<! (ird.p2p.mailbox/replay-all mbx nil))]
(is (= [{:kind :json :value {:text "offline"}}] (mapv :message items))))))))))