(ns ird.p2p.db.access-test
"Write access is possession of the secret. With another secret a peer
derives another identity: by name it lands in a different database, and
by address — joining the very same log — OrbitDB's access controller
refuses its appends while its own key cannot read anything."
(:require [cljs.test :refer [deftest is testing]]
[cljs.core.async :as async :refer [<!]]
[ird.p2p.db.core :as db]
[ird.p2p.db.errors :as err]
[ird.p2p.db.test-util :as tu :refer [deftest-async]]))
(deftest-async wrong-secret-cannot-write
{:timeout-ms 60000}
(if (tu/skip-nodes?)
(is true "IRD_P2P_DB_NO_NODE set — skipping the real-node access test")
(let [secret (db/random-secret)
wrong (db/random-secret)
a (<! (db/open {:secret secret :app-salt "test/v1" :name "chat"}))
_ (is (not (tu/error? a)) (some-> a ex-message))
r1 (<! (db/append a {:text "only for holders" :n 1}))
_ (is (not (tu/error? r1)))]
(testing "by name: another secret is another database"
(let [c1 (<! (db/open {:secret wrong :app-salt "test/v1" :name "chat"}))]
(is (not (tu/error? c1)) (some-> c1 ex-message))
(is (not= (db/address a) (db/address c1)))
(is (not= (:identity a) (:identity c1)))
(is (empty? (<! (db/entries c1))))
(<! (db/close c1))))
(testing "by address: the log can be joined, never written, never read"
(let [c2 (<! (db/open {:secret wrong :app-salt "test/v1" :name "chat"
:address (db/address a) :dial (<! (db/multiaddrs a))}))]
(is (not (tu/error? c2)) (some-> c2 ex-message))
(is (= (db/address a) (db/address c2)))
(is (= [(:orbit-id (:identity a))] (:write (:db c2))) "the manifest's write list names the secret's identity only")
(is (not= (:orbit-id (:identity c2)) (first (:write (:db c2)))))
(let [r (<! (db/append c2 {:text "forged" :n 99}))]
(is (err/append-rejected? r) (pr-str r))
(is (err/db-error? r)))
;; C receives A's entry through sync but cannot decrypt it: nothing on its stream
(let [cc (tu/collector (db/entries-stream c2))
pc (tu/collector (db/peers-stream c2))]
(is (some? (<! (tu/wait-for pc #(= :peer-joined (:type %)) 20000))) "C synced with A")
(<! (async/timeout 500))
(is (empty? @cc) "A's record does not decrypt under C's key")
(is (= 1 (count (<! ((:all (:db c2)))))) "…although C holds the ciphertext entry")
(is (empty? (<! (db/entries c2)))))
(<! (db/close c2))))
(is (= 1 (count (<! (db/entries a)))) "A's log is untouched")
(<! (db/close a)))))