Files
deepseek-harness/packages/storage/storage/tests/contract.ts
T

258 lines
12 KiB
TypeScript

/**
* 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('deleteRecords removes the whole batch durably and is idempotent', 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('alpha', 'k3', { n: 3 })
await unit.putRecord('beta', 'b1', { n: 4 })
await unit.deleteRecords('alpha', ['k1', 'k3', 'missing', 'k1'])
await unit.deleteRecords('alpha', [])
expect((await unit.loadAll()).tables).toEqual({ alpha: { k2: { n: 2 } }, beta: { b1: { n: 4 } } })
await harness.backend.close()
const reopened = await harness.reopen()
const unit2 = await reopened.kv!.open(DESCRIPTOR)
expect((await unit2.loadAll()).tables).toEqual({ alpha: { k2: { n: 2 } }, beta: { b1: { n: 4 } } })
await reopened.close()
})
it.each([2, 4])('rejects version %s on reopen without touching the data', async (version) => {
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 })).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('reads an accepted older version without changing its stamp or records', async () => {
const harness = await create()
const original = await harness.backend.kv!.open(DESCRIPTOR)
await original.putRecord('alpha', 'k', { v: 1 })
await original.setGlobal({ counter: 1 })
await harness.backend.close()
const current = { ...DESCRIPTOR, version: 4, compatibleVersions: [3] }
const reader = await harness.reopen()
const unit = await reader.kv!.open(current)
expect(await unit.loadAll()).toEqual({ tables: { alpha: { k: { v: 1 } }, beta: {} }, global: { counter: 1 } })
await reader.close()
const unchanged = await harness.reopen()
expect((await (await unchanged.kv!.open(DESCRIPTOR)).loadAll()).tables['alpha']).toEqual({ k: { v: 1 } })
await unchanged.close()
})
it.each(['deleteRecord', 'deleteRecords', 'emptyBatch'] as const)('keeps the older stamp for a no-op %s', async (operation) => {
const harness = await create()
const original = await harness.backend.kv!.open(DESCRIPTOR)
await original.putRecord('alpha', 'saved', { v: 1 })
await harness.backend.close()
const writer = await harness.reopen()
const unit = await writer.kv!.open({ ...DESCRIPTOR, version: 4, compatibleVersions: [3] })
if (operation === 'deleteRecord') await unit.deleteRecord('alpha', 'missing')
else await unit.deleteRecords('alpha', operation === 'emptyBatch' ? [] : ['missing', 'missing'])
await writer.close()
const unchanged = await harness.reopen()
expect((await (await unchanged.kv!.open(DESCRIPTOR)).loadAll()).tables['alpha'])
.toEqual({ saved: { v: 1 } })
await unchanged.close()
})
it('restores the open snapshot after serialization failure and retries on the same unit', async () => {
const harness = await create()
await (await harness.backend.kv!.open(DESCRIPTOR)).putRecord('alpha', 'saved', { v: 1 })
await harness.backend.close()
const current = { ...DESCRIPTOR, version: 4, compatibleVersions: [3] }
const writer = await harness.reopen()
const unit = await writer.kv!.open(current)
await expect(unit.putRecord('alpha', 'broken', { toJSON: () => { throw new Error('serialization failed') } }))
.rejects.toThrow('serialization failed')
expect((await unit.loadAll()).tables['alpha']).toEqual({ saved: { v: 1 } })
await unit.putRecord('alpha', 'retry', { v: 2 })
await writer.close()
const restarted = await harness.reopen()
expect((await (await restarted.kv!.open(current)).loadAll()).tables['alpha'])
.toEqual({ saved: { v: 1 }, retry: { v: 2 } })
await restarted.close()
})
it.each(['putRecord', 'deleteRecord', 'deleteRecords', 'setGlobal'] as const)('%s commits the current stamp with its data', async (operation) => {
const harness = await create()
const original = await harness.backend.kv!.open(DESCRIPTOR)
await original.putRecord('alpha', 'k', { v: 1 })
await original.putRecord('beta', 'untouched', { saved: true })
await original.setGlobal({ counter: 1 })
const otherDescriptor = { ...DESCRIPTOR, name: 'other_unit' }
await (await harness.backend.kv!.open(otherDescriptor)).putRecord('alpha', 'saved', { v: 1 })
await harness.backend.close()
const current = { ...DESCRIPTOR, version: 4, compatibleVersions: [3] }
const writer = await harness.reopen()
const unit = await writer.kv!.open(current)
await expect(unit.putRecord('alpha', 'k', { toJSON: () => { throw new Error('serialization failed') } }))
.rejects.toThrow('serialization failed')
expect((await unit.loadAll()).tables['alpha']).toEqual({ k: { v: 1 } })
await writer.close()
const afterFailure = await harness.reopen()
expect((await (await afterFailure.kv!.open(DESCRIPTOR)).loadAll()).tables['alpha']).toEqual({ k: { v: 1 } })
await afterFailure.close()
const retry = await harness.reopen()
const writable = await retry.kv!.open(current)
await writable.deleteRecord('alpha', 'missing')
await writable.deleteRecords('alpha', [])
if (operation === 'putRecord') await writable.putRecord('alpha', 'k', { v: 2 })
else if (operation === 'deleteRecord') await writable.deleteRecord('alpha', 'k')
else if (operation === 'deleteRecords') await writable.deleteRecords('alpha', ['k', 'missing', 'k'])
else await writable.setGlobal({ counter: 2 })
await retry.close()
const restarted = await harness.reopen()
await expect(restarted.kv!.open(DESCRIPTOR)).rejects.toMatchObject({ code: 'version-mismatch' })
const saved = await (await restarted.kv!.open(current)).loadAll()
expect(saved.tables['beta']).toEqual({ untouched: { saved: true } })
expect(saved.tables['alpha']).toEqual(
operation === 'deleteRecord' || operation === 'deleteRecords' ? {} : { k: { v: operation === 'putRecord' ? 2 : 1 } },
)
expect(saved.global).toEqual({ counter: operation === 'setGlobal' ? 2 : 1 })
expect((await (await restarted.kv!.open(otherDescriptor)).loadAll()).tables['alpha'])
.toEqual({ saved: { v: 1 } })
await restarted.close()
})
it.each([
{ name: 'v1 → v3', writeV2: false },
{ name: 'v1 → v2 → v3', writeV2: true },
])('reads historical values through $name', async ({ writeV2 }) => {
const harness = await create()
const v1 = { ...DESCRIPTOR, version: 1 }
const v2 = { ...DESCRIPTOR, version: 2, compatibleVersions: [1] }
const v3 = { ...DESCRIPTOR, compatibleVersions: writeV2 ? [2] : [1] }
const original = await harness.backend.kv!.open(v1)
await original.putRecord('alpha', 'untouched', { label: 'Keep' })
await original.putRecord('alpha', 'edited', { label: 'Edit' })
await harness.backend.close()
if (writeV2) {
const second = await harness.reopen()
const middle = await second.kv!.open(v2)
await middle.putRecord('alpha', 'edited', { title: 'Edit' })
await second.close()
}
const third = await harness.reopen()
const latest = await third.kv!.open(v3)
expect((await latest.loadAll()).tables['alpha']).toEqual({
untouched: { label: 'Keep' }, edited: writeV2 ? { title: 'Edit' } : { label: 'Edit' },
})
await latest.putRecord('alpha', 'edited', { title: 'Edit', addedInV3: true })
await third.close()
const restarted = await harness.reopen()
await expect(restarted.kv!.open(v2)).rejects.toMatchObject({ code: 'version-mismatch' })
expect((await (await restarted.kv!.open(v3)).loadAll()).tables['alpha']).toMatchObject({
untouched: { label: 'Keep' }, edited: { addedInV3: true },
})
await restarted.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 expect(unit.deleteRecords('alpha', ['k'])).rejects.toMatchObject({ code: 'closed' })
await backend.close()
await backend.close()
})
})
}