🐛 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.undo :as dwu]
[app.main.data.workspace.variants :as dwv]
[app.main.repo :as rp]
[app.main.store :as st]
[beicon.v2.core :as rx]
[cljs.test :as t]
[frontend-tests.composable-tests.comp.nodes :as n]
[frontend-tests.composable-tests.core :as tm]
[frontend-tests.helpers.mock :as mock]
[potok.v2.core :as ptk]))
;; --------------------------------------------------------------------------
@ -351,17 +353,11 @@
situation)))
(defn- op-grace-ms
"Extra wait AFTER an event-op has settled, before proceeding. Zero for all ops
except `SyncFromLibrary`: the production `sync-file` event additionally
schedules `rx/timer 3000` + an `:update-file-library-sync-status` RPC. There is
no backend in the headless runner, so that delayed call fails (benignly) — but
3s after the sync it would land INSIDE whatever test is then running, leaking
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)))
"Extra wait AFTER an event-op has settled, before proceeding. Always zero:
the `rx/timer` and `rp/cmd!` calls that `SyncFromLibrary` schedules are
mocked (see `check`) so they fire instantly and succeed."
[_op]
0)
(defn- run-ops
"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
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)`."
([done case-map] (check done case-map nil))
([done {:keys [setup operation]} asserter]
(let [variants (tm/enumerate operation)]
(letfn [(run-next [vs]
(if (empty? vs)
(done)
(run-variant
setup
;; a variant is a composed operation; flatten to its ordered leaf
;; ops. `enumerate` already removed all one-of choices, so the
;; variant is a Sequence (or a single op).
(tm/sequence-ops (first vs))
(fn [situation]
(when asserter
(t/testing (str "operations:\n " (tm/describe-applied situation))
(asserter situation)))
(run-next (rest vs))))))]
(run-next variants)))))
(mock/with-mocks
{rp/cmd! mock/rpc-cmd-mock
rx/timer mock/timer-mock}
(fn [inner-done]
(let [variants (tm/enumerate operation)]
(letfn [(run-next [vs]
(if (empty? vs)
(inner-done)
(run-variant
setup
;; a variant is a composed operation; flatten to its ordered leaf
;; ops. `enumerate` already removed all one-of choices, so the
;; variant is a Sequence (or a single op).
(tm/sequence-ops (first vs))
(fn [situation]
(when asserter
(t/testing (str "operations:\n " (tm/describe-applied situation))
(asserter situation)))
(run-next (rest vs))))))]
(run-next variants))))
done)))