Review findings applied across the group: - storage hub: stale disposers no longer remove a successor registration; the package now default-exports the Storage service class per the service-package export shape. - json backend: failed publishes roll back the authoritative memory state (a rejected write can no longer resurface via get() or ride the next publish); close() drains in-flight writes and blocks in-flight opens; double-open rejects as a plain caller error instead of malformed-medium. - sqlite backend: loadAll builds records on a null prototype (__proto__ keys round-trip instead of polluting), user_version is stamped only after the schema is fully created, and corrupt record JSON rejects as malformed-medium instead of a bare SyntaxError. - domain form: writes persist before mutating authoritative memory or emitting; DomainChanged is a put/deleted discriminated union. - workspace: attach/detach idempotence decided on the write chain (stale snapshots no longer short-circuit), create() requires a directory, and startup fails loud on duplicate stored paths. Eleven regression tests pin the fixed behaviors.
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { Context } from 'cordis'
|
|
import Storage, { BackendRegistry } from '../src/index.ts'
|
|
import type { StorageBackend } from '../src/index.ts'
|
|
|
|
const fakeBackend = (): StorageBackend => ({ close: async () => {} })
|
|
|
|
describe('BackendRegistry', () => {
|
|
it('registers, resolves, and disposes names', () => {
|
|
const registry = new BackendRegistry()
|
|
const backend = fakeBackend()
|
|
const dispose = registry.register('json', backend)
|
|
expect(registry.get('json')).toBe(backend)
|
|
expect(registry.names()).toEqual(['json'])
|
|
dispose()
|
|
expect(registry.names()).toEqual([])
|
|
expect(() => registry.get('json')).toThrowMatchingObject({ code: 'backend-not-found' })
|
|
})
|
|
|
|
it('rejects duplicate names', () => {
|
|
const registry = new BackendRegistry()
|
|
registry.register('json', fakeBackend())
|
|
expect(() => registry.register('json', fakeBackend())).toThrowMatchingObject({ code: 'duplicate-backend' })
|
|
})
|
|
})
|
|
|
|
describe('Storage service', () => {
|
|
it('mounts on the context and exposes registry plus form mounting', async () => {
|
|
const ctx = new Context()
|
|
await ctx.plugin(Storage)
|
|
expect(ctx.storage).toBeInstanceOf(Storage)
|
|
|
|
const facility = { marker: true }
|
|
const dispose = ctx.storage.mount('domain' as never, facility as never)
|
|
expect(ctx.storage.form('domain' as never)).toBe(facility)
|
|
expect(() => ctx.storage.mount('domain' as never, facility as never)).toThrowMatchingObject({
|
|
code: 'duplicate-mount',
|
|
})
|
|
dispose()
|
|
expect(() => ctx.storage.form('domain' as never)).toThrowMatchingObject({ code: 'form-not-mounted' })
|
|
})
|
|
})
|
|
|
|
expect.extend({
|
|
toThrowMatchingObject(received: () => unknown, expected: object) {
|
|
try {
|
|
received()
|
|
} catch (error) {
|
|
const pass = Object.entries(expected).every(
|
|
(entry) => (error as Record<string, unknown>)[entry[0]] === entry[1],
|
|
)
|
|
return { pass, message: () => `expected thrown error to match ${JSON.stringify(expected)}, got ${String(error)}` }
|
|
}
|
|
return { pass: false, message: () => 'expected function to throw' }
|
|
},
|
|
})
|
|
|
|
declare module 'vitest' {
|
|
interface Assertion<T> {
|
|
toThrowMatchingObject(expected: object): T
|
|
}
|
|
}
|