diff --git a/docs/agent-guide/062-cli-list-default-sort-updated-at.md b/docs/agent-guide/062-cli-list-default-sort-updated-at.md new file mode 100644 index 0000000000..a567d2056b --- /dev/null +++ b/docs/agent-guide/062-cli-list-default-sort-updated-at.md @@ -0,0 +1,164 @@ +# Logseq CLI List Default Updated-at Sort Implementation Plan + +Goal: Make `list page`, `list tag`, and `list property` behave as if `--sort updated-at` is provided when users do not pass `--sort`. + +Architecture: Keep the change in the CLI command layer so the existing db-worker-node API surface remains stable. +Architecture: Reuse current CLI-side sorting flow in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs` where sorting already runs before offset and limit. +Architecture: Keep db-worker-node list providers in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/common/mcp/tools.cljs` unchanged for this scope, and document that decision explicitly. + +Tech Stack: ClojureScript, babashka.cli option specs, Promesa, Datascript-backed db-worker-node thread-api, existing CLI integration test harness. + +Related: Builds on `/Users/rcmerci/gh-repos/logseq/docs/agent-guide/004-logseq-cli-verb-subcommands.md` and `/Users/rcmerci/gh-repos/logseq/docs/agent-guide/043-logseq-cli-tag-property-management.md`. + +## Problem statement + +Current list commands support `--sort` and `--order`, but default behavior is unsorted because no sort field is applied unless users pass `--sort`. + +In current implementation, `list` execution fetches items from db-worker-node and runs `apply-sort` only when `:sort` is present in options. + +This means default output order depends on entity scan order from db-worker-node list functions, which is not aligned with the desired product behavior. + +The requested behavior is a consistent default sort key of `updated-at` for page, tag, and property list subcommands. + +## Current behavior snapshot + +| Layer | File | Current behavior | +| --- | --- | --- | +| CLI command parsing and execution | `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs` | `:sort` is optional and defaults to nil, so `apply-sort` is skipped when `--sort` is absent. | +| db-worker-node list thread-api bridge | `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs` | `:thread-api/api-list-pages`, `:thread-api/api-list-tags`, and `:thread-api/api-list-properties` return unsorted collections from shared list helpers. | +| Shared list helpers | `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/common/mcp/tools.cljs` | List helpers filter and shape items but do not apply sort by `updated-at`. | +| User docs | `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md` | List command syntax documents `--sort` as optional but does not state default sort behavior. | + +## Scope and non-goals + +This plan changes default sort behavior for CLI commands `list page`, `list tag`, and `list property`. + +This plan does not add new db-worker-node thread-api methods. + +This plan does not push pagination or sorting into db-worker-node. + +This plan does not change field names or output schemas. + +## Proposed behavior + +When users run `logseq list page`, `logseq list tag`, or `logseq list property` without `--sort`, CLI should sort by `updated-at` using the existing list sorting pipeline. + +When users pass `--sort`, the explicit value must override the default. + +`--order` should continue to default to `asc` unless explicitly set to `desc`. + +When multiple entities have the same primary sort value, CLI should apply `:db/id` as the deterministic secondary sort key. + +`offset` and `limit` must still be applied after sorting. + +The behavior should be equivalent to explicitly passing `--sort updated-at` today, with deterministic tie-breaking by `:db/id`. + +## Integration overview + +```text +logseq list page + -> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs + - determine effective sort field (default updated-at) + - apply CLI-side sort/order + - apply offset/limit/fields + -> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/transport.cljs + -> /Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs + -> /Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/common/mcp/tools.cljs +``` + +## Testing Plan + +I will follow `@test-driven-development` and add tests before implementation changes. + +I will add unit tests for CLI list execution paths that verify default sorting is applied when `:sort` is missing. + +I will add unit tests that verify explicit `--sort` still overrides the new default. + +I will add integration tests that verify default output order matches explicit `--sort updated-at` output for page, tag, and property list commands. + +I will update docs assertions or command help expectations if any existing tests encode old behavior. + +NOTE: I will write *all* tests before I add any implementation behavior. + +## Detailed implementation plan + +1. Add a failing unit test in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs` for list-page execution where input items are intentionally out of updated-at order and no `:sort` is provided. +2. Assert in that test that returned item ids are ordered exactly as if `:sort` were `"updated-at"` with default `:order`. +3. Add a failing unit test for list-tag execution with no `:sort` and confirm default updated-at ordering is applied after tag item preparation. +4. Add a failing unit test for list-property execution with no `:sort` and confirm default updated-at ordering is applied after property item preparation. +5. Add a failing unit test that passes explicit `:sort "title"` and confirms explicit sort overrides default updated-at behavior. +6. Add a failing unit test that passes explicit `:order "desc"` without `:sort` and confirms order is applied to default updated-at sort key. +7. Add a failing integration test in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs` that compares `list page` results against `list page --sort updated-at` for the same graph and asserts identical id sequences. +8. Add a failing integration test that compares `list tag` against `list tag --sort updated-at` and asserts identical id sequences. +9. Add a failing integration test that compares `list property` against `list property --sort updated-at` and asserts identical id sequences. +10. Run focused tests to verify they fail for the new default-sort behavior and not for unrelated setup issues. +11. Implement an `effective-sort` decision in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs` so list commands use `"updated-at"` when `:sort` is absent. +12. Reuse the effective sort key for all three executors: `execute-list-page`, `execute-list-tag`, and `execute-list-property`. +13. Keep existing explicit sort validation and allowed sort fields unchanged. +14. Add deterministic tie-breaking by `:db/id` in CLI list sorting when primary sort values are equal. +15. Keep existing order default (`asc`) unchanged. +16. Update option descriptions in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs` to clarify default sort behavior for users. +17. Update docs in `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md` to state that list subcommands default to sorting by `updated-at`. +18. Run focused unit and integration tests again and confirm green. +19. Run `bb dev:test -v logseq.cli.commands-test` to confirm command-level regressions are not introduced. +20. Run `bb dev:test -v logseq.cli.integration-test` for list command scenarios impacted by ordering. +21. Run `bb dev:lint-and-test` for final confidence. + +## Edge cases to cover + +Entities with missing `:block/updated-at` should still be sortable without runtime errors. + +Multiple entities with equal `updated-at` values should be secondarily sorted by `:db/id` for deterministic output across repeated runs. + +`--fields` filtering should not remove `updated-at` before sorting is executed. + +`--offset` and `--limit` should continue to apply after sorting, not before sorting. + +`--sort` with any allowed non-time field should keep existing behavior and take precedence over the new default. + +`--order desc` without explicit `--sort` should now reverse default updated-at order. + +## Verification commands + +| Command | Expected result | +| --- | --- | +| `bb dev:test -v logseq.cli.commands-test/test-list-subcommand-parse` | Existing parse behavior remains valid and list options remain accepted. | +| `bb dev:test -v logseq.cli.commands-test` | New default-sort unit tests pass and no command-level regressions are introduced. | +| `bb dev:test -v logseq.cli.integration-test` | Integration checks for list default ordering pass against a real db-worker-node flow. | +| `bb dev:lint-and-test` | Full lint and unit suite pass with zero errors. | + +## Rollout and compatibility + +This is a behavior change in default ordering for three CLI list commands. + +Scripts that depended on previous implicit scan order may observe changed item order. + +Scripts that already pass explicit `--sort` remain unaffected. + +No db-worker-node API contract change is introduced in this scope. + +## Testing Details + +Tests verify user-visible command behavior by comparing result ordering between default list calls and explicit `--sort updated-at` calls. + +Tests validate override behavior so explicit sort fields still control final ordering. + +Tests validate order plus pagination interaction to ensure behavior consistency. + +## Implementation Details + +- Update default sort selection in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/command/list.cljs` for page, tag, and property list executors. +- Keep existing `apply-sort`, `apply-offset-limit`, and `apply-fields` sequencing unchanged. +- Extend CLI sort implementation to apply `:db/id` as secondary key when primary sort values are equal. +- Reuse existing `list-*-field-map` entries for `updated-at` so no new field mapping is introduced. +- Keep db-worker-node list handlers in `/Users/rcmerci/gh-repos/logseq/src/main/frontend/worker/db_core.cljs` unchanged in this scope. +- Keep shared list helper behavior in `/Users/rcmerci/gh-repos/logseq/src/main/logseq/cli/common/mcp/tools.cljs` unchanged in this scope. +- Add command-level unit coverage in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/commands_test.cljs`. +- Add end-to-end list ordering coverage in `/Users/rcmerci/gh-repos/logseq/src/test/logseq/cli/integration_test.cljs`. +- Update user-facing list docs in `/Users/rcmerci/gh-repos/logseq/docs/cli/logseq-cli.md`. + +## Question + +No open questions. + +--- diff --git a/docs/cli/logseq-cli.md b/docs/cli/logseq-cli.md index 8c19ca5ac6..618de01cfd 100644 --- a/docs/cli/logseq-cli.md +++ b/docs/cli/logseq-cli.md @@ -176,9 +176,9 @@ Sync config persistence: - Cloud auth is persisted separately in `~/logseq/auth.json`. Inspect and edit commands: -- `list page [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list pages -- `list tag [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list tags -- `list property [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list properties (`TYPE` is included by default even without `--expand`) +- `list page [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list pages (defaults to `--sort updated-at`) +- `list tag [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list tags (defaults to `--sort updated-at`) +- `list property [--expand] [--limit ] [--offset ] [--sort ] [--order asc|desc]` - list properties (defaults to `--sort updated-at`; `TYPE` is included by default even without `--expand`) - `upsert block --content [--target-page |--target-id |--target-uuid ] [--pos first-child|last-child|sibling]` - create blocks; defaults to today’s journal page if no target is given - `upsert block --blocks [--target-page |--target-id |--target-uuid ] [--pos first-child|last-child|sibling]` - insert blocks via EDN vector - `upsert block --blocks-file [--target-page |--target-id |--target-uuid ] [--pos first-child|last-child|sibling]` - insert blocks from an EDN file diff --git a/src/main/logseq/cli/command/list.cljs b/src/main/logseq/cli/command/list.cljs index 7be544d06c..ec7576fc01 100644 --- a/src/main/logseq/cli/command/list.cljs +++ b/src/main/logseq/cli/command/list.cljs @@ -26,9 +26,15 @@ :list-tag #{"title" "id" "ident" "created-at" "updated-at"} :list-property #{"title" "id" "ident" "created-at" "updated-at" "type"}}) +(def ^:private default-sort-field "updated-at") + +(defn- effective-sort-field + [options] + (or (:sort options) default-sort-field)) + (def ^:private list-page-spec (merge list-common-spec - {:sort {:desc "Sort field" + {:sort {:desc "Sort field. Default: updated-at" :values (:list-page list-sort-fields)} :include-journal {:desc "Include journal pages" :coerce :boolean} @@ -42,7 +48,7 @@ (def ^:private list-tag-spec (merge list-common-spec - {:sort {:desc "Sort field" + {:sort {:desc "Sort field. Default: updated-at" :values (:list-tag list-sort-fields)} :include-built-in {:desc "Include built-in tags" :coerce :boolean} @@ -54,7 +60,7 @@ (def ^:private list-property-spec (merge list-common-spec - {:sort {:desc "Sort field" + {:sort {:desc "Sort field. Default: updated-at" :values (:list-property list-sort-fields)} :include-built-in {:desc "Include built-in properties" :coerce :boolean} @@ -157,7 +163,9 @@ (if (seq sort-field) (let [sort-key (get field-map sort-field) sorted (if sort-key - (sort-by #(get % sort-key) items) + (sort-by (fn [item] + [(get item sort-key) (:db/id item)]) + items) items) sorted (if (= "desc" order) (reverse sorted) sorted)] (vec sorted)) @@ -194,9 +202,10 @@ options (apply-user-only (:options action)) items (transport/invoke cfg :thread-api/api-list-pages false [(:repo action) options]) + sort-field (effective-sort-field options) order (or (:order options) "asc") fields (parse-field-list (:fields options)) - sorted (apply-sort items (:sort options) order list-page-field-map) + sorted (apply-sort items sort-field order list-page-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) final (apply-fields limited fields list-page-field-map)] {:status :ok @@ -210,10 +219,11 @@ (assoc :expand true)) items (transport/invoke cfg :thread-api/api-list-tags false [(:repo action) options]) + sort-field (effective-sort-field options) order (or (:order options) "asc") fields (parse-field-list (:fields options)) prepared (mapv #(prepare-tag-item % options) items) - sorted (apply-sort prepared (:sort options) order list-tag-field-map) + sorted (apply-sort prepared sort-field order list-tag-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) final (apply-fields limited fields list-tag-field-map)] {:status :ok @@ -226,10 +236,11 @@ (:with-classes (:options action)) (assoc :expand true)) items (transport/invoke cfg :thread-api/api-list-properties false [(:repo action) options]) + sort-field (effective-sort-field options) order (or (:order options) "asc") fields (parse-field-list (:fields options)) prepared (mapv #(prepare-property-item % options) items) - sorted (apply-sort prepared (:sort options) order list-property-field-map) + sorted (apply-sort prepared sort-field order list-property-field-map) limited (apply-offset-limit sorted (:offset options) (:limit options)) final (apply-fields limited fields list-property-field-map)] {:status :ok diff --git a/src/test/logseq/cli/commands_test.cljs b/src/test/logseq/cli/commands_test.cljs index fa32fa5295..af6222f750 100644 --- a/src/test/logseq/cli/commands_test.cljs +++ b/src/test/logseq/cli/commands_test.cljs @@ -2,6 +2,7 @@ (:require [cljs.test :refer [async deftest is testing]] [clojure.string :as string] [logseq.cli.command.add :as add-command] + [logseq.cli.command.list :as list-command] [logseq.cli.command.show :as show-command] [logseq.cli.command.sync :as sync-command] [logseq.cli.commands :as commands] @@ -48,6 +49,10 @@ (sequential? value) (some contains-block-uuid? value) :else false)) +(defn- item-ids + [result] + (mapv :db/id (get-in result [:data :items]))) + (deftest test-help-output (testing "top-level help lists command groups" (let [result (binding [style/*color-enabled?* true] @@ -958,6 +963,48 @@ (is (false? (:ok? result))) (is (= :invalid-options (get-in result [:error :code])))))) +(deftest test-list-execute-default-sort-updated-at + (async done + (-> (p/with-redefs [cli-server/ensure-server! (fn [_ _] {:base-url "http://example"}) + transport/invoke (fn [_ method _ _] + (case method + :thread-api/api-list-pages [{:db/id 11 :block/title "Page C" :block/updated-at 30} + {:db/id 7 :block/title "Page B" :block/updated-at 10} + {:db/id 5 :block/title "Page A" :block/updated-at 10}] + :thread-api/api-list-tags [{:db/id 4 :block/title "Tag C" :block/updated-at 20} + {:db/id 9 :block/title "Tag B" :block/updated-at 5} + {:db/id 2 :block/title "Tag A" :block/updated-at 5}] + :thread-api/api-list-properties [{:db/id 8 :block/title "Property C" :block/updated-at 9} + {:db/id 6 :block/title "Property B" :block/updated-at 3} + {:db/id 1 :block/title "Property A" :block/updated-at 3}] + (throw (ex-info "unexpected invoke" {:method method}))))] + (p/let [page-result (list-command/execute-list-page {:repo "demo" :options {}} {}) + tag-result (list-command/execute-list-tag {:repo "demo" :options {}} {}) + property-result (list-command/execute-list-property {:repo "demo" :options {}} {})] + (is (= [5 7 11] (item-ids page-result))) + (is (= [2 9 4] (item-ids tag-result))) + (is (= [1 6 8] (item-ids property-result))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done)))) + +(deftest test-list-execute-default-sort-respects-order-and-explicit-sort + (async done + (-> (p/with-redefs [cli-server/ensure-server! (fn [_ _] {:base-url "http://example"}) + transport/invoke (fn [_ method _ _] + (case method + :thread-api/api-list-pages [{:db/id 3 :block/title "Gamma" :block/updated-at 20} + {:db/id 2 :block/title "Alpha" :block/updated-at 5} + {:db/id 1 :block/title "Beta" :block/updated-at 10}] + (throw (ex-info "unexpected invoke" {:method method}))))] + (p/let [desc-default-result (list-command/execute-list-page {:repo "demo" :options {:order "desc"}} {}) + explicit-sort-result (list-command/execute-list-page {:repo "demo" :options {:sort "title"}} {})] + (is (= [3 1 2] (item-ids desc-default-result))) + (is (= [2 1 3] (item-ids explicit-sort-result))))) + (p/catch (fn [e] + (is false (str "unexpected error: " e)))) + (p/finally done)))) + (deftest test-verb-subcommand-parse-upsert-remove (testing "remove block parses with id" (let [result (commands/parse-args ["remove" "block" "--id" "10"])] diff --git a/src/test/logseq/cli/integration_test.cljs b/src/test/logseq/cli/integration_test.cljs index 956d48e9eb..ecd225e0ef 100644 --- a/src/test/logseq/cli/integration_test.cljs +++ b/src/test/logseq/cli/integration_test.cljs @@ -182,6 +182,13 @@ (when (= title (item-title item)) item))) item-id)) +(defn- ordered-item-ids-by-title-set + [payload titles] + (->> (get-in payload [:data :items]) + (filter (fn [item] + (contains? titles (item-title item)))) + (mapv item-id))) + (defn- first-result-id [payload] (first (get-in payload [:data :result]))) @@ -2239,6 +2246,112 @@ (is false (str "unexpected error: " e)) (done))))))) +(deftest ^:long test-cli-list-page-default-sort-matches-explicit-updated-at + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-list-page-default-sort") + repo "list-page-default-sort-graph" + titles #{"SortPageA" "SortPageB" "SortPageC"}] + (-> (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" repo] data-dir cfg-path) + _ (run-cli ["--graph" repo "upsert" "page" "--page" "SortPageA"] data-dir cfg-path) + _ (run-cli ["--graph" repo "upsert" "page" "--page" "SortPageB"] data-dir cfg-path) + _ (run-cli ["--graph" repo "upsert" "page" "--page" "SortPageC"] data-dir cfg-path) + _ (p/delay 80) + update-result (run-cli ["--graph" repo "upsert" "page" "--page" "SortPageA" + "--update-properties" "{:logseq.property/publishing-public? true}"] + data-dir cfg-path) + _ (p/delay 80) + default-result (run-cli ["--graph" repo "list" "page" "--fields" "title,id,updated-at"] data-dir cfg-path) + default-payload (parse-json-output default-result) + explicit-result (run-cli ["--graph" repo "list" "page" "--sort" "updated-at" "--fields" "title,id,updated-at"] data-dir cfg-path) + explicit-payload (parse-json-output explicit-result) + default-ids (ordered-item-ids-by-title-set default-payload titles) + explicit-ids (ordered-item-ids-by-title-set explicit-payload titles) + stop-payload (stop-repo! data-dir cfg-path repo)] + (is (= 0 (:exit-code create-result))) + (is (= 0 (:exit-code update-result))) + (is (= "ok" (:status default-payload))) + (is (= "ok" (:status explicit-payload))) + (is (= 3 (count default-ids))) + (is (= explicit-ids default-ids)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + +(deftest ^:long test-cli-list-tag-default-sort-matches-explicit-updated-at + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-list-tag-default-sort") + repo "list-tag-default-sort-graph" + titles #{"SortTagA-Renamed" "SortTagB" "SortTagC"}] + (-> (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" repo] data-dir cfg-path) + tag-a-result (run-cli ["--graph" repo "upsert" "tag" "--name" "SortTagA"] data-dir cfg-path) + tag-a-id (first-result-id (parse-json-output tag-a-result)) + _ (run-cli ["--graph" repo "upsert" "tag" "--name" "SortTagB"] data-dir cfg-path) + _ (run-cli ["--graph" repo "upsert" "tag" "--name" "SortTagC"] data-dir cfg-path) + _ (p/delay 80) + update-result (run-cli ["--graph" repo "upsert" "tag" "--id" (str tag-a-id) "--name" "SortTagA-Renamed"] + data-dir cfg-path) + _ (p/delay 80) + default-result (run-cli ["--graph" repo "list" "tag" "--user-only" "--fields" "title,id,updated-at"] data-dir cfg-path) + default-payload (parse-json-output default-result) + explicit-result (run-cli ["--graph" repo "list" "tag" "--user-only" "--sort" "updated-at" "--fields" "title,id,updated-at"] data-dir cfg-path) + explicit-payload (parse-json-output explicit-result) + default-ids (ordered-item-ids-by-title-set default-payload titles) + explicit-ids (ordered-item-ids-by-title-set explicit-payload titles) + stop-payload (stop-repo! data-dir cfg-path repo)] + (is (= 0 (:exit-code create-result))) + (is (number? tag-a-id)) + (is (= 0 (:exit-code update-result))) + (is (= "ok" (:status default-payload))) + (is (= "ok" (:status explicit-payload))) + (is (= 3 (count default-ids))) + (is (= explicit-ids default-ids)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + +(deftest ^:long test-cli-list-property-default-sort-matches-explicit-updated-at + (async done + (let [data-dir (node-helper/create-tmp-dir "db-worker-list-property-default-sort") + repo "list-property-default-sort-graph" + titles #{"SortPropertyA" "SortPropertyB" "SortPropertyC"}] + (-> (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" repo] data-dir cfg-path) + property-a-result (run-cli ["--graph" repo "upsert" "property" "--name" "SortPropertyA" "--type" "default"] data-dir cfg-path) + property-a-id (first-result-id (parse-json-output property-a-result)) + _ (run-cli ["--graph" repo "upsert" "property" "--name" "SortPropertyB" "--type" "default"] data-dir cfg-path) + _ (run-cli ["--graph" repo "upsert" "property" "--name" "SortPropertyC" "--type" "default"] data-dir cfg-path) + _ (p/delay 80) + update-result (run-cli ["--graph" repo "upsert" "property" "--id" (str property-a-id) "--type" "node"] data-dir cfg-path) + _ (p/delay 80) + default-result (run-cli ["--graph" repo "list" "property" "--user-only" "--fields" "title,id,updated-at"] data-dir cfg-path) + default-payload (parse-json-output default-result) + explicit-result (run-cli ["--graph" repo "list" "property" "--user-only" "--sort" "updated-at" "--fields" "title,id,updated-at"] data-dir cfg-path) + explicit-payload (parse-json-output explicit-result) + default-ids (ordered-item-ids-by-title-set default-payload titles) + explicit-ids (ordered-item-ids-by-title-set explicit-payload titles) + stop-payload (stop-repo! data-dir cfg-path repo)] + (is (= 0 (:exit-code create-result))) + (is (number? property-a-id)) + (is (= 0 (:exit-code update-result))) + (is (= "ok" (:status default-payload))) + (is (= "ok" (:status explicit-payload))) + (is (= 3 (count default-ids))) + (is (= explicit-ids default-ids)) + (is (= "ok" (:status stop-payload))) + (done)) + (p/catch (fn [e] + (is false (str "unexpected error: " e)) + (done))))))) + (deftest ^:long test-cli-list-outputs-include-id (async done (let [data-dir (node-helper/create-tmp-dir "db-worker")]