| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102 |
- /**
- * Shared KV-backend conformance suite. Each backend's spec file calls
- * {@link runKvBackendContract} with a factory bound to its own medium; the
- * suite asserts every clause of the `src/backend.ts` contract so both
- * backends are held to identical semantics.
- * @module
- */
- import { describe, expect, it } from 'vitest'
- import type { KvUnitDescriptor, StorageBackend } from '../src/backend.ts'
- /** One conformance run: a fresh backend plus a way to reopen the same medium (crash simulation). */
- export interface KvBackendContractHarness {
- /** The backend under test, freshly created over an empty medium. */
- backend: StorageBackend
- /** Open a NEW backend instance over the SAME medium, as after a process restart. */
- reopen(): Promise<StorageBackend>
- }
- const DESCRIPTOR: KvUnitDescriptor = {
- name: 'contract_unit',
- version: 3,
- tables: ['alpha', 'beta'],
- hasGlobal: true,
- }
- /**
- * Run the shared conformance suite against one backend implementation.
- * @param label - Suite label, e.g. `json` / `sqlite`.
- * @param create - Factory producing a fresh harness per test.
- */
- export function runKvBackendContract(label: string, create: () => Promise<KvBackendContractHarness>) {
- describe(`kv backend contract: ${label}`, () => {
- it('opens a missing unit as empty and serves loadAll immediately', async () => {
- const { backend } = await create()
- const unit = await backend.kv!.open(DESCRIPTOR)
- const snapshot = await unit.loadAll()
- expect(snapshot.tables).toEqual({ alpha: {}, beta: {} })
- expect(snapshot.global).toBeNull()
- await backend.close()
- })
- it('round-trips records and global durably across reopen', async () => {
- const harness = await create()
- const unit = await harness.backend.kv!.open(DESCRIPTOR)
- await unit.putRecord('alpha', 'k1', { n: 1 })
- await unit.putRecord('alpha', 'k2', { n: 2 })
- await unit.putRecord('beta', 'weird key / with:stuff', { ok: true })
- await unit.setGlobal({ counter: 7 })
- await harness.backend.close()
- const reopened = await harness.reopen()
- const unit2 = await reopened.kv!.open(DESCRIPTOR)
- const snapshot = await unit2.loadAll()
- expect(snapshot.tables['alpha']).toEqual({ k1: { n: 1 }, k2: { n: 2 } })
- expect(snapshot.tables['beta']).toEqual({ 'weird key / with:stuff': { ok: true } })
- expect(snapshot.global).toEqual({ counter: 7 })
- await reopened.close()
- })
- it('putRecord overwrites and deleteRecord is idempotent', async () => {
- const { backend } = await create()
- const unit = await backend.kv!.open(DESCRIPTOR)
- await unit.putRecord('alpha', 'k', { v: 'old' })
- await unit.putRecord('alpha', 'k', { v: 'new' })
- await unit.deleteRecord('alpha', 'k')
- await unit.deleteRecord('alpha', 'k')
- await unit.deleteRecord('alpha', 'never-existed')
- const snapshot = await unit.loadAll()
- expect(snapshot.tables['alpha']).toEqual({})
- await backend.close()
- })
- it('rejects a version mismatch on reopen without touching the data', async () => {
- const harness = await create()
- const unit = await harness.backend.kv!.open(DESCRIPTOR)
- await unit.putRecord('alpha', 'k', { v: 1 })
- await harness.backend.close()
- const reopened = await harness.reopen()
- await expect(reopened.kv!.open({ ...DESCRIPTOR, version: 4 })).rejects.toMatchObject({
- name: 'StorageError',
- code: 'version-mismatch',
- })
- // Original version still opens and still holds the data.
- const unit2 = await reopened.kv!.open(DESCRIPTOR)
- expect((await unit2.loadAll()).tables['alpha']).toEqual({ k: { v: 1 } })
- await reopened.close()
- })
- it('rejects operations after unit close, and close is idempotent', async () => {
- const { backend } = await create()
- const unit = await backend.kv!.open(DESCRIPTOR)
- await unit.close()
- await unit.close()
- await expect(unit.putRecord('alpha', 'k', {})).rejects.toMatchObject({ code: 'closed' })
- await expect(unit.loadAll()).rejects.toMatchObject({ code: 'closed' })
- await backend.close()
- await backend.close()
- })
- })
- }
|