Skip to content

Testing domain -- L1

1. Shared testkit

posthaste-testkit is the dev-only library ([dev-dependencies]) that standardizes lower-layer test support across crates. It is the reference implementation of this contract.

Surface Responsibility State
Harness Disposable config repo + SQLite store + MailService on a temp root; save_account helper. Exposes service and store for direct driving. realized
StalwartFixture Disposable real Stalwart on free loopback ports, seeded via tools/dev/stalwart; jmap_transport / imap_transport / email accessors; inject(count) SMTP burst; tears down on drop. realized
paths temp_root, free_loopback_port, stalwart_bin, workspace_root. realized
Harness::with_runtime Stand up an in-process authority server (the posthaste-runtime-api + posthaste-client-link surfaces) against the harness config root; exposes the handle, store, and event bus. realized
View-settlement recorder ViewSettlement: captures the ordered RuntimeFrame stream a mutation settles through; asserts confirmed / recompute-present / only-touched-view / seq-monotonic. realized
Live-account + sync-driven watch create_jmap_account (JMAP account over a StalwartFixture), watch_view / ViewWatch (a sync-driven drain that stays subscribed across an external action and waits until a snapshot satisfies a predicate). The live scenario (twenty_injected_messages_converge_into_inbox_view) drives a real Stalwart through the app's real sync path, querying the inbox by its actual MailboxId. realized
Declarative fixtures Fixture / FixtureAccount / FixtureMessage typed TOML (accounts + messages with field overrides) loaded via RuntimeHarness::load_fixture_toml / load_fixture, driving create_mock_account + seed_messages_typed. realized
Mock Gmail IMAP fixture GmailImapFixture: a stateful, multi-connection mock Gmail IMAP server answering the discovery + full-snapshot and CONDSTORE/QRESYNC-delta sync command sets; create_gmail_account wires an ImapSmtp account at it so a seeded X-GM-LABELS INBOX message lands in the store and mailList view, and vanish_inbox_and_deliver + sync_account drives a CHANGEDSINCE/VANISHED delta. Variants (start_condstore_only, start_generic_uidplus, start_generic_without_uidplus) exercise capability-degraded paths. Omits IDLE (no push stream); IDLE-push parity is a later increment. Also shipped as a runnable dev server (src/bin/mock-gmail.rs, POSTHASTE_DEV_GMAIL=1 just dev) so the dev app can point a Gmail IMAP account at the same fixture. realized
Client testkit (apps/web/test/harness) The deterministic browser-client testkit (D119): a fake transport (createFakeTransport, with severLink()/severWith(status) to inject stream errors), a loopback replica fakeWorker, a virtualClock, and the real wasm handle (loadRealHandleFactory), composed by createClientHarness into one driveable client. Scenario tests (scenarios/) cover gap frames, stream-sever reopen (M40 re-prepare), unload flush, and worker-wedge restart. realized

Tests that touch a real Stalwart gate on POSTHASTE_STALWART_INTEGRATION=1 and skip otherwise; the fixture panics on spawn failure (an environment, not test, failure).

2. Verification ladder

Rung Driver Proves State
1. Unit/integration cargo / nextest against posthaste-testkit::Harness domain, store, sync, API logic realized
2. Frontend logic bun test (incl. the apps/web/test/harness client testkit) cache invalidation, routing, IPC adapters, replica/worker resilience scenarios realized
3. Built-web automation Playwright vs built assets + lab daemon rendered client behavior realized
4. Linux Tauri automation feature-gated tauri-playwright bridge real desktop app realized
5. Agent-driven full app posthastectl + declarative fixtures + settlement recorder multi-step convergence, view-update bugs planned

