Test doubles
One owned double per seam, behind a `test-support` feature a default build never compiles — the scripted WS streams, the in-memory history store, the recording broker, and the sanitized capture writer.
The rule is one sentence: one owned double per seam, behind a test-support feature. It exists
because the alternative was measured. Before crates/vike-bridge-core/src/scripted.rs, every
consumer hand-rolled the same two doubles — the user-data pump tests each carried a private scripted
queue, and the market and depth drivers plus a pump tap and a re-parse harness each carried a private
canned-text stream. One drift-prone shape, roughly eight copies. The shared
RecordingSink in vike_data::test_support tells the same story on the live-data seam: one
capturing sink instead of the roughly thirteen inline copies the venue bridges carried.
Every double is gated #[cfg(any(test, feature = "test-support"))], so in-crate unit tests see it
always, and a downstream crate reaches it by enabling the feature as a dev-dependency. A normal
build never compiles any of it, and no dev-dependency edge can reach a shipped binary.
The four homes
vike_bridge_core::scripted — the scripted stream doubles for the three WS seams: UserStream,
MarketStream and DepthStream. Its behaviour pins are carried verbatim from the copies it
replaced, and they are the interesting part:
- exhausting the script is a stream-closed error, the same shape a real disconnect takes, so a driver returning an error at the end of a script is expected rather than a failure;
send_textrecords what the driver sent — subscribe frames, keepalive pings — into a log the test reads back, shareable across sessions for the reconnect-test shape;- an optional shared clock advances before every read, so consuming the script ages an ack or freshness watchdog with zero real sleeps;
- a timeout step can escalate the reported stall the moment it is popped, because a synchronous session gives a test no re-entry point to mutate the stream mid-call.
vike_data::test_support — a DataFusion-free in-memory HistStore, plus the shared
RecordingSink and NoopSink live-data doubles. Venue bridges build a properties recorder over it
to assert their opt-in recording wiring without pulling the Arrow and DataFusion tree into a test
build.
vike_model::strategy::MockBroker — a minimal recording Broker for the strategy layer. It
captures every market and limit submit and lets a test script the clock, the mark and the position;
the rest of the surface is inert. It lives in the trait's own home, consolidated from near-identical
per-crate copies, so a new strategy-layer test inherits it instead of declaring another broker.
vike_bridge_core::capture — a separate feature, and a different kind of tool: it writes
fixtures rather than standing in for something. See below.
A double's job is fidelity, not convenience
The in-memory store is the worked example, and its header argues each choice. Bars are erased on append of exactly the fields the real bar schema never persists, so the round trip loses precisely what the real store's does. An empty batch returns zero without consuming the commit key, matching the real store — a double that burned the key would turn a later real append under that key into a no-op that happens only in tests. The catalog verbs answer with a real fold over the held seams rather than inheriting a default, because the empty default they replaced had a seeded double answering "no series" while holding rows. And the parts that hold nothing say so honestly: quotes, trades and book read empty, and the resample verbs return zero rather than fabricating, because resampling zero ticks derives zero bars on the real store too.
The capture feature: real wire as a fixture
vike_bridge_core::capture takes a verbatim venue frame and returns it with wire structure
preserved exactly — field names, value types, nesting, casing — because the structure is the
drift-sensitive part a hand-authored fixture can never witness. Only a known-sensitive leaf key set
is redacted, and the placeholder depends on the key, never on the secret value, so two captures
taken with different session keys sanitize to identical bytes and a re-capture diff shows real wire
drift rather than secret churn.
Venue order ids, trade ids, prices, quantities and timestamps are deliberately kept — they are the fixture's fidelity, and a demo-account order id identifies nothing. Key order inside a frame is sorted on write, since JSON key order is not wire semantics and sorting keeps diffs minimal.
Fixtures land under crates/bridges/<venue>/tests/fixtures/captured/ with a provenance stamp,
written by that venue's opt-in capture smoke and then consumed twice: by the venue's normal, CI-run
captured_wire_replay test, and as frame templates by the
exec contract harness. The replay test is not
#[ignore]d, needs no network and no credentials — and a missing committed fixture is a failure,
because a deleted gate must not pass silently.
Which features imply what
vike-bridge-core's test-support implies its full feature, for a structural reason: the doubles
are depth, market-pump and user-data streams, so without full there is nothing for them to
double. capture implies nothing — standard library and serde_json only — and is gated so the
shipped order-signing binary never compiles the fixture writer. vike-model and vike-data both
declare an empty, additive test-support.
The crate also carries a self-referential dev-dependency on itself with test-support enabled, which
is the standard cargo idiom for making a feature-gated module visible to the crate's own integration
tests: an integration test compiles the library without cfg(test), so the feature is the only way
in. It never affects a normal build.
Related: live smokes for where captures come from, and the live data seam for what the sink doubles stand in for.
Property tests
Randomized suites over the pure cores where golden fixtures only pin examples — the laws they assert, the never-widen discipline, and why a counterexample seed is committed.
Wire quantizers
The places an f64 becomes the decimal string a venue is sent or an order is signed over — the roster, the source gate that holds them exact, and the cliff they amplify.