Skip to main content

Crate cratefield_testing

Crate cratefield_testing 

Source
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 Harness. The open-source core. Modules are crates, compiled into one stateless Worker with its own database.

cratefield-testing on crates.io cratefield-testing documentation MIT

§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’s postgres feature and a server at FZ_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 (records Messages; SendOk/NotConfigured/Fail modes, switchable mid-test), FakeCaptcha (allow-all or token list), FakeRateLimiter (scripted Decisions + call count), FixedClock, MemoryKeyValue, FakeHttpClient (scripted responses + captured requests), EmptyDatabase (services SELECT 1 only), FakeDefer (collects deferred futures; drain().await runs them), and a Signer with the fixed dummy TEST_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_for hides undeclared ports, the two-concurrent-requests request-id test (ADR 0007), and — for modules with a well_known router — that it serves at the root /.well-known and never under /v1 (#46).
  • assert_wasm_safe_deps(env!("CARGO_PKG_NAME"))cargo tree check: no worker/wasm-bindgen/tokio/reqwest in 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§

EmptyDatabase
FakeCaptcha
FakeDefer
FakeDispatcher
An in-process Dispatcher that answers from an axum Router, 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).
FakeHttpClient
FakeMailer
FakePayments
An in-memory cratefield_core::Payments for module tests: records which calls were made and answers per its PaymentsMode. verify_webhook treats a signature header of "invalid" as a tampered event.
FakePush
An in-memory cratefield_core::Push for module tests: records every (token, notification) and answers according to its PushMode.
FakeRateLimiter
FakeSidecar
A dispatcher backed by an in-process router.
FixedClock
MemoryBlob
An in-memory cratefield_core::Blob store for module tests: keeps objects in a map, and has no presigned URLs (so signed_url reports Unsupported, as a directory store does).
MemoryKeyValue
TestHarness
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).
TestResponse
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.
MailerMode
PaymentsCall
What a FakePayments recorded, for assertions.
PaymentsMode
How a FakePayments responds.
PushMode
How a FakePush responds, mirroring MailerMode for the push port.

Constants§

TEST_HARNESS_SECRET

Functions§

assert_wasm_safe_deps
Asserts the named crate’s normal dependency tree is wasm-safe: no worker, wasm-bindgen, tokio, reqwest (ADR 0001). Runs cargo tree -p <crate> --edges normal in the caller’s workspace — call it with env!("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_URL names a server):
conformance_in_process_only
conformance without the sidecar parity axis (issue #64). reason is 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 (when Some) becomes a JSON body with content-type: application/json.
shared_sidecar
Wraps a FakeSidecar so the same handle can be given to Ports and 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).