052-logseq-cli-sync-upload-graph-uuid-alignment.md

This commit is contained in:
rcmerci
2026-03-07 16:50:06 +08:00
parent c8da78efd1
commit 4fe1dde02f
5 changed files with 319 additions and 54 deletions

View File

@@ -0,0 +1,172 @@
# CLI Sync Upload Graph UUID Alignment Implementation Plan
Goal: Make `logseq sync upload` persist local graph UUID metadata so CLI and web app upload flows leave the graph in the same sync-ready state.
Architecture: Keep `:thread-api/db-sync-upload-graph` as the single upload contract used by both CLI and web app.
Architecture: Align identity persistence inside `frontend.worker.sync` so every resolved graph id is written to both client-op storage and graph KV metadata.
Architecture: Use web app graph identity persistence semantics from `frontend.handler.db_based.sync/<rtc-create-graph!` as the source of truth for what local metadata must exist after upload bootstrap.
Tech Stack: ClojureScript, Promesa, Datascript, db-worker-node HTTP invoke API, logseq-cli sync command stack.
Related: Builds on `docs/agent-guide/051-logseq-cli-sync-upload-fix.md`.
Related: Relates to `docs/agent-guide/047-logseq-cli-sync-command.md`.
## Problem statement
`logseq sync upload` currently reaches `frontend.worker.sync/upload-graph!` through `db-worker-node` and can complete snapshot upload while local graph KV metadata still does not contain `:logseq.kv/graph-uuid`.
The current worker persistence path writes graph id to `frontend.worker.sync.client-op` but does not write `:logseq.kv/graph-uuid` into the graph database.
The web app has an established graph identity persistence pattern in `frontend.handler.db_based.sync/<rtc-create-graph!`, where graph creation persists `:logseq.kv/graph-uuid` and related sync metadata into graph KV.
This mismatch causes CLI-uploaded graphs to remain partially initialized from the local metadata perspective even though remote upload succeeded.
The practical symptom is that features reading graph UUID from `logseq.db/get-graph-rtc-uuid` still see nil after upload.
I will use @test-driven-development for every implementation step and keep CLI behavior aligned with @logseq-cli command contracts.
## Current flow comparison
| Dimension | CLI upload flow today | Web app reference behavior | Gap to close |
|---|---|---|---|
| Entry point | `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/sync.cljs` via `execute-sync-upload`. | `/Users/rcmerci/gh-repos/logseq/src/main/frontend/handler/db_based/sync.cljs` via `<rtc-upload-graph!` and `<rtc-create-graph!`. | None on entry API shape. |
| Worker API | Calls `:thread-api/db-sync-upload-graph` through `/v1/invoke`. | Calls the same `:thread-api/db-sync-upload-graph` for upload. | None on worker method selection. |
| Graph id resolution | `frontend.worker.sync/<ensure-upload-graph-identity!` resolves from DB KV, client-op, remote list, or remote create. | Same worker resolution path during upload bootstrap. | None on resolution source order. |
| Graph id persistence | `frontend.worker.sync/persist-upload-graph-identity!` writes client-op graph UUID and sync flags but not `:logseq.kv/graph-uuid`. | Web app graph create flow writes `:logseq.kv/graph-uuid` into graph KV. | Missing graph UUID write in worker upload persistence path. |
| Post-upload local state | Graph may still miss `:logseq.kv/graph-uuid`. | Expected local graph identity metadata is complete after remote bootstrap. | CLI path is not fully sync-ready for UUID readers. |
## Target aligned behavior
After `logseq sync upload --graph <name>` succeeds, local graph metadata must contain a stable `:logseq.kv/graph-uuid` value in graph KV and the same graph id in client-op storage.
The persistence rule must be enforced by worker sync code so both CLI and web app callers inherit identical behavior.
When upload finds graph id from client-op fallback, worker must backfill missing graph KV UUID before returning success.
Repeated uploads must be idempotent and must not create a new graph id or rewrite to a different value unexpectedly.
## Architecture and integration points
```text
CLI path
logseq.cli.command.sync/execute-sync-upload
-> logseq.cli.transport/invoke POST /v1/invoke
-> frontend.worker.db_worker_node /v1/invoke
-> :thread-api/db-sync-upload-graph in frontend.worker.db_core
-> frontend.worker.sync/<ensure-upload-graph-identity!
-> frontend.worker.sync/upload-graph!
Web app path
frontend.handler.db_based.sync/<rtc-upload-graph!
-> state/<invoke-db-worker :thread-api/db-sync-upload-graph
-> frontend.worker.sync/<ensure-upload-graph-identity!
-> frontend.worker.sync/upload-graph!
Web app source-of-truth identity persistence reference
frontend.handler.db_based.sync/<rtc-create-graph!
-> ldb/transact! with :logseq.kv/graph-uuid
```
## Testing Plan
I will add worker unit tests that fail first when upload identity resolution does not persist `:logseq.kv/graph-uuid` into the graph database.
I will add worker unit tests that fail first for the three identity branches, which are graph id from remote create, graph id from remote name match, and graph id from client-op fallback.
I will add regression assertions that verify the persisted graph UUID is readable through `logseq.db/get-graph-rtc-uuid` and matches `client-op/get-graph-uuid`.
I will add a CLI-facing regression check that validates upload success is followed by graph info data containing `logseq.kv/graph-uuid` in real or staged integration coverage.
I will run targeted tests after each micro-change, then run broad lint and test commands before final review.
NOTE: I will write *all* tests before I add any implementation behavior.
## Implementation plan
### Phase 1. Add failing worker tests for UUID persistence parity.
1. Add a failing assertion in `/Users/rcmerci/gh-repos/logseq/src/test/frontend/worker/db_sync_test.cljs` for remote-create upload bootstrap to assert `ldb/get-graph-rtc-uuid` is set after `<ensure-upload-graph-identity!`.
2. Add a failing assertion in the same file for remote-name-match bootstrap to assert graph KV UUID is also set.
3. Add a failing test for the client-op-fallback branch where graph id exists only in client-op and graph KV UUID is missing before upload identity resolution.
4. Add a failing idempotency assertion that repeated identity ensure calls keep the same graph UUID value.
5. Run `bb dev:test -v frontend.worker.db-sync-test` and confirm the new assertions fail before implementation.
### Phase 2. Implement shared worker identity persistence aligned with web app semantics.
6. Update `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync.cljs` to persist graph UUID into graph KV when graph id is resolved.
7. Reuse the same UUID coercion semantics as web app reference code by converting graph-id string to UUID before writing `:logseq.kv/graph-uuid`.
8. Extend `set-graph-sync-metadata!` or a new helper to write `:logseq.kv/graph-uuid`, `:logseq.kv/graph-remote?`, and `:logseq.kv/graph-rtc-e2ee?` in one place.
9. Keep `client-op/update-graph-uuid` writes unchanged so existing fallback behavior remains compatible.
10. Ensure `<ensure-upload-graph-identity!` calls the persistence helper even when graph id is resolved from local fallback, so backfill works for previously uploaded graphs.
11. Preserve return payload shape from upload identity functions to avoid CLI output contract changes.
12. Run `bb dev:test -v frontend.worker.db-sync-test` until all new and existing sync tests pass.
### Phase 3. Guard CLI and web caller alignment.
13. Add or extend tests in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/command/sync_test.cljs` to assert CLI upload still delegates to `:thread-api/db-sync-upload-graph` without introducing a divergent bootstrap branch.
14. Add or extend tests in `/Users/rcmerci/gh-repos/logseq/src/test/frontend/handler/db_based/sync_test.cljs` to assert web app upload path continues to defer bootstrap to worker.
15. Add a regression check in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` that verifies graph metadata reported after upload includes a graph UUID field when the worker upload path succeeds.
16. Run `bb dev:test -v logseq.cli.command.sync-test` and `bb dev:test -v frontend.handler.db-based.sync-test`.
17. Run `bb dev:test -v logseq.cli.integration-test` and confirm sync upload behavior remains green.
### Phase 4. Document and verify user-visible behavior.
18. Update `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md` to state that successful `sync upload` persists local graph UUID metadata.
19. Add a short troubleshooting note that graph UUID should be visible in graph info output after upload.
20. Run `bb dev:lint-and-test` from `/Users/rcmerci/gh-repos/logseq` and confirm exit code `0`.
## Edge cases to cover
| Edge case | Expected behavior |
|---|---|
| Graph id resolved from client-op only. | Worker backfills `:logseq.kv/graph-uuid` in graph KV during upload identity ensure. |
| Graph datascript conn is unavailable. | Worker still keeps client-op graph UUID and returns a structured error or deferred persistence path without false success. |
| Graph id string is malformed. | Worker fails fast with a sync-specific error code instead of writing invalid KV data. |
| Repeated upload on same graph. | Graph UUID stays stable and no duplicate graph identity writes change semantic value. |
| Remote graph list returns multiple same-name matches. | Existing ambiguous-match error remains and CLI surfaces deterministic failure. |
## Clarifications needed before coding
Should the fix include automatic backfill on `sync start` and `sync status` in addition to `sync upload`, or is upload-only backfill sufficient for now.
Should malformed graph-id values be rejected hard or stored as raw strings if UUID coercion fails.
For this iteration, rely on upload-triggered backfill on the next upload; do not add a one-time migration or dedicated backfill command.
## Verification commands
| Command | Expected outcome |
|---|---|
| `bb dev:test -v frontend.worker.db-sync-test` | New UUID persistence tests fail first, then pass after implementation. |
| `bb dev:test -v logseq.cli.command.sync-test` | CLI upload delegation contract stays unchanged. |
| `bb dev:test -v frontend.handler.db-based.sync-test` | Web app upload path remains worker-centered. |
| `bb dev:test -v logseq.cli.integration-test` | Sync upload integration remains green with graph UUID metadata visible post-upload. |
| `bb dev:lint-and-test` | Full lint and test suite passes. |
| `node ./dist/logseq.js --graph demo sync upload --output json` | Returns `status=ok` with graph id on success. |
| `node ./dist/logseq.js --graph demo graph info --output json` | Output includes `logseq.kv/graph-uuid` for the uploaded graph. |
## Testing Details
The primary behavior test is that graph identity is fully persisted locally after upload, not just remotely acknowledged.
Tests will verify externally observable behavior through `ldb/get-graph-rtc-uuid`, `client-op/get-graph-uuid`, and CLI graph info output.
Tests will avoid asserting private implementation internals and will focus on identity outcomes across the three upload identity branches.
## Implementation Details
- Keep all orchestration inside `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/sync.cljs` so CLI and web callers stay aligned.
- Preserve `:thread-api/db-sync-upload-graph` as the single cross-surface upload API in `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs`.
- Reuse existing metadata transaction style from `frontend.handler.db_based.sync/<rtc-create-graph!` for graph UUID persistence semantics.
- Ensure UUID persistence helper is idempotent and safe to call multiple times.
- Keep client-op UUID persistence as a compatibility fallback.
- Do not introduce new cloud API endpoints or protocol changes.
- Keep CLI upload call sequence unchanged in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/sync.cljs`.
- Add regression tests before implementation and run them incrementally with @test-driven-development.
- Update CLI docs to reflect guaranteed local graph UUID persistence after successful upload.
## Decision
For this iteration, upload-triggered backfill is sufficient. Do not add a dedicated backfill command for previously uploaded graphs missing `:logseq.kv/graph-uuid` yet.
---

View File

@@ -109,9 +109,11 @@ Sync upload behavior:
- If the local graph already has sync metadata, upload reuses the stored remote `graph-id`.
- If the local graph does not have a stored remote `graph-id`, upload first lists visible remote graphs and reuses an exact same-name match when one exists.
- If no same-name remote graph exists, upload creates a new remote graph and persists the returned remote metadata locally before snapshot transfer.
- Successful upload persists graph identity metadata locally in both client-op state and graph KV (`logseq.kv/graph-uuid`, `logseq.kv/graph-remote?`, and `logseq.kv/graph-rtc-e2ee?`) so CLI and web upload/bootstrap flows stay aligned.
- Fresh uploads default to encrypted remote graph creation unless local sync metadata explicitly marks the graph as non-e2ee. In headless CLI mode, set `e2ee-password` via `sync config set` (or in `--config`) before uploading encrypted graphs.
- `sync upload` returns a real error instead of false success when auth, remote graph bootstrap, or snapshot upload fails.
- Common upload failures include missing/invalid `auth-token`, missing `http-base`, remote graph creation failure, snapshot upload failure, and local DB/worker startup failure.
- Troubleshooting: after a successful upload, run `graph info --graph <name> --output json` and confirm `data.kv.logseq.kv/graph-uuid` is present. If it is missing, rerun `sync upload` for the same graph to trigger identity backfill.
Sync download behavior:
- `sync download` requires `--graph <name>`.

View File

@@ -207,7 +207,6 @@
(def ^:private max-asset-size (* 100 1024 1024))
(def ^:private upload-kvs-batch-size 500)
(def ^:private snapshot-content-type "application/transit+json")
(def ^:private snapshot-content-encoding "gzip")
(def ^:private snapshot-text-encoder (js/TextEncoder.))
(def ^:private reconnect-base-delay-ms 1000)
(def ^:private reconnect-max-delay-ms 30000)
@@ -2140,32 +2139,37 @@
(.set out data 4)
out))
(defn- maybe-compress-stream [stream]
(if (exists? js/CompressionStream)
(.pipeThrough stream (js/CompressionStream. "gzip"))
stream))
(defn- <buffer-stream
[stream]
(p/let [resp (js/Response. stream)
buf (.arrayBuffer resp)]
buf))
(defn- <snapshot-upload-body
[rows]
(let [frame (frame-bytes (encode-snapshot-rows rows))]
(p/resolved {:body frame :encoding nil})))
(defn- graph-id->uuid
[repo graph-id]
(when-not (seq graph-id)
(fail-fast :db-sync/missing-field {:repo repo :field :graph-id}))
(try
(uuid graph-id)
(catch :default e
(fail-fast :db-sync/invalid-field {:repo repo
:field :graph-id
:value graph-id
:error e}))))
(defn- set-graph-sync-metadata!
[repo graph-e2ee?]
[repo graph-id graph-e2ee?]
(when-let [conn (worker-state/get-datascript-conn repo)]
(ldb/transact! conn [(ldb/kv :logseq.kv/graph-remote? true)
(ldb/transact! conn [(ldb/kv :logseq.kv/graph-uuid (graph-id->uuid repo graph-id))
(ldb/kv :logseq.kv/graph-remote? true)
(ldb/kv :logseq.kv/graph-rtc-e2ee? (true? graph-e2ee?))])))
(defn- persist-upload-graph-identity!
[repo graph-id graph-e2ee?]
(let [graph-e2ee? (normalize-graph-e2ee? graph-e2ee?)]
(set-graph-sync-metadata! repo graph-e2ee?)
(let [graph-id (some-> graph-id str)
graph-e2ee? (normalize-graph-e2ee? graph-e2ee?)]
(when-not (seq graph-id)
(fail-fast :db-sync/missing-field {:repo repo :field :graph-id}))
(set-graph-sync-metadata! repo graph-id graph-e2ee?)
(ensure-client-graph-uuid! repo graph-id)
{:graph-id graph-id
:graph-e2ee? graph-e2ee?}))
@@ -2227,8 +2231,8 @@
(defn- <ensure-upload-graph-identity!
[repo]
(if-let [graph-id (get-graph-id repo)]
(p/resolved {:graph-id graph-id
:graph-e2ee? (normalize-graph-e2ee? (sync-crypt/graph-e2ee? repo))})
(p/resolved (persist-upload-graph-identity! repo graph-id
(normalize-graph-e2ee? (sync-crypt/graph-e2ee? repo))))
(let [target-graph-name (some-> repo common-config/strip-leading-db-version-prefix)
local-graph-e2ee? (normalize-graph-e2ee? (sync-crypt/graph-e2ee? repo))]
(if-not (seq target-graph-name)

View File

@@ -297,35 +297,26 @@
:t 18
:txs [{:t 18 :tx tx-payload}]}))
local-tx (atom 16)
orig-get-local-tx client-op/get-local-tx
orig-update-local-tx client-op/update-local-tx
orig-graph-e2ee? sync-crypt/graph-e2ee?
orig-ensure-graph-aes-key sync-crypt/<ensure-graph-aes-key
client {:repo test-repo
:graph-id "graph-1"
:inflight (atom [])
:last-sync-error (atom nil)
:online-users (atom [])
:ws-state (atom :open)}]
(set! client-op/get-local-tx (fn [_repo] @local-tx))
(set! client-op/update-local-tx (fn [_repo tx] (reset! local-tx tx)))
(set! sync-crypt/graph-e2ee? (fn [_repo] true))
(set! sync-crypt/<ensure-graph-aes-key (fn [_repo _graph-id]
(p/resolved nil)))
(-> (p/let [_ (#'db-sync/handle-message! test-repo client raw-message)
error-state @(:last-sync-error client)]
(is (= 16 @local-tx))
(is (= :missing-field (:code error-state)))
(is (= "missing-field" (:message error-state)))
(is (= :aes-key (get-in error-state [:data :field]))))
(-> (p/with-redefs [client-op/get-local-tx (fn [_repo] @local-tx)
client-op/update-local-tx (fn [_repo tx] (reset! local-tx tx))
sync-crypt/graph-e2ee? (fn [_repo] true)
sync-crypt/<ensure-graph-aes-key (fn [_repo _graph-id]
(p/resolved nil))]
(p/let [_ (#'db-sync/handle-message! test-repo client raw-message)
error-state @(:last-sync-error client)]
(is (= 16 @local-tx))
(is (= :missing-field (:code error-state)))
(is (= "missing-field" (:message error-state)))
(is (= :aes-key (get-in error-state [:data :field])))))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally (fn []
(set! client-op/get-local-tx orig-get-local-tx)
(set! client-op/update-local-tx orig-update-local-tx)
(set! sync-crypt/graph-e2ee? orig-graph-e2ee?)
(set! sync-crypt/<ensure-graph-aes-key orig-ensure-graph-aes-key)
(done))))))))
(p/finally done))))))
(deftest pull-ok-out-of-order-stale-response-is-ignored-test
(testing "late stale pull/ok should not overwrite a newer already-applied tx"
@@ -351,11 +342,13 @@
(reset! db-sync/*repo->latest-remote-tx {})
(with-datascript-conns conn client-ops-conn
(fn []
(-> (p/let [_ (#'db-sync/handle-message! test-repo client raw-new)
_ (#'db-sync/handle-message! test-repo client raw-stale)
parent' (d/entity @conn parent-id)]
(is (= "remote-new-title" (:block/title parent')))
(is (= 2 (client-op/get-local-tx test-repo))))
(-> (p/with-redefs [sync-crypt/graph-e2ee? (fn [_repo] false)]
(p/let [_ (client-op/update-local-tx test-repo 0)
_ (#'db-sync/handle-message! test-repo client raw-new)
_ (#'db-sync/handle-message! test-repo client raw-stale)
parent' (d/entity @conn parent-id)]
(is (= "remote-new-title" (:block/title parent')))
(is (= 2 (client-op/get-local-tx test-repo)))))
(p/finally (fn []
(reset! db-sync/*repo->latest-remote-tx latest-prev)
(done))))))))))
@@ -1320,10 +1313,12 @@
(deftest ensure-upload-graph-identity-creates-remote-graph-when-local-graph-id-missing-test
(testing "first upload bootstrap creates and persists a remote graph when local metadata is missing"
(async done
(let [client-ops-conn (d/create-conn client-op/schema-in-db)
(let [conn (d/create-conn {})
client-ops-conn (d/create-conn client-op/schema-in-db)
self-prev (js* "globalThis.self")
fetch-prev js/fetch
config-prev @worker-state/*db-sync-config
created-graph-id "c52bf9d4-3a95-4f17-9c8c-7f86eef5f75d"
fetch-calls (atom [])]
(aset js/globalThis "self" #js {:postMessage (fn [_] nil)})
(reset! worker-state/*db-sync-config {:http-base "https://example.com"
@@ -1339,19 +1334,21 @@
(and (= "https://example.com/graphs" url)
(= "POST" method))
(json-response 200 {:graph-id "created-graph-id"
(json-response 200 {:graph-id created-graph-id
:graph-e2ee? false})
:else
(js/Promise.reject (js/Error. (str "unexpected fetch url: " method " " url)))))))
(with-worker-conns {} {test-repo client-ops-conn}
(with-worker-conns {test-repo conn} {test-repo client-ops-conn}
(fn []
(with-redefs [db-sync/coerce-http-request (fn [_schema-key body] body)]
(-> (p/let [result (#'db-sync/<ensure-upload-graph-identity! test-repo)]
(is (= {:graph-id "created-graph-id"
(is (= {:graph-id created-graph-id
:graph-e2ee? false}
result))
(is (= "created-graph-id" (client-op/get-graph-uuid test-repo)))
(is (= created-graph-id (client-op/get-graph-uuid test-repo)))
(is (= created-graph-id
(some-> (ldb/get-graph-rtc-uuid @conn) str)))
(is (= [{:url "https://example.com/graphs" :method "GET"}
{:url "https://example.com/graphs" :method "POST"}]
@fetch-calls)))
@@ -1366,10 +1363,12 @@
(deftest ensure-upload-graph-identity-reuses-existing-remote-graph-when-local-graph-id-missing-test
(testing "first upload bootstrap reuses a same-name remote graph instead of creating a duplicate"
(async done
(let [client-ops-conn (d/create-conn client-op/schema-in-db)
(let [conn (d/create-conn {})
client-ops-conn (d/create-conn client-op/schema-in-db)
self-prev (js* "globalThis.self")
fetch-prev js/fetch
config-prev @worker-state/*db-sync-config
remote-graph-id "56cfcfc5-035a-4c6a-a5ba-70fe9cc27530"
fetch-calls (atom [])]
(aset js/globalThis "self" #js {:postMessage (fn [_] nil)})
(reset! worker-state/*db-sync-config {:http-base "https://example.com"
@@ -1382,20 +1381,22 @@
(and (= "https://example.com/graphs" url)
(= "GET" method))
(json-response 200 {:graphs [{:graph-name test-repo
:graph-id "remote-graph-id"
:graph-id remote-graph-id
:graph-e2ee? false
:created-at 1
:updated-at 1}]})
:else
(js/Promise.reject (js/Error. (str "unexpected fetch url: " method " " url)))))))
(with-worker-conns {} {test-repo client-ops-conn}
(with-worker-conns {test-repo conn} {test-repo client-ops-conn}
(fn []
(-> (p/let [result (#'db-sync/<ensure-upload-graph-identity! test-repo)]
(is (= {:graph-id "remote-graph-id"
(is (= {:graph-id remote-graph-id
:graph-e2ee? false}
result))
(is (= "remote-graph-id" (client-op/get-graph-uuid test-repo)))
(is (= remote-graph-id (client-op/get-graph-uuid test-repo)))
(is (= remote-graph-id
(some-> (ldb/get-graph-rtc-uuid @conn) str)))
(is (= [{:url "https://example.com/graphs" :method "GET"}]
@fetch-calls)))
(p/catch (fn [e]
@@ -1490,6 +1491,37 @@
(reset! worker-state/*db-sync-config config-prev)
(done)))))))))))
(deftest ensure-upload-graph-identity-backfills-graph-kv-from-client-op-fallback-test
(testing "upload identity fallback backfills graph kv and stays idempotent"
(async done
(let [conn (d/create-conn {})
client-ops-conn (d/create-conn client-op/schema-in-db)
fallback-graph-id "6f3b0d47-4a62-4da7-8e56-7e5dbfbe3f47"
list-calls (atom 0)]
(with-worker-conns {test-repo conn} {test-repo client-ops-conn}
(fn []
(with-redefs [sync-crypt/graph-e2ee? (fn [_repo] nil)
db-sync/get-graph-id (fn [_repo] fallback-graph-id)
db-sync/list-remote-graphs! (fn []
(swap! list-calls inc)
(p/resolved []))]
(is (nil? (client-op/get-graph-uuid test-repo)))
(is (nil? (ldb/get-graph-rtc-uuid @conn)))
(let [first-result-p (#'db-sync/<ensure-upload-graph-identity! test-repo)
second-result-p (#'db-sync/<ensure-upload-graph-identity! test-repo)]
(-> (p/let [first-result first-result-p
second-result second-result-p]
(is (= fallback-graph-id (:graph-id first-result)))
(is (= fallback-graph-id (:graph-id second-result)))
(is (= (:graph-e2ee? first-result)
(:graph-e2ee? second-result)))
(is (= fallback-graph-id
(some-> (ldb/get-graph-rtc-uuid @conn) str)))
(is (= 0 @list-calls)))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally done))))))))))
(deftest ^:long rehydrate-large-title-test
(testing "rehydrate fills empty title from object storage"
(async done

View File

@@ -310,6 +310,61 @@
(set! cli-server/ensure-server! orig-ensure-server!)
(set! transport/invoke orig-invoke)))))))
(deftest test-cli-sync-upload-followed-by-graph-info-shows-graph-uuid-test
(async done
(let [data-dir (node-helper/create-tmp-dir "db-worker-sync-upload-info-cli")
upload-repo "sync-upload-graph-info"
uploaded-graph-id "0f64b4a9-6f31-4f35-a83c-6b16f9ddf1ff"
orig-ensure-server! cli-server/ensure-server!
orig-invoke transport/invoke
invoke-calls (atom [])]
(-> (p/let [cfg-path (node-path/join (node-helper/create-tmp-dir "cli") "cli.edn")
_ (fs/writeFileSync cfg-path "{:output-format :json}")
create-result (run-cli ["graph" "create" "--graph" upload-repo] data-dir cfg-path)
create-payload (parse-json-output-safe create-result "graph create")
_ (is (= 0 (:exit-code create-result)))
_ (is (= "ok" (:status create-payload)))
_ (set! cli-server/ensure-server!
(fn [config _repo]
(p/resolved (assoc config :base-url "http://example"))))
_ (set! transport/invoke
(fn [_ method _direct-pass? args]
(swap! invoke-calls conj [method args])
(case method
:thread-api/set-db-sync-config
(p/resolved nil)
:thread-api/db-sync-upload-graph
(p/resolved {:graph-id uploaded-graph-id})
:thread-api/q
(p/resolved [[:logseq.kv/graph-uuid uploaded-graph-id]
[:logseq.kv/schema-version "65"]])
(p/resolved nil))))
upload-result (run-cli ["--graph" upload-repo "sync" "upload"] data-dir cfg-path)
upload-payload (parse-json-output-safe upload-result "sync upload")
info-result (run-cli ["--graph" upload-repo "graph" "info"] data-dir cfg-path)
info-payload (parse-json-output-safe info-result "graph info after upload")
q-call (some (fn [[method args]]
(when (= :thread-api/q method)
args))
@invoke-calls)]
(is (= 0 (:exit-code upload-result)))
(is (= "ok" (:status upload-payload)))
(is (= uploaded-graph-id (get-in upload-payload [:data :graph-id])))
(is (= 0 (:exit-code info-result)))
(is (= "ok" (:status info-payload)))
(is (= uploaded-graph-id
(get-in info-payload [:data :kv :logseq.kv/graph-uuid])))
(is (= "logseq_db_sync-upload-graph-info" (first q-call))))
(p/catch (fn [e]
(is false (str "unexpected error: " e))))
(p/finally (fn []
(set! cli-server/ensure-server! orig-ensure-server!)
(set! transport/invoke orig-invoke)
(done)))))))
(deftest ^:long test-cli-graph-list
(async done
(let [data-dir (node-helper/create-tmp-dir "db-worker")]