Purpose
Protect the SDK contracts that apps depend on: signed data, author ownership, convergent records, precise partial results, durable writes, and released resources.
The fast suite has 430 tests in 33 files. Separate environment projects add browser and external service checks. Tests are selected by risk and behavior. There is no line coverage target. Each test name states its contract or the failure it prevents. A passing suite establishes the stated behaviors within the tested environment.
Run the suite
Use Node 22.12+ and install the locked dependencies with npm ci.
| Command | Purpose |
|---|---|
npm run test-unit |
Functions, signer races, SDK behavior, cache isolation, durable commit faults, private group state, and type contracts |
npm run test-integration |
Relay messages, realtime, recovery, persistence, private group membership, real HTTP storage, and the built package |
npm test |
Both Vitest projects |
npm run test-mutations |
Check that eight named contract tests detect deliberate faults |
npm run check |
Formatting, lint, TypeScript, both test projects, and ESM/declaration build |
npm run prepare:integrations |
Download pinned independent services, browsers, and extension inputs |
npm run test-environments |
Independent relay, Blossom, NIP-46, network fault, crash, and bounded load projects |
npm run test-browser |
Real IndexedDB and browser lifecycle checks in Chromium, Firefox, and WebKit |
npm run test-extension |
Actual NIP-07 extension permission and signing checks |
npm run test-extended |
All local environment and browser projects |
npm run test-live-services |
Explicitly configured deployment checks; excluded from the local aggregate |
Select one failure contract while working:
npm run test-unit -- -t "a failed durable enqueue"
npx vitest run --project integration tests/network.integration.test.ts
npm run typecheck is required for type tests. Vitest transpiles TypeScript; it does not replace the compiler’s checks of expectTypeOf and @ts-expect-error contracts. The package test also invokes TypeScript in a separate consumer directory.
The integration project includes existing mixed suites. Their simulated transport tests remain there with the related WebSocket and persistence tests.
Separate environment projects
See the environment guide for prerequisites, software provenance, commands, artifacts, and each project’s purpose. The named Vitest projects are defined in vitest.environment.config.ts. Browser and extension checks have separate Playwright configurations. They are excluded from npm test so the fast gate remains available without Docker or downloaded browsers.
Each environment project must run its actual boundary. Independent relay and Blossom checks use pinned upstream software. Real browser checks use browser IndexedDB. Remote signing uses an Applesauce NIP-46 provider in another process. Extension signing uses a loaded upstream extension. Network faults use controlled proxies, and crash checks terminate test processes abruptly. A missing prerequisite fails with setup instructions. Tests are not silently skipped.
Live-service checks require explicit endpoints. They default to read-only operations. Writes require a separate operator flag and a test identity. There are no default public relay or Blossom URLs.
Contract and risk map
| Area | Failure prevented | Evidence |
|---|---|---|
| Record encoding | Ambiguous addresses, changed JSON types, omitted false/zero indexes, unsupported versions, incorrect scope, invalid creation times | protocol.unit.test.ts, sdk.test.ts |
| Signature verification | Forged fields, malformed wire objects, and reused verification symbols expose unsigned data | protocol.unit.test.ts, tooling.test.ts, network.integration.test.ts |
| Auth and signing | An older sign-in overwrites a newer one; sign-out or signer mutation permits a delayed write; observer errors change auth state | auth-transport.unit.test.ts, sdk.test.ts, tooling.test.ts |
| Key backup | Wrong passwords replace a session; malformed envelopes run an unbounded KDF; cancellation exposes key data; backup errors contain secrets | key-backup.unit.test.ts, browser recovery.spec.ts |
| Rich queries | Logical branches discard candidates; JSON paths read inherited values; mutated filter input changes queries; count changes with page size | query.unit.test.ts, rich-query-types.unit.test.ts, rich-queries.integration.test.ts |
| Automatic replay | Duplicate concurrent loops, old callbacks change a new loop, auth transitions send stale work, rejected queues disappear, group recovery returns stale state | auto-replay.unit.test.ts, auto-replay.integration.test.ts, browser recovery.spec.ts |
| Image processing | Transform pixels or orientation are wrong; padding flattens alpha; oversized cover intermediates fail; upload hashes identify original bytes; download transforms unverified bytes | image-processing.integration.test.ts, browser images.spec.ts |
| Query semantics | Coerced comparisons, incorrect subsets, missing/null confusion, wrong sort/range order, widened empty ID filters, invalid queries reaching a relay | query.unit.test.ts, sdk.test.ts |
| Cursor pages | Equal timestamps duplicate boundaries, a foreign cursor crosses scope, or an old version reappears because newer versions were outside a cursor bound | tooling.test.ts, private-storage.test.ts |
| Record convergence | Arrival order, duplicate echoes, unauthorized tombstones, or deletion/recreation change the final record | state.unit.test.ts, sdk.test.ts |
| Mutation results | Cancellation or a middle batch failure loses committed data, attempted receipts, or the write lock | state.unit.test.ts, sdk.test.ts, private-storage.test.ts |
| Cache ownership | A transport, native query result, or callback changes verified data without a new signature | cache-isolation.unit.test.ts, state.unit.test.ts |
| Channels | False deletes, changed observer payloads, duplicate changes, filter transition errors, or leaked subscriptions | state.unit.test.ts, sdk.test.ts, relay.test.ts |
| Broadcast and Presence | Expired or out-of-scope traffic is accepted; stale heartbeats restore a departed session; queued ephemeral traffic or signer changes leak state | realtime.test.ts |
| Personal encryption | Plaintext enters wire/cache/backups; another author reads data; slow decrypt/publish crosses an account change; old versions emit changes | private-storage.test.ts, cache-isolation.unit.test.ts, state.unit.test.ts, network.integration.test.ts |
| Group trust and membership | Forged or foreign proofs enter private records; removed authors gain new writes; noncanonical branches stay visible; peer relay metadata selects unauthorized destinations | group-records.unit.test.ts, group-network.unit.test.ts, groups.integration.test.ts, groups-failures.integration.test.ts |
| Group query scope | Invalid or missing groups fall back to public data; concurrent lookups lose writes; cancellation exposes stale handles or drops accepted receipts | query.unit.test.ts, groups-api.integration.test.ts, groups-failures.integration.test.ts, groups-restart.integration.test.ts, types.test.ts |
| Group durability | Consumed MLS state loses received records; retries create fresh ciphertext; partial acknowledgements lose receipts; restart loses pending Welcomes; device or account state crosses scopes | group-store.unit.test.ts, group-recovery.unit.test.ts, group-ingress.unit.test.ts, groups-restart.integration.test.ts, groups-failures.integration.test.ts |
| Blossom | Wrong bytes/hashes, invalid descriptors, broad authorization, hidden partial delete failure, redirect credential forwarding, or uncancelled body reads | private-storage.test.ts, storage.integration.test.ts |
| Persistence | Namespace crossover, reference mutation, partially committed IndexedDB batches, overlapping cache writes, missing tombstones, or lost writes at close | durability.unit.test.ts, offline-sync.test.ts, network.integration.test.ts |
| Offline delivery | Failed disk writes expose optimistic success; capacity races overfill the queue; retry re-signs an event; simultaneous flushes duplicate delivery; account changes publish another queue | durability.unit.test.ts, offline-sync.test.ts, tooling.test.ts |
| Reconciliation | Missing IDs are silently lost, requests exceed the batch bound, cancelled responses enter the cache, partial data disappears, or cached third-party events are published | sync-boundaries.unit.test.ts, offline-sync.test.ts, network.integration.test.ts |
| Migrations | A late invalid transform causes early writes; account changes rewrite another author’s data; skipped rows write; obsolete fields persist; partial progress is lost | tooling-boundaries.unit.test.ts, tooling.test.ts |
| Backups | A late invalid event partially imports; foreign e-only deletes gain authority; stale copies revive deleted rows; imported/exported objects corrupt the cache | tooling-boundaries.unit.test.ts, tooling.test.ts, private-storage.test.ts |
| References | Two authors with the same ID resolve to the wrong record, or missing batch records lose their positions | tooling.test.ts |
| Dashboard and diagnostics | Private plaintext appears in inspection/logs, HTML contains executable record data, or diagnostic buffers grow without a bound | tooling.test.ts |
| Transport lifetime | Values reset an absolute timeout, synchronous completion misses teardown, cancellation leaves subscriptions, or relay CLOSED is ignored | auth-transport.unit.test.ts, relay.test.ts, network.integration.test.ts |
| Shared resources | Closing one client closes another client’s injected store or pool | sdk.test.ts, network.integration.test.ts |
| Distribution and types | Source-only tests pass while the archive lacks exports, usable declarations, working encrypted APIs, or browser module compatibility | types.test.ts, package.integration.test.ts |
Test design
- Use fixed development keys. No production keys or public relays are required.
- Use actual Schnorr signatures and NIP-44 encryption for data trust tests. Do not mock verification as successful.
- The new relay and Blossom fixtures verify wire behavior independently of SDK encoding and replacement helpers.
- Use loopback servers on operating-system assigned ports. Poll only for external socket state. Do not use fixed sleeps.
- Use deferred promises to stop at an exact async boundary. Use fake timers to inspect absolute deadlines and timer cleanup.
- Register cleanup before assertions. The shared scope closes resources in reverse order and reports cleanup errors. Release fault gates before closing their dependent resources.
- Inspect data, receipts, persisted state, and wire side effects. A rejected promise alone does not establish that a rejected write stayed out of the cache.
- Model arrival-order tests use 32 fixed seeds with replacement ties, author-scoped deletes, recreation, and duplicate echoes. Failure messages include the seed. Expected winners come from the protocol rule, independent of SDK comparison helpers.
- Use mixed valid/invalid batches to prove validation happens before the first write or import. Use a failure after relay acknowledgement to prove safe exact-event replay.
- The package test builds, packs without running prepack recursively, unpacks into a temporary consumer, compiles its types, runs its ESM code, and bundles its exports for a browser. Only installed dependency directories are linked. The SDK itself is loaded from the archive.
Deliberate-fault checks
npm run test-mutations runs the relevant unchanged tests first. It then changes one behavior at a time in a temporary copy:
- Retain a caller-owned event in the verified cache.
- Ignore cancellation on a cache read.
- Publish the caller’s mutable signed event.
- Share table change payloads between observers.
- Emit a deletion of an older version while a newer one exists.
- Expose optimistic state before the queue commit succeeds.
- Accept record content with an incorrect namespace.
- Remove write author checks.
Each fault must cause its named behavioral test to fail. Collection errors, compiler errors, missing reports, and runner timeouts do not count as detection. The script deletes the temporary copy and leaves workspace source unchanged. This is a small contract check, not an exhaustive mutation score.
GitHub CI is configured to run npm run check and these fault checks on Node 22.12 and 24. Separate jobs run the environment projects and retain failure logs and browser traces. A local pass does not prove that hosted CI has run.
Limits
The fast suite uses fake-indexeddb and simulated signer/service fixtures where exact fault control is needed. The separate projects check real browsers, independent services, NIP-46, and extension permissions. Their scoped guides state the exact software and fault boundary tested. Local passes do not establish compatibility with a public deployment, a different extension, Safari, S3 storage, or production retention policies.
The suite does not claim PostgreSQL transactions, permanent relay storage, deletion from every copy, global query completeness, or a performance service level. The load project uses a bounded reproducible workload and records metrics. It checks SDK lifecycle contracts; it does not set a production throughput target.
Full application checks
Fieldwork installs a freshly packed SDK into a separate application and runs named user flows in Chromium, Firefox, and WebKit against independent nak 0.20.7 relay and Blossom services. The app tests use real IndexedDB, signatures, NIP-44, NIP-77, CORS, file downloads, and a separate NIP-46 signer process.
Run npm run example:test. Run npm run example:test:fallback to repeat recovery with NIP-77 disabled. Missing relay capability cannot count as feature success. These checks are separate from the fast default suite and from the other environment projects.
See the example’s feature map, test contracts, developer experience, and stated limits. In particular, offline reload restores HTTP access first; this suite does not claim offline installation, forced browser crashes, or third-party wallet UI approval.