Expand description
cratefield-testing: the conformance kit every Factory Zero module runs
against (issue #9) — fake ports, an in-memory SQLite Database, and
request helpers over the axum router with no network.
use cratefield_testing::{conformance, TestHarness, request};
#[test]
fn my_module_conforms() {
conformance(Box::new(my_module::MyModule::new()));
}
#[pollster::test]
async fn join_returns_202() {
let kit = TestHarness::new(vec![Box::new(my_module::MyModule::new())]);
let response = request(&kit.router, http::Method::POST,
"/v1/my-module/join", Some(r#"{"email":"nick@example.com"}"#)).await;
assert_eq!(response.status, http::StatusCode::ACCEPTED);
}§cratefield-testing
The conformance kit every Factory Zero module runs against — public and
private modules alike. Fake ports, an in-memory SQLite Database, and
request helpers over the axum router with no network.
§A 15-line module test
ⓘ
use cratefield_testing::{conformance, request, TestHarness};
#[test]
fn my_module_conforms() {
conformance(Box::new(my_module::MyModule::new()));
}
#[pollster::test]
async fn join_accepts_an_email() {
let kit = TestHarness::new(vec![Box::new(my_module::MyModule::new())]);
let res = request(&kit.router, http::Method::POST, "/v1/my-module/join",
Some(r#"{"email":"nick@example.com"}"#)).await;
assert_eq!(res.status, http::StatusCode::ACCEPTED);
assert_eq!(kit.mailer.sent().len(), 1); // the confirmation mail
}§What you get
TestHarness::new(vec![Box<dyn Module>])— builds the harness with every port faked, applies each module’s sqlite migrations to a fresh in-memory database, assembles the router. Exposes{ router, mailer, captcha, rate_limiter, db, clock, kv, http, defer, signer, events, modules, dialect }.- Parity (issue #20):
TestHarness::with_database(modules, Dialect::Sqlite | Dialect::Postgres { url })runs the same harness on a throwaway Postgres 16 database (per-harness, dropped on drop; needs the crate’spostgresfeature and a server atFZ_TEST_POSTGRES_URL).TestHarness::all_dialects(make_modules)/all_dialects_with_ports(make_modules, patch)build one kit per dialect available in the environment so a module suite loops over them — one test definition, every engine. The module factory runs once per dialect: modules may carry per-build state, so kits never share instances. - Fakes:
FakeMailer(recordsMessages;SendOk/NotConfigured/Failmodes, switchable mid-test),FakeCaptcha(allow-all or token list),FakeRateLimiter(scriptedDecisions + call count),FixedClock,MemoryKeyValue,FakeHttpClient(scripted responses + captured requests),EmptyDatabase(servicesSELECT 1only),FakeDefer(collects deferred futures;drain().awaitruns them), and aSignerwith the fixed dummyTEST_HARNESS_SECRET. request(&router, method, path, json?) -> TestResponse { status, headers, json() }—tower::ServiceExt::oneshot, no network.conformance(module)— the shared suite, run once per available dialect: mounts + health listing, request under the prefix, migrations apply twice on fresh databases,view_forhides undeclared ports, the two-concurrent-requests request-id test (ADR 0007), and — for modules with awell_knownrouter — that it serves at the root/.well-knownand never under/v1(#46).assert_wasm_safe_deps(env!("CARGO_PKG_NAME"))—cargo treecheck: noworker/wasm-bindgen/tokio/reqwestin the module’s normal dependency tree.
The in-memory Database is cratefield-adapter-sqlite; assertions on
kit.db see exactly what the module wrote. On the Postgres leg kit.db
is a pool on the kit’s own tokio runtime marshalled per call, so
pollster-driven tests, spawned threads and deferred handlers all reach
it.
Structs§
- Empty
Database - Fake
Captcha - Fake
Defer - Fake
Dispatcher - An in-process
Dispatcherthat answers from an axumRouter, so a module can be exercised through a sidecar mount without a network or a second Worker (ADR 0009). The conformance kit uses it to run the same assertions against both mounts (#64). - Fake
Http Client - Fake
Mailer - Fake
Rate Limiter - Fake
Sidecar - A dispatcher backed by an in-process router.
- Fixed
Clock - Memory
Blob - An in-memory
cratefield_core::Blobstore for module tests: keeps objects in a map, and has no presigned URLs (sosigned_urlreportsUnsupported, as a directory store does). - Memory
KeyValue - Test
Harness - A harness with every port faked and a migrated database, migrations
applied per module on creation. The same database handle is wired
into the router and exposed for assertions — SQLite in memory by
default (
TestHarness::new), or a throwaway Postgres 16 database for the parity suite (TestHarness::with_database, issue #20). - Test
Response - A fully-buffered test response.
Enums§
- Dialect
- The database engine backing a test harness.
- Fault
- A way the hop can be wrong. Used by the kit’s own tests to prove the parity checks fail when the forwarder misbehaves; never by a module.
- Mailer
Mode
Constants§
Functions§
- assert_
wasm_ safe_ deps - Asserts the named crate’s normal dependency tree is wasm-safe: no
worker,wasm-bindgen,tokio,reqwest(ADR 0001). Runscargo tree -p <crate> --edges normalin the caller’s workspace — call it withenv!("CARGO_PKG_NAME")from a module test. - conformance
- Runs the shared conformance suite against one module, once per
dialect available in the environment (issue #20 — SQLite always,
Postgres when
FZ_TEST_POSTGRES_URLnames a server): - conformance_
in_ process_ only conformancewithout the sidecar parity axis (issue #64).reasonis recorded in code and printed by the run, so a module that opts out says why in the same place it opts out. Use it only for a module that genuinely cannot be sidecar-mounted; “it fails” is not a reason.- request
- Sends a request through the router without a network.
json(whenSome) becomes a JSON body withcontent-type: application/json. - shared_
sidecar - Wraps a
FakeSidecarso the same handle can be given toPortsand still be asked how many calls it saw. - sidecar_
parity - Asserts that a module answers identically in-process and behind a sidecar mount (issue #64, ADR 0009: a caller cannot tell which).