[::state partial plan=docs/eph/PLAN-L2-testkit-roadmap#roadmap]

3. Coverage model

Each behavior boundary must have focused tests. Tests are named by the behavior they prove, use Arrange/Act/Assert, and link their spec assertion with a // spec: docs/...#id comment when one exists.

Provider observation matrix

Each row is at least one focused test unless explicitly out of scope.

Area Observation Expected local outcome
Gmail IMAP labels Same message via Sent/Starred/All Mail One message, multiple ImapMessageLocations, unioned membership, one keyword set
Generic IMAP copies Same RFC Message-ID, different UIDs Separate messages unless a stable identity proves equality
IMAP flags Remote flag change, no UIDNEXT change MODSEQ/QRESYNC delta applies keywords; no skip on UID count
IMAP expunge UID disappears from a mailbox Location removed; deleted only when no membership remains
IMAP UIDVALIDITY UIDVALIDITY changes Stored locations invalidated by authoritative refresh
IMAP VANISHED QRESYNC returns VANISHED UIDs Matching locations deleted without pruning unrelated messages
JMAP changes cannotCalculateChanges Full authoritative snapshot replaces the affected set
Local mutation echo Local keyword/mailbox mutation succeeds remotely All visible views reflect it before a manual sync
Remote convergence Remote mutation by another client Push/poll converges every view without stale per-mailbox state

Store, runtime, API, frontend contracts

  • Store reconciliation: SyncBatch application is atomic, account-scoped, projection-safe, event-complete.
  • Runtime view update: a mutation or sync delta produces the minimal correct view-diff; no missed recompute, no over-broad invalidation. The settlement recorder (planned) asserts this on the ordered diff stream.
  • API boundary: stable error codes, pagination cursors, SSE replay, mutation-response shape.
  • Frontend state: user-visible transitions, not component internals.

4. Test quality standard

  • Name tests by the behavior they prove.
  • Arrange / Act / Assert, visually distinct, one logical assertion per test.
  • No incidental implementation assertions.
  • Link SPECial assertions: // spec: docs/testing/L1#provider-observation-matrix.
  • Red-first: write the smallest failing test, confirm it fails for the right reason, then implement.
  • Error-path tests assert the typed error and its context, not "something threw".
  • Prefer real behavior through StalwartFixture over mocking; mock only true system boundaries (provider responses for unit tests that must not touch a server).

5. Fixtures and Stalwart

A real Stalwart with real email is the parity substrate. StalwartFixture manages a disposable instance (free ports, temp data, seeded dev@example.org). Tests inject mail by pre-seeding the maildir or by live SMTP/LMTP delivery; the app's own live sync (JMAP WebSocket push / IMAP IDLE) observes the delta — the observation path is the system under test and must not be bypassed.

Declarative Fixture TOML (accounts + messages with field overrides) loads via RuntimeHarness::load_fixture_toml / load_fixture over the programmatic create_mock_account + seed_messages_typed helpers. JMAP / provider-state fixtures remain planned (the loader only supports driver = "mock" today). Programmatic setup (Harness::save_account, StalwartFixture::start) remains for cases that need direct control.

[::state partial plan=docs/eph/PLAN-L2-testkit-roadmap#roadmap]

6. Forward contracts

The settlement recorder and runtime-in-harness landed (P2); declarative TOML fixtures landed (P3b). The remaining forward contract — the posthastectl headless driver — is specified in docs/eph/PLAN-L2-testkit-roadmap.md and folds back here as it lands.

[::state planned plan=docs/eph/PLAN-L2-testkit-roadmap#roadmap]

Assertions

ID Sev. Assertion
shared-testkit-surface MUST Lower-layer tests use posthaste-testkit for harness, fixtures, and path helpers rather than ad-hoc copies
real-provider-parity MUST Parity tests drive a real Stalwart through StalwartFixture and observe via the app's real sync path, never by injecting into the local store
integration-gate MUST Real-Stalwart tests gate on POSTHASTE_STALWART_INTEGRATION=1 and skip otherwise
provider-observation-contracts MUST Provider adapters are tested against the observation matrix before output reaches the store
sync-convergence-contracts MUST Local and remote mutations converge across every affected mailbox/smart/tag view
store-reconciliation-contracts MUST Store sync tests prove account scoping, atomicity, event emission, projection refresh, deletion
api-boundary-contracts MUST API tests prove error codes, pagination, SSE replay, mutation responses
frontend-state-contracts MUST Frontend tests prove user-visible state transitions, not internals
declarative-fixtures MUST load_fixture_toml / load_fixture create declared accounts and seed declared messages (with field overrides) so a view reflects the fixture
view-settlement-correctness SHOULD Runtime view updates assert minimal correct diffs via the settlement recorder (realized)
spec-linked-coverage SHOULD Tests for SPECial assertions link with a spec: comment