1
0

db_worker_node_test.cljs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. (ns frontend.worker.db-worker-node-test
  2. (:require ["http" :as http]
  3. [cljs.test :refer [async deftest is]]
  4. [clojure.string :as string]
  5. [frontend.test.node-helper :as node-helper]
  6. [frontend.worker-common.util :as worker-util]
  7. [frontend.worker.db-worker-node :as db-worker-node]
  8. [goog.object :as gobj]
  9. [logseq.db :as ldb]
  10. [logseq.db.sqlite.util :as sqlite-util]
  11. [promesa.core :as p]
  12. ["fs" :as fs]
  13. ["path" :as node-path]))
  14. (defn- http-request
  15. [opts body]
  16. (p/create
  17. (fn [resolve reject]
  18. (let [req (.request http (clj->js opts)
  19. (fn [^js res]
  20. (let [chunks (array)]
  21. (.on res "data" (fn [chunk] (.push chunks chunk)))
  22. (.on res "end" (fn []
  23. (resolve {:status (.-statusCode res)
  24. :body (.toString (js/Buffer.concat chunks) "utf8")}))))))
  25. finish! (fn []
  26. (when body (.write req body))
  27. (.end req))]
  28. (.on req "error" reject)
  29. (finish!)))))
  30. (defn- http-get
  31. [host port path]
  32. (http-request {:hostname host
  33. :port port
  34. :path path
  35. :method "GET"}
  36. nil))
  37. (defn- invoke
  38. [host port method args]
  39. (let [payload (js/JSON.stringify
  40. (clj->js {:method method
  41. :directPass false
  42. :argsTransit (ldb/write-transit-str args)}))]
  43. (p/let [{:keys [status body]}
  44. (http-request {:hostname host
  45. :port port
  46. :path "/v1/invoke"
  47. :method "POST"
  48. :headers {"Content-Type" "application/json"}}
  49. payload)
  50. parsed (js->clj (js/JSON.parse body) :keywordize-keys true)]
  51. (when (not= 200 status)
  52. (println "[db-worker-node-test] invoke failed"
  53. {:method method
  54. :status status
  55. :body body}))
  56. (is (= 200 status))
  57. (is (:ok parsed))
  58. (ldb/read-transit-str (:resultTransit parsed)))))
  59. (defn- invoke-raw
  60. [host port method args]
  61. (let [payload (js/JSON.stringify
  62. (clj->js {:method method
  63. :directPass false
  64. :argsTransit (ldb/write-transit-str args)}))]
  65. (http-request {:hostname host
  66. :port port
  67. :path "/v1/invoke"
  68. :method "POST"
  69. :headers {"Content-Type" "application/json"}}
  70. payload)))
  71. (defn- lock-path
  72. [data-dir repo]
  73. (let [pool-name (worker-util/get-pool-name repo)
  74. repo-dir (node-path/join data-dir (str "." pool-name))]
  75. (node-path/join repo-dir "db-worker.lock")))
  76. (defn- pad2
  77. [value]
  78. (if (< value 10)
  79. (str "0" value)
  80. (str value)))
  81. (defn- yyyymmdd
  82. [^js date]
  83. (str (.getFullYear date)
  84. (pad2 (inc (.getMonth date)))
  85. (pad2 (.getDate date))))
  86. (defn- log-path
  87. [data-dir repo]
  88. (let [pool-name (worker-util/get-pool-name repo)
  89. repo-dir (node-path/join data-dir (str "." pool-name))
  90. date-str (yyyymmdd (js/Date.))]
  91. (node-path/join repo-dir (str "db-worker-node-" date-str ".log"))))
  92. (deftest db-worker-node-creates-log-file
  93. (async done
  94. (let [daemon (atom nil)
  95. data-dir (node-helper/create-tmp-dir "db-worker-log")
  96. repo (str "logseq_db_log_" (subs (str (random-uuid)) 0 8))
  97. log-file (log-path data-dir repo)]
  98. (-> (p/let [{:keys [stop!]}
  99. (db-worker-node/start-daemon! {:data-dir data-dir
  100. :repo repo})
  101. _ (reset! daemon {:stop! stop!})
  102. _ (p/delay 50)]
  103. (is (fs/existsSync log-file)))
  104. (p/catch (fn [e]
  105. (is false (str "unexpected error: " e))))
  106. (p/finally (fn []
  107. (if-let [stop! (:stop! @daemon)]
  108. (-> (stop!) (p/finally (fn [] (done))))
  109. (done))))))))
  110. (deftest db-worker-node-log-file-has-entries
  111. (async done
  112. (let [daemon (atom nil)
  113. data-dir (node-helper/create-tmp-dir "db-worker-log-entries")
  114. repo (str "logseq_db_log_entries_" (subs (str (random-uuid)) 0 8))
  115. log-file (log-path data-dir repo)]
  116. (-> (p/let [{:keys [host port stop!]}
  117. (db-worker-node/start-daemon! {:data-dir data-dir
  118. :repo repo})
  119. _ (reset! daemon {:stop! stop!})
  120. _ (invoke host port "thread-api/create-or-open-db" [repo {}])
  121. _ (p/delay 50)
  122. contents (when (fs/existsSync log-file)
  123. (.toString (fs/readFileSync log-file) "utf8"))]
  124. (is (fs/existsSync log-file))
  125. (is (pos? (count contents))))
  126. (p/catch (fn [e]
  127. (is false (str "unexpected error: " e))))
  128. (p/finally (fn []
  129. (if-let [stop! (:stop! @daemon)]
  130. (-> (stop!) (p/finally (fn [] (done))))
  131. (done))))))))
  132. (deftest db-worker-node-log-retention
  133. (let [enforce-log-retention! #'db-worker-node/enforce-log-retention!
  134. data-dir (node-helper/create-tmp-dir "db-worker-log-retention")
  135. repo (str "logseq_db_log_retention_" (subs (str (random-uuid)) 0 8))
  136. pool-name (worker-util/get-pool-name repo)
  137. repo-dir (node-path/join data-dir (str "." pool-name))
  138. days ["20240101" "20240102" "20240103" "20240104" "20240105"
  139. "20240106" "20240107" "20240108" "20240109"]
  140. make-log (fn [day]
  141. (node-path/join repo-dir (str "db-worker-node-" day ".log")))]
  142. (fs/mkdirSync repo-dir #js {:recursive true})
  143. (doseq [day days]
  144. (fs/writeFileSync (make-log day) "log\n"))
  145. (enforce-log-retention! repo-dir)
  146. (let [remaining (->> (fs/readdirSync repo-dir)
  147. (filter (fn [^js name]
  148. (re-matches #"db-worker-node-\d{8}\.log" name)))
  149. (sort))]
  150. (is (= 7 (count remaining)))
  151. (is (= ["db-worker-node-20240103.log"
  152. "db-worker-node-20240104.log"
  153. "db-worker-node-20240105.log"
  154. "db-worker-node-20240106.log"
  155. "db-worker-node-20240107.log"
  156. "db-worker-node-20240108.log"
  157. "db-worker-node-20240109.log"]
  158. remaining)))))
  159. (deftest db-worker-node-parse-args-ignores-host-and-port
  160. (let [parse-args #'db-worker-node/parse-args
  161. result (parse-args #js ["node" "db-worker-node.js"
  162. "--host" "0.0.0.0"
  163. "--port" "1234"
  164. "--repo" "logseq_db_parse_args"
  165. "--data-dir" "/tmp/db-worker"])]
  166. (is (nil? (:host result)))
  167. (is (nil? (:port result)))
  168. (is (= "logseq_db_parse_args" (:repo result)))
  169. (is (= "/tmp/db-worker" (:data-dir result)))))
  170. (deftest db-worker-node-daemon-smoke-test
  171. (async done
  172. (let [daemon (atom nil)
  173. data-dir (node-helper/create-tmp-dir "db-worker-daemon")
  174. repo (str "logseq_db_smoke_" (subs (str (random-uuid)) 0 8))
  175. now (js/Date.now)
  176. page-uuid (random-uuid)
  177. block-uuid (random-uuid)]
  178. (-> (p/let [{:keys [host port stop!]}
  179. (db-worker-node/start-daemon!
  180. {:data-dir data-dir
  181. :repo repo})
  182. health (http-get host port "/healthz")
  183. ready (http-get host port "/readyz")
  184. _ (do
  185. (reset! daemon {:host host :port port :stop! stop!})
  186. (println "[db-worker-node-test] daemon started" {:host host :port port})
  187. (println "[db-worker-node-test] /healthz" health)
  188. (is (= 200 (:status health)))
  189. (println "[db-worker-node-test] /readyz" ready)
  190. (is (= 200 (:status ready)))
  191. (println "[db-worker-node-test] repo" repo))
  192. _ (invoke host port "thread-api/create-or-open-db" [repo {}])
  193. dbs (invoke host port "thread-api/list-db" [])
  194. _ (do
  195. (println "[db-worker-node-test] list-db" dbs)
  196. (let [prefix sqlite-util/db-version-prefix
  197. expected-name (if (string/starts-with? repo prefix)
  198. (subs repo (count prefix))
  199. repo)]
  200. (is (some #(= expected-name (:name %)) dbs))))
  201. lock-file (lock-path data-dir repo)
  202. _ (is (fs/existsSync lock-file))
  203. lock-contents (js/JSON.parse (.toString (fs/readFileSync lock-file) "utf8"))
  204. _ (is (= repo (gobj/get lock-contents "repo")))
  205. _ (is (= host (gobj/get lock-contents "host")))
  206. _ (invoke host port "thread-api/transact"
  207. [repo
  208. [{:block/uuid page-uuid
  209. :block/title "Smoke Page"
  210. :block/name "smoke-page"
  211. :block/tags #{:logseq.class/Page}
  212. :block/created-at now
  213. :block/updated-at now}
  214. {:block/uuid block-uuid
  215. :block/title "Smoke Test"
  216. :block/page [:block/uuid page-uuid]
  217. :block/parent [:block/uuid page-uuid]
  218. :block/order "a0"
  219. :block/created-at now
  220. :block/updated-at now}]
  221. {}
  222. nil])
  223. result (invoke host port "thread-api/q"
  224. [repo
  225. ['[:find ?e
  226. :in $ ?uuid
  227. :where [?e :block/uuid ?uuid]]
  228. block-uuid]])]
  229. (println "[db-worker-node-test] q result" result)
  230. (is (seq result)))
  231. (p/catch (fn [e]
  232. (println "[db-worker-node-test] e:" e)
  233. (is false (str e))))
  234. (p/finally (fn []
  235. (if-let [stop! (:stop! @daemon)]
  236. (-> (stop!)
  237. (p/finally (fn []
  238. (is (not (fs/existsSync (lock-path data-dir repo))))
  239. (done))))
  240. (done))))))))
  241. (deftest db-worker-node-repo-mismatch-test
  242. (async done
  243. (let [daemon (atom nil)
  244. data-dir (node-helper/create-tmp-dir "db-worker-repo-mismatch")
  245. repo (str "logseq_db_mismatch_" (subs (str (random-uuid)) 0 8))
  246. other-repo (str repo "_other")]
  247. (-> (p/let [{:keys [host port stop!]}
  248. (db-worker-node/start-daemon! {:data-dir data-dir
  249. :repo repo})
  250. _ (reset! daemon {:host host :port port :stop! stop!})
  251. {:keys [status body]} (invoke-raw host port "thread-api/create-or-open-db" [other-repo {}])
  252. parsed (js->clj (js/JSON.parse body) :keywordize-keys true)]
  253. (is (= 409 status))
  254. (is (= false (:ok parsed)))
  255. (is (= "repo-mismatch" (get-in parsed [:error :code]))))
  256. (p/catch (fn [e]
  257. (is false (str "unexpected error: " e))))
  258. (p/finally (fn []
  259. (if-let [stop! (:stop! @daemon)]
  260. (-> (stop!) (p/finally (fn [] (done))))
  261. (done))))))))
  262. (deftest db-worker-node-lock-prevents-multiple-daemons
  263. (async done
  264. (let [daemon (atom nil)
  265. data-dir (node-helper/create-tmp-dir "db-worker-lock")
  266. repo (str "logseq_db_lock_" (subs (str (random-uuid)) 0 8))]
  267. (-> (p/let [{:keys [stop!]}
  268. (db-worker-node/start-daemon! {:data-dir data-dir
  269. :repo repo})
  270. _ (reset! daemon {:stop! stop!})]
  271. (-> (db-worker-node/start-daemon! {:data-dir data-dir
  272. :repo repo})
  273. (p/then (fn [_]
  274. (is false "expected lock error")))
  275. (p/catch (fn [e]
  276. (is (= :repo-locked (-> (ex-data e) :code)))))))
  277. (p/catch (fn [e]
  278. (is false (str "unexpected error: " e))))
  279. (p/finally (fn []
  280. (if-let [stop! (:stop! @daemon)]
  281. (-> (stop!) (p/finally (fn [] (done))))
  282. (done))))))))