sync_test.cljs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. (ns frontend.handler.db-based.sync-test
  2. (:require [cljs.test :refer [deftest is async]]
  3. [clojure.string :as string]
  4. [frontend.db :as db]
  5. [frontend.handler.db-based.sync :as db-sync]
  6. [frontend.handler.user :as user-handler]
  7. [frontend.state :as state]
  8. [logseq.db :as ldb]
  9. [logseq.db.sqlite.util :as sqlite-util]
  10. [promesa.core :as p]))
  11. (def ^:private test-text-encoder (js/TextEncoder.))
  12. (defn- frame-bytes [^js data]
  13. (let [len (.-byteLength data)
  14. out (js/Uint8Array. (+ 4 len))
  15. view (js/DataView. (.-buffer out))]
  16. (.setUint32 view 0 len false)
  17. (.set out data 4)
  18. out))
  19. (defn- encode-framed-rows [rows]
  20. (let [payload (.encode test-text-encoder (sqlite-util/write-transit-str rows))]
  21. (frame-bytes payload)))
  22. (defn- <gzip-bytes [^js payload]
  23. (if (exists? js/CompressionStream)
  24. (p/let [stream (js/ReadableStream.
  25. #js {:start (fn [controller]
  26. (.enqueue controller payload)
  27. (.close controller))})
  28. compressed (.pipeThrough stream (js/CompressionStream. "gzip"))
  29. resp (js/Response. compressed)
  30. buf (.arrayBuffer resp)]
  31. (js/Uint8Array. buf))
  32. (p/resolved payload)))
  33. (deftest remove-member-request-test
  34. (async done
  35. (let [called (atom nil)]
  36. (-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
  37. db-sync/fetch-json (fn [url opts _]
  38. (reset! called {:url url :opts opts})
  39. (p/resolved {:ok true}))
  40. user-handler/task--ensure-id&access-token (fn [resolve _reject]
  41. (resolve true))]
  42. (p/let [_ (db-sync/<rtc-remove-member! "graph-1" "user-2")
  43. {:keys [url opts]} @called]
  44. (is (= "http://base/graphs/graph-1/members/user-2" url))
  45. (is (= "DELETE" (:method opts)))
  46. (done)))
  47. (p/catch (fn [e]
  48. (is false (str e))
  49. (done)))))))
  50. (deftest leave-graph-uses-current-user-test
  51. (async done
  52. (let [called (atom nil)]
  53. (-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
  54. db-sync/fetch-json (fn [url opts _]
  55. (reset! called {:url url :opts opts})
  56. (p/resolved {:ok true}))
  57. user-handler/task--ensure-id&access-token (fn [resolve _reject]
  58. (resolve true))
  59. user-handler/user-uuid (fn [] "user-1")]
  60. (p/let [_ (db-sync/<rtc-leave-graph! "graph-1")
  61. {:keys [url opts]} @called]
  62. (is (= "http://base/graphs/graph-1/members/user-1" url))
  63. (is (= "DELETE" (:method opts)))
  64. (done)))
  65. (p/catch (fn [e]
  66. (is false (str e))
  67. (done)))))))
  68. (deftest leave-graph-missing-user-test
  69. (async done
  70. (-> (p/with-redefs [user-handler/user-uuid (fn [] nil)]
  71. (db-sync/<rtc-leave-graph! "graph-1"))
  72. (p/then (fn [_]
  73. (is false "expected rejection")
  74. (done)))
  75. (p/catch (fn [e]
  76. (is (= :db-sync/invalid-member (:type (ex-data e))))
  77. (done))))))
  78. (deftest rtc-start-skips-when-graph-missing-from-remote-list-test
  79. (async done
  80. (let [called (atom [])]
  81. (-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "repo-other"}])
  82. state/<invoke-db-worker (fn [& args]
  83. (swap! called conj args)
  84. (p/resolved :ok))]
  85. (db-sync/<rtc-start! "repo-current"))
  86. (p/then (fn [_]
  87. (is (= [[:thread-api/db-sync-stop]] @called))
  88. (done)))
  89. (p/catch (fn [e]
  90. (is false (str e))
  91. (done)))))))
  92. (deftest rtc-start-invokes-worker-when-graph-in-remote-list-test
  93. (async done
  94. (let [called (atom nil)]
  95. (-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "repo-current"}])
  96. state/<invoke-db-worker (fn [& args]
  97. (reset! called args)
  98. (p/resolved :ok))]
  99. (db-sync/<rtc-start! "repo-current"))
  100. (p/then (fn [_]
  101. (is (= [:thread-api/db-sync-start "repo-current"] @called))
  102. (done)))
  103. (p/catch (fn [e]
  104. (is false (str e))
  105. (done)))))))
  106. (deftest rtc-start-waits-for-db-worker-before-start-test
  107. (async done
  108. (let [worker (atom nil)
  109. called (atom [])]
  110. (-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "repo-current"}])
  111. state/*db-worker worker]
  112. (p/let [start-p (db-sync/<rtc-start! "repo-current")
  113. _ (p/delay 30)
  114. _ (is (empty? @called))
  115. _ (reset! worker
  116. (fn [qkw direct-pass? & args]
  117. (swap! called conj [qkw direct-pass? args])
  118. (p/resolved :ok)))
  119. _ start-p]
  120. (is (= [[:thread-api/db-sync-start false ["repo-current"]]]
  121. @called))
  122. (done)))
  123. (p/catch (fn [e]
  124. (is false (str e))
  125. (done)))))))
  126. (deftest rtc-start-waits-for-db-worker-before-stop-test
  127. (async done
  128. (let [worker (atom nil)
  129. called (atom [])]
  130. (-> (p/with-redefs [state/get-rtc-graphs (fn [] [{:url "repo-other"}])
  131. state/*db-worker worker]
  132. (p/let [start-p (db-sync/<rtc-start! "repo-current")
  133. _ (p/delay 30)
  134. _ (is (empty? @called))
  135. _ (reset! worker
  136. (fn [qkw direct-pass? & args]
  137. (swap! called conj [qkw direct-pass? args])
  138. (p/resolved :ok)))
  139. _ start-p]
  140. (is (= [[:thread-api/db-sync-stop false []]]
  141. @called))
  142. (done)))
  143. (p/catch (fn [e]
  144. (is false (str e))
  145. (done)))))))
  146. (deftest rtc-create-graph-persists-disabled-e2ee-flag-test
  147. (async done
  148. (let [fetch-called (atom nil)
  149. tx-called (atom nil)]
  150. (-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
  151. user-handler/task--ensure-id&access-token (fn [resolve _reject]
  152. (resolve true))
  153. db/get-db (fn [] :db)
  154. ldb/get-graph-schema-version (fn [_] {:major 65})
  155. db-sync/fetch-json (fn [url opts _]
  156. (reset! fetch-called {:url url :opts opts})
  157. (p/resolved {:graph-id "graph-1"
  158. :graph-e2ee? false}))
  159. ldb/transact! (fn [repo tx-data]
  160. (reset! tx-called {:repo repo :tx-data tx-data})
  161. nil)]
  162. (db-sync/<rtc-create-graph! "logseq_db_demo" false))
  163. (p/then (fn [graph-id]
  164. (let [request-body (-> @fetch-called
  165. (get-in [:opts :body])
  166. js/JSON.parse
  167. (js->clj :keywordize-keys true))
  168. tx-data (:tx-data @tx-called)]
  169. (is (= "graph-1" graph-id))
  170. (is (= "http://base/graphs" (:url @fetch-called)))
  171. (is (= false (:graph-e2ee? request-body)))
  172. (is (= :logseq.kv/graph-rtc-e2ee?
  173. (get-in tx-data [2 :db/ident])))
  174. (is (= false
  175. (get-in tx-data [2 :kv/value]))))
  176. (done)))
  177. (p/catch (fn [e]
  178. (is false (str e))
  179. (done)))))))
  180. (deftest rtc-create-graph-defaults-e2ee-enabled-test
  181. (async done
  182. (let [fetch-called (atom nil)
  183. tx-called (atom nil)]
  184. (-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
  185. user-handler/task--ensure-id&access-token (fn [resolve _reject]
  186. (resolve true))
  187. db/get-db (fn [] :db)
  188. ldb/get-graph-schema-version (fn [_] {:major 65})
  189. db-sync/fetch-json (fn [url opts _]
  190. (reset! fetch-called {:url url :opts opts})
  191. (p/resolved {:graph-id "graph-2"}))
  192. ldb/transact! (fn [repo tx-data]
  193. (reset! tx-called {:repo repo :tx-data tx-data})
  194. nil)]
  195. (db-sync/<rtc-create-graph! "logseq_db_demo"))
  196. (p/then (fn [graph-id]
  197. (let [request-body (-> @fetch-called
  198. (get-in [:opts :body])
  199. js/JSON.parse
  200. (js->clj :keywordize-keys true))
  201. tx-data (:tx-data @tx-called)]
  202. (is (= "graph-2" graph-id))
  203. (is (= "http://base/graphs" (:url @fetch-called)))
  204. (is (= true (:graph-e2ee? request-body)))
  205. (is (= :logseq.kv/graph-rtc-e2ee?
  206. (get-in tx-data [2 :db/ident])))
  207. (is (= true
  208. (get-in tx-data [2 :kv/value]))))
  209. (done)))
  210. (p/catch (fn [e]
  211. (is false (str e))
  212. (done)))))))
  213. (deftest rtc-download-graph-emits-feedback-before-snapshot-fetch-test
  214. (let [trace (atom [])
  215. log-events (atom [])]
  216. (with-redefs [db-sync/http-base (fn [] "http://base")
  217. state/set-state! (fn [k v]
  218. (swap! trace conj [:set k v]))
  219. state/pub-event! (fn [[event payload :as e]]
  220. (when (and (= :rtc/log event)
  221. (= "graph-1" (:graph-uuid payload)))
  222. (swap! trace conj :log)
  223. (swap! log-events conj e)))
  224. ;; Keep auth pending so we only validate immediate click-time feedback.
  225. user-handler/task--ensure-id&access-token (fn [_resolve _reject] nil)
  226. db-sync/fetch-json (fn [url _opts _schema]
  227. (swap! trace conj [:fetch url])
  228. (p/resolved {:t 1}))]
  229. (db-sync/<rtc-download-graph! "demo-graph" "graph-1")
  230. (is (= [[:set :rtc/downloading-graph-uuid "graph-1"] :log]
  231. (take 2 @trace)))
  232. (let [[event {:keys [type sub-type graph-uuid message]}] (first @log-events)]
  233. (is (= :rtc/log event))
  234. (is (= :rtc.log/download type))
  235. (is (= :download-progress sub-type))
  236. (is (= "graph-1" graph-uuid))
  237. (is (and (string? message)
  238. (string/includes? message "Preparing")))))))
  239. (deftest rtc-download-graph-imports-snapshot-once-test
  240. (async done
  241. (let [import-calls (atom [])
  242. fetch-calls (atom [])
  243. rows [[1 "content-1" "addresses-1"]
  244. [2 "content-2" "addresses-2"]]
  245. framed-bytes (encode-framed-rows rows)
  246. original-fetch js/fetch
  247. stream-url "http://base/sync/graph-1/snapshot/stream"]
  248. (-> (p/let [gzip-bytes (<gzip-bytes framed-bytes)]
  249. (set! js/fetch
  250. (fn [url opts]
  251. (let [method (or (aget opts "method") "GET")]
  252. (swap! fetch-calls conj [url method])
  253. (cond
  254. (and (= url stream-url) (= method "GET"))
  255. (js/Promise.resolve
  256. #js {:ok true
  257. :status 200
  258. :headers #js {:get (fn [header]
  259. (when (= header "content-length")
  260. (str (.-byteLength gzip-bytes))))}
  261. :arrayBuffer (fn [] (js/Promise.resolve (.-buffer gzip-bytes)))})
  262. :else
  263. (js/Promise.resolve
  264. #js {:ok true
  265. :status 200})))))
  266. (-> (p/with-redefs [db-sync/http-base (fn [] "http://base")
  267. db-sync/fetch-json (fn [url _opts _schema]
  268. (cond
  269. (string/ends-with? url "/pull")
  270. (p/resolved {:t 42})
  271. :else
  272. (p/rejected (ex-info "unexpected fetch-json URL"
  273. {:url url}))))
  274. user-handler/task--ensure-id&access-token (fn [resolve _reject]
  275. (resolve true))
  276. state/<invoke-db-worker (fn [& args]
  277. (swap! import-calls conj args)
  278. (p/resolved :ok))
  279. state/set-state! (fn [& _] nil)
  280. state/pub-event! (fn [& _] nil)]
  281. (db-sync/<rtc-download-graph! "demo-graph" "graph-1" false))
  282. (p/finally (fn [] (set! js/fetch original-fetch)))))
  283. (p/then (fn [_]
  284. (is (= 1 (count @import-calls)))
  285. (let [[op graph imported-rows reset? graph-uuid remote-tx graph-e2ee?] (first @import-calls)]
  286. (is (= :thread-api/db-sync-import-kvs-rows op))
  287. (is (string/ends-with? graph "demo-graph"))
  288. (is (= rows imported-rows))
  289. (is (= true reset?))
  290. (is (= "graph-1" graph-uuid))
  291. (is (= 42 remote-tx))
  292. (is (= false graph-e2ee?)))
  293. (is (= [[stream-url "GET"]]
  294. @fetch-calls))
  295. (done)))
  296. (p/catch (fn [error]
  297. (set! js/fetch original-fetch)
  298. (is false (str error))
  299. (done)))))))