Skip to content
Ownfold
Esc
↑↓navigate↵open⌘Jpreview
On this page

Testing API reference

Complete reference for Ownfold compliance suites, in-memory adapters, transports, storage, lifecycle channels, encrypted-record repositories, and corruption helpers.

@ownfold/testing contains contract-compliant in-memory implementations and adversarial helpers. Use it in tests only.

pnpm add -D @ownfold/testing@beta

Export map

Export Implements or returns Primary use
checkVaultAdapterCompliance Compliance result Verify lifecycle adapter semantics.
checkVaultRotationAdapterCompliance Compliance result Verify rotation transactions and conflicts.
checkPasskeyVaultAccessAdapterCompliance Compliance result Verify passkey persistence, challenge consumption, revocation, and atomic root rotation.
InMemoryVaultAdapter VaultAdapter & VaultRotationAdapter Server unit/integration tests without a database.
InMemoryVaultTransport VaultTransport & VaultRotationTransport Browser lifecycle tests without HTTP.
createInMemoryVaultTransport InMemoryVaultTransport Concise transport construction.
MemoryVaultStorage VaultStorage Browser client tests without IndexedDB.
MemoryVaultLifecycleChannelHub Channel factory and message recorder Multi-tab lifecycle tests.
InMemoryEncryptedRecordRepository RotationRecordStore Key-rotation tests.
corruptBase64Url string Authentication/corruption negative tests.
assertSecretSafeTransportCapture Boundary assertion result Reject secret-shaped fields or supplied secret values in captured server inputs.
passkeyFailureScenario Deterministic scenario Model cancellation, PRF absence, replay, expiry, tampering, substitution, and storage failure.

Adapter compliance

checkVaultAdapterCompliance(adapter)

Runs the required vault, recovery, device, and pairing behavior against the supplied adapter. The suite checks successful operations, same-identity idempotency, conflicting identity, ownership scope, terminal state, stale revision rejection, display-only device rename semantics, environment metadata, and exact adapter input projection.

import { checkVaultAdapterCompliance } from "@ownfold/testing"

it("implements the lifecycle contract", async () => {
  const result = await checkVaultAdapterCompliance(createRealAdapter())
  expect(result.status).toBe("ok")
})

checkVaultRotationAdapterCompliance(adapter)

Checks begin, progress, stale checkpoint, completion, active-device coverage, atomic cutover, and idempotent completion behavior.

const result = await checkVaultRotationAdapterCompliance(adapter)
if (result.status === "error") throw result.error

Run both suites against a newly created disposable database for each test. The suite owns its fixed identifiers and expects no pre-existing rows. A passing in-memory run does not replace real driver, transaction, migration, and concurrency tests.

checkPasskeyVaultAccessAdapterCompliance(adapter)

Checks atomic one-time challenge consumption, cross-owner isolation, stale and duplicate credential rejection, usage revision updates, revocation without normal-device revocation, and all-or-nothing root-key rotation for active passkey recipients. Revoked recipients must retain the old envelope and must not be included in replacement coverage.

import { checkPasskeyVaultAccessAdapterCompliance } from "@ownfold/testing"

const result = await checkPasskeyVaultAccessAdapterCompliance(adapter)
if (result.status === "error") throw result.error

Run this suite with the lifecycle and rotation suites against every real adapter implementation.

InMemoryVaultAdapter

Could not generate a type table for InMemoryVaultAdapter: No interface or type named "InMemoryVaultAdapter" found.

The adapter holds validated records in process memory and implements official conflict semantics. Use a fresh instance per test to avoid state leakage.

const adapter = new InMemoryVaultAdapter()
const server = createVaultServer({
  adapter,
  getUserId: async () => "test-user",
})

It is not a production fallback: process exit loses data, instances do not coordinate, and memory does not model database isolation.

InMemoryVaultTransport

Could not generate a type table for InMemoryVaultTransport: No interface or type named "InMemoryVaultTransport" found.

The transport stores remote-safe lifecycle values and records observed operation names. It is useful for testing VaultClient without a server route.

const transport = createInMemoryVaultTransport()
const client = createVaultClient({
  transport,
  storage: new MemoryVaultStorage(),
  cryptoEngine: testCryptoEngine,
})

MemoryTransportOperation is the union of observable operation names from getVault through completeRotation. Assert on those names when verifying that invalid local state fails before a network call.

MemoryVaultStorage

Could not generate a type table for MemoryVaultStorage: No interface or type named "MemoryVaultStorage" found.

It implements main local vault state plus pending pairing, Recovery Kit replacement, and rotation records. clear() removes main local state; each pending workflow has its own clear method, matching the production storage contract.

Use a new instance per browser profile. Sharing one instance models reload in the same profile; separate instances model separate devices.

MemoryVaultLifecycleChannelHub

const hub = new MemoryVaultLifecycleChannelHub()

const first = createVaultClient({
  transport,
  lifecycleChannelFactory: hub.createChannel,
})
const second = createVaultClient({
  transport,
  lifecycleChannelFactory: hub.createChannel,
})

createChannel supplies same-name in-memory channels. observedMessages() returns all posted VaultLifecycleMessage values in order, allowing tests to assert cross-client lock and state-change propagation without BroadcastChannel.

Rotation record repository

InMemoryEncryptedRecordRepository implements the browser RotationRecordStore. Seed it with encrypted records, pass it to rewrapRotationRecords or useKeyRotation, and inspect the updated envelopes after each batch.

Could not generate a type table for InMemoryEncryptedRecordRepository: No interface or type named "InMemoryEncryptedRecordRepository" found.

Test interruption by stopping after a checkpoint, constructing a new client with the same storage and repository, and resuming. Assert that processed records are not duplicated or skipped.

Corruption helper

corruptBase64Url(value) changes a non-empty base64url value while keeping it structurally valid. Use it to test authenticated failure rather than parser failure.

const corrupted = {
  ...envelope,
  ciphertext: corruptBase64Url(envelope.ciphertext),
}

const result = await client.decryptJson({
  ...context,
  payload: corrupted,
})

expect(result.status).toBe("error")

For structural validation tests, supply malformed or unsupported-version data directly instead.

Boundary Minimum cases
Authentication Valid, anonymous, expired, malformed session, provider failure
Adapter Compliance suites, driver failure, corrupted stored JSON, concurrent stale writes
Transport Oversized/malformed response, non-success code, network failure, duplicate delivery
Browser lifecycle Create, verify, reload, lock, unlock, restore, unavailable device
Records JSON/binary round trip, wrong owner/namespace/record ID, corrupted ciphertext
Pairing Approve, cancel, expiry, stale revision, mismatched device envelope
Rotation Empty and multi-batch sets, interruption, resume, stale checkpoint, atomic completion
Multi-tab Remote lock, disposal, channel isolation, missed/duplicate notification tolerance
Passkey access PRF unavailable/cancelled, stripped transport, follow-up assertion, expiry/replay, cross-owner substitution, revocation, new-device enrollment/reload, active-recipient rotation

Production exclusion

Keep this package in devDependencies. Do not export test instances from application modules or bundle deterministic crypto/test storage into production. Production smoke tests should use packed public packages with real database and transport boundaries.

See Database adapters, contracts, and the complete export index.

Last updated on August 10, 2026

Was this page helpful?