Expand description
In-process multi-instance test harness (Tier 0).
Standing up several real Instances that sync with one another takes a pile
of identical boilerplate: create a user and key, enable sync, register a
transport, start serving, resolve the bound address. Cluster does that
plumbing and nothing else — it hands back wired peers and leaves every policy
decision (auth, what to write, how to drive sync) to the test.
That split is deliberate. A correctness harness for authenticated CRDT sync must keep the two things it exists to exercise — the transport and the auth — under the test’s control, not baked into the setup:
- Transport is a seam.
ClusterBuilder::transporttakes anyTestTransport; the default isHttpLoopback. A controllable in-memory transport (deliver / reorder / drop / single-step — Tier 1) is a drop-in here, which is the point: Tier 1 extends this, it doesn’t replace it. - Auth is the test’s. The harness never grants keys or permissions for
you. A peer exposes its
User, key id, and key name; the test creates its database with whatever auth posture it’s exercising.add_auth_keysandset_global_auth_keyare policy-neutral tools the test composes — they apply the keys you pass, they don’t choose them.
What the harness owns is plumbing only: wiring peers, marking a tree
sync-enabled (Peer::serve), driving an exchange (Cluster::exchange),
and observing convergence (Cluster::converged). It does not hold your
databases — the test opens and keeps those itself.
This is topology A (multi-peer sync): N independent Instances, each
owning an in-memory backend. Sync is driven explicitly (no background timers),
so a test fully orders the exchange. The multi-client / single-service
topology is a separate harness that lands with the service feature.
Gated behind cfg(any(test, feature = "testing")) alongside FixedClock
and Instance::create_backend_with_clock; never compiled into a release build.
use eidetica::{
auth::{Permission, types::AuthKey},
crdt::Doc,
testing::{Cluster, set_global_auth_key},
user::types::SyncSettings,
};
let mut net = Cluster::builder().peers(2).build().await?;
// Peer 0 creates a database with auth the *test* chooses, then serves it.
let key0 = net.peer(0).key_id().clone();
let mut settings = Doc::new();
settings.set("name", "chat");
let db = net.peer_mut(0).user_mut().create_database(settings, &key0).await?;
let room = db.root_id().clone();
set_global_auth_key(&db, AuthKey::active(None, Permission::Write(10))).await?;
net.peer_mut(0).serve(&room).await?;
// Peer 1 bootstraps with its own key, then converges against peer 0.
let key1 = net.peer(1).key_id().clone();
let signing_key1 = net.peer(1).user().get_signing_key(&key1)?;
let name1 = net.peer(1).key_name().to_string();
let addr0 = net.peer(0).address().clone();
net.peer(1)
.sync()
.sync_with_peer_for_bootstrap_with_key(
&addr0,
&room,
&signing_key1,
&name1,
Permission::Write(10),
)
.await?;
net.peer_mut(1)
.user_mut()
.track_database(room.clone(), &key1, SyncSettings::disabled())
.await?;
net.exchange(1, 0, &room).await?;
assert!(net.converged(&[0, 1], &room).await?);Structs§
- Cluster
- A set of in-process eidetica peers wired for multi-peer sync. Each peer is a
full
Instancewith its own backend. The cluster owns the wiring; the test owns the databases, the auth, and the order of operations. - Cluster
Builder - Builder for a
Cluster. Obtain viaCluster::builder. - Http
Loopback - Default
TestTransport: HTTP over an OS-assigned loopback port. - Peer
- One peer in a
Cluster: a fullInstanceplus the handles a test needs to act as that peer. It does not hold the peer’s application databases — the test opens and keeps those. - SimLoopback
TestTransportbacked by aSimNetwork: an in-memory drop-in forHttpLoopback. Build a cluster over it withCluster::builder().transport(Arc::new(SimLoopback::new(net.clone())))and keepnetto drive partitions.- SimNetwork
- In-memory message fabric shared by every
SimTransportin a cluster, and the control handle a test uses to inject faults. A drop-in forHttpLoopbackviaClusterBuilder::transportthat additionally lets a testpartitionlinks andhealthem. - SimTransport
- In-memory
SyncTransport. Routes aSyncRequeststraight to the target peer’sSyncHandlerthrough the sharedSimNetwork— no sockets, no serialization — and honors the network’s partition state.
Traits§
- Test
Transport - How a peer makes itself reachable to other peers. The one seam Tier 1 swaps: implement this over an in-memory, controllable network and the rest of the harness is unchanged.
Functions§
- add_
auth_ keys - Apply per-key auth to a database via a settings transaction. The caller chooses the keys and permissions; this just writes them.
- set_
global_ auth_ key - Set the global (wildcard) auth key on a database via a settings transaction. The caller chooses the permission level.