Files
deepseek-harness/packages/workspace/workspace/tests/invariant.spec.ts
2026-07-27 12:38:11 +08:00

62 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import InvariantService from '@deepseek-ai/dsh-invariants'
import type { DomainChanged } from '@deepseek-ai/dsh-storage-domain'
import * as WorkspaceInvariant from '../src/invariant.ts'
import { WorkspaceId } from '../src/index.ts'
/** Boot the invariant service plus the companion over a stubbed registry knowing exactly `ids`. */
async function setup(ids: string[]): Promise<Context> {
const ctx = new Context()
await ctx.plugin(InvariantService)
ctx.provide('workspace', {
get: (id: WorkspaceId) => (ids.includes(id) ? { id } : undefined),
})
await ctx.plugin(WorkspaceInvariant)
return ctx
}
type ChangeLocation = Partial<Pick<DomainChanged, 'domain' | 'table' | 'key'>>
const put = (overrides?: ChangeLocation): DomainChanged => ({
domain: 'workspace',
table: 'workspaces',
key: 'w1',
operation: 'put',
value: {},
...overrides,
})
const deleted = (): DomainChanged => ({
domain: 'workspace',
table: 'workspaces',
key: 'w1',
operation: 'deleted',
})
describe('workspace cache/table invariant', () => {
it('accepts a put whose record has a cached entity and ignores foreign events', async () => {
const ctx = await setup(['w1'])
expect(() => { ctx.emit('domain/changed', put()) }).not.toThrow()
// Other domains and other tables are out of scope, whatever their shape.
expect(() => { ctx.emit('domain/changed', put({ domain: 'other', key: 'missing' })) }).not.toThrow()
expect(() => { ctx.emit('domain/changed', put({ table: 'other', key: 'missing' })) }).not.toThrow()
})
it('fails deletion while the registry still publishes the entity', async () => {
const ctx = await setup(['w1'])
expect(() => { ctx.emit('domain/changed', deleted()) })
.toThrow(/cache still publishes/)
})
it('allows deletion after the registry removed the cache entry for rollback or explicit deletion', async () => {
const ctx = await setup([])
expect(() => { ctx.emit('domain/changed', deleted()) }).not.toThrow()
})
it('fails a put whose record the registry cache does not hold', async () => {
const ctx = await setup([])
expect(() => { ctx.emit('domain/changed', put()) }).toThrow(/diverged/)
})
})