🐛 Mock RPC and timer in composable test interpreter to fix network errors

The `SyncFromLibrary` op dispatches a real `sync-file` event that
schedules `rx/timer 3000` + an RPC call to
`update-file-library-sync-status`. In the headless test runner
(no backend), this produces a network error that leaks into test
output.

Wrap the `check` function in `mock/with-mocks` to mock `rp/cmd!`
(returning success) and `rx/timer` (firing instantly). This
eliminates the 3200ms grace period in `op-grace-ms` and prevents
the network error from appearing in test output.

AI-assisted-by: mimo-v2.5
This commit is contained in:
Andrey Antukh 2026-08-04 15:55:47 +00:00
parent 8e713df5f0
commit 83a3d099f6

View File

@ -47,11 +47,13 @@
[app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.variants :as dwv] [app.main.data.workspace.variants :as dwv]
[app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cljs.test :as t] [cljs.test :as t]
[frontend-tests.composable-tests.comp.nodes :as n] [frontend-tests.composable-tests.comp.nodes :as n]
[frontend-tests.composable-tests.core :as tm] [frontend-tests.composable-tests.core :as tm]
[frontend-tests.helpers.mock :as mock]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@ -351,17 +353,11 @@
situation))) situation)))
(defn- op-grace-ms (defn- op-grace-ms
"Extra wait AFTER an event-op has settled, before proceeding. Zero for all ops "Extra wait AFTER an event-op has settled, before proceeding. Always zero:
except `SyncFromLibrary`: the production `sync-file` event additionally the `rx/timer` and `rp/cmd!` calls that `SyncFromLibrary` schedules are
schedules `rx/timer 3000` + an `:update-file-library-sync-status` RPC. There is mocked (see `check`) so they fire instantly and succeed."
no backend in the headless runner, so that delayed call fails (benignly) — but [_op]
3s after the sync it would land INSIDE whatever test is then running, leaking 0)
an error trace across test boundaries (and historically destabilising
whole-suite runs). Waiting it out here absorbs the failure within the test that
caused it."
[op]
(let [op (if (tm/recorded-choice? op) (tm/choice-of op) op)]
(if (instance? n/SyncFromLibrary op) 3200 0)))
(defn- run-ops (defn- run-ops
"Async fold over `ops` (concrete operation units, in order — plain ops and/or "Async fold over `ops` (concrete operation units, in order — plain ops and/or
@ -464,22 +460,31 @@
references the test holds (e.g. `has-property-of` on a change node). In-file references the test holds (e.g. `has-property-of` on a change node). In-file
propagation is AUTOMATIC (the watcher) — no propagate op is added. propagation is AUTOMATIC (the watcher) — no propagate op is added.
Mocks are installed for the duration of the check: `rp/cmd!` returns
success (recording calls) and `rx/timer` fires instantly, so the
`SyncFromLibrary` op's delayed RPC does not produce network errors.
Arities: `(check done case-map)` or `(check done case-map asserter)`." Arities: `(check done case-map)` or `(check done case-map asserter)`."
([done case-map] (check done case-map nil)) ([done case-map] (check done case-map nil))
([done {:keys [setup operation]} asserter] ([done {:keys [setup operation]} asserter]
(let [variants (tm/enumerate operation)] (mock/with-mocks
(letfn [(run-next [vs] {rp/cmd! mock/rpc-cmd-mock
(if (empty? vs) rx/timer mock/timer-mock}
(done) (fn [inner-done]
(run-variant (let [variants (tm/enumerate operation)]
setup (letfn [(run-next [vs]
;; a variant is a composed operation; flatten to its ordered leaf (if (empty? vs)
;; ops. `enumerate` already removed all one-of choices, so the (inner-done)
;; variant is a Sequence (or a single op). (run-variant
(tm/sequence-ops (first vs)) setup
(fn [situation] ;; a variant is a composed operation; flatten to its ordered leaf
(when asserter ;; ops. `enumerate` already removed all one-of choices, so the
(t/testing (str "operations:\n " (tm/describe-applied situation)) ;; variant is a Sequence (or a single op).
(asserter situation))) (tm/sequence-ops (first vs))
(run-next (rest vs))))))] (fn [situation]
(run-next variants))))) (when asserter
(t/testing (str "operations:\n " (tm/describe-applied situation))
(asserter situation)))
(run-next (rest vs))))))]
(run-next variants))))
done)))