feat(invariants): implement package runtime checks
This commit is contained in:
@@ -1,10 +1,20 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { Context, Service } from 'cordis'
|
||||
import InvariantService, { InvariantError, type Config } from '@deepseek-ai/dsh-invariants'
|
||||
import InvariantService, {
|
||||
InvariantError,
|
||||
assertInvariant,
|
||||
observePluginInvariant,
|
||||
observeServiceInvariant,
|
||||
serviceShapeViolation,
|
||||
type Config,
|
||||
type InvariantInstaller,
|
||||
type PluginInvariantContract,
|
||||
} from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
invariantProbe: InvariantProbeService
|
||||
watchedInvariantProbe: WatchedInvariantProbeService
|
||||
}
|
||||
|
||||
interface Events {
|
||||
@@ -18,6 +28,12 @@ class InvariantProbeService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
class WatchedInvariantProbeService extends Service {
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'watchedInvariantProbe')
|
||||
}
|
||||
}
|
||||
|
||||
interface RuntimeRegistration extends PromiseLike<() => void> {
|
||||
(): void | Promise<void>
|
||||
}
|
||||
@@ -264,3 +280,207 @@ describe('InvariantService lifecycle', () => {
|
||||
expect(() => service.register('@deepseek-ai/dsh-session', () => {})).toThrow(/inactive/i)
|
||||
})
|
||||
})
|
||||
|
||||
describe('package-owned invariant helpers', () => {
|
||||
async function registerInstaller(
|
||||
ctx: Context,
|
||||
packageName: string,
|
||||
installer: InvariantInstaller,
|
||||
): Promise<() => void> {
|
||||
const registration = runtimeRegistration(ctx.invariants.register(packageName, installer))
|
||||
const dispose = await Promise.resolve(registration)
|
||||
return dispose
|
||||
}
|
||||
|
||||
function effectPlugin(options: {
|
||||
name?: string
|
||||
inject?: string[]
|
||||
effect?: string
|
||||
service?: string
|
||||
} = {}) {
|
||||
return {
|
||||
name: options.name ?? 'effect-probe',
|
||||
inject: options.inject ?? [],
|
||||
apply(ctx: Context) {
|
||||
if (options.service !== undefined) ctx.provide(options.service, {})
|
||||
if (options.effect !== undefined) {
|
||||
ctx.effect(() => {
|
||||
ctx.effect(() => () => {}, `${options.effect}.child`)
|
||||
return () => {}
|
||||
}, options.effect)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function expectPluginViolation(
|
||||
contract: PluginInvariantContract,
|
||||
plugin: ReturnType<typeof effectPlugin>,
|
||||
message: RegExp,
|
||||
): Promise<void> {
|
||||
const { ctx } = await setup()
|
||||
await registerInstaller(ctx, `@deepseek-ai/${contract.name}`, (child, fail) => {
|
||||
observePluginInvariant(child, fail, contract)
|
||||
})
|
||||
await expect(Promise.resolve(ctx.plugin(plugin))).rejects.toThrow(message)
|
||||
}
|
||||
|
||||
it('checks existing and later plugin fibers, including nested effects and alternatives', async () => {
|
||||
const { ctx } = await setup()
|
||||
await ctx.plugin(InvariantProbeService)
|
||||
const plugin = effectPlugin({
|
||||
inject: ['invariantProbe'],
|
||||
effect: 'probe.effect',
|
||||
service: 'pluginProbe',
|
||||
})
|
||||
await ctx.plugin(plugin)
|
||||
const validated = vi.fn(() => undefined)
|
||||
await registerInstaller(ctx, '@deepseek-ai/dsh-existing-probe', (child, fail) => {
|
||||
observePluginInvariant(child, fail, {
|
||||
plugin,
|
||||
name: 'effect-probe',
|
||||
inject: ['invariantProbe'],
|
||||
effects: [['missing.effect', 'probe.effect.child']],
|
||||
services: ['pluginProbe'],
|
||||
validate: validated,
|
||||
})
|
||||
})
|
||||
expect(validated).toHaveBeenCalledOnce()
|
||||
|
||||
const later = effectPlugin({ name: 'later-probe', effect: 'later.effect' })
|
||||
await registerInstaller(ctx, '@deepseek-ai/dsh-later-probe', (child, fail) => {
|
||||
observePluginInvariant(child, fail, {
|
||||
plugin: later,
|
||||
name: 'later-probe',
|
||||
effects: ['later.effect'],
|
||||
})
|
||||
})
|
||||
await ctx.plugin(later)
|
||||
})
|
||||
|
||||
it('matches package plugins by Cordis name without importing their callback', async () => {
|
||||
const { ctx } = await setup()
|
||||
const plugin = {
|
||||
name: 'name-only-probe',
|
||||
apply(pluginCtx: Context) {
|
||||
pluginCtx.effect(() => () => {}, 'name-only.effect')
|
||||
pluginCtx.inject([], () => {})
|
||||
},
|
||||
}
|
||||
await registerInstaller(ctx, '@deepseek-ai/dsh-name-only-probe', (child, fail) => {
|
||||
observePluginInvariant(child, fail, {
|
||||
name: 'name-only-probe',
|
||||
effects: ['name-only.effect'],
|
||||
})
|
||||
})
|
||||
await ctx.plugin(plugin)
|
||||
})
|
||||
|
||||
it('rejects a contract that does not identify a plugin', async () => {
|
||||
const { ctx } = await setup()
|
||||
const registration = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-invalid-plugin', (child, fail) => {
|
||||
observePluginInvariant(child, fail, {
|
||||
plugin: {} as never,
|
||||
name: 'invalid-plugin',
|
||||
})
|
||||
}))
|
||||
await expect(Promise.resolve(registration)).rejects.toThrow(/does not identify a Cordis plugin/)
|
||||
})
|
||||
|
||||
it('rejects wrong plugin names, missing injections, effects, services, and custom checks', async () => {
|
||||
const wrongName = effectPlugin({ name: 'actual-name', effect: 'probe.effect' })
|
||||
await expectPluginViolation({
|
||||
plugin: wrongName,
|
||||
name: 'expected-name',
|
||||
}, wrongName, /plugin name must be "expected-name"/)
|
||||
|
||||
const missingInjection = effectPlugin({ effect: 'probe.effect' })
|
||||
await expectPluginViolation({
|
||||
plugin: missingInjection,
|
||||
name: 'effect-probe',
|
||||
inject: ['missingService'],
|
||||
}, missingInjection, /must inject "missingService"/)
|
||||
|
||||
const missingEffect = effectPlugin()
|
||||
await expectPluginViolation({
|
||||
plugin: missingEffect,
|
||||
name: 'effect-probe',
|
||||
effects: [['first.effect', 'second.effect']],
|
||||
}, missingEffect, /must own effect "first.effect" or "second.effect"/)
|
||||
|
||||
const missingService = effectPlugin({ effect: 'probe.effect' })
|
||||
await expectPluginViolation({
|
||||
plugin: missingService,
|
||||
name: 'effect-probe',
|
||||
services: ['missingService'],
|
||||
}, missingService, /must provide service "missingService"/)
|
||||
|
||||
const invalidCustom = effectPlugin({ effect: 'probe.effect' })
|
||||
await expectPluginViolation({
|
||||
plugin: invalidCustom,
|
||||
name: 'effect-probe',
|
||||
validate: () => 'custom plugin contract failed',
|
||||
}, invalidCustom, /custom plugin contract failed/)
|
||||
})
|
||||
|
||||
it('checks existing and future service implementations while ignoring unrelated changes', async () => {
|
||||
const existing = await setup()
|
||||
await existing.ctx.plugin(WatchedInvariantProbeService)
|
||||
await registerInstaller(existing.ctx, '@deepseek-ai/dsh-existing-service', (child, fail) => {
|
||||
observeServiceInvariant(child, fail, 'watchedInvariantProbe', value => (
|
||||
value instanceof WatchedInvariantProbeService ? undefined : 'wrong watched service'
|
||||
))
|
||||
})
|
||||
|
||||
const future = await setup()
|
||||
await registerInstaller(future.ctx, '@deepseek-ai/dsh-future-service', (child, fail) => {
|
||||
observeServiceInvariant(child, fail, 'watchedInvariantProbe', value => (
|
||||
value instanceof WatchedInvariantProbeService ? undefined : 'wrong watched service'
|
||||
))
|
||||
})
|
||||
await future.ctx.plugin(InvariantProbeService)
|
||||
await future.ctx.plugin(WatchedInvariantProbeService)
|
||||
|
||||
const invalid = await setup()
|
||||
await registerInstaller(invalid.ctx, '@deepseek-ai/dsh-invalid-service', (child, fail) => {
|
||||
observeServiceInvariant(child, fail, 'watchedInvariantProbe', () => 'wrong watched service')
|
||||
})
|
||||
await expect(Promise.resolve(invalid.ctx.plugin(WatchedInvariantProbeService)))
|
||||
.rejects.toThrow(/wrong watched service/)
|
||||
})
|
||||
|
||||
it('reports synchronous package assertions through the bound failure reporter', async () => {
|
||||
const { ctx } = await setup()
|
||||
const valid = await registerInstaller(ctx, '@deepseek-ai/dsh-valid-assertion', (_child, fail) => {
|
||||
assertInvariant(fail, true, 'must stay true')
|
||||
})
|
||||
valid()
|
||||
|
||||
const invalid = runtimeRegistration(ctx.invariants.register('@deepseek-ai/dsh-invalid-assertion', (_child, fail) => {
|
||||
assertInvariant(fail, false, 'must stay true')
|
||||
}))
|
||||
await expect(Promise.resolve(invalid)).rejects.toThrow(/must stay true/)
|
||||
})
|
||||
|
||||
it('accepts structural service implementations and test doubles', () => {
|
||||
expect(serviceShapeViolation({ kind: 'probe', run() {} }, {
|
||||
methods: ['run'],
|
||||
stringProperties: ['kind'],
|
||||
})).toBeUndefined()
|
||||
expect(serviceShapeViolation(Object.assign(() => {}, { run() {} }), {
|
||||
methods: ['run'],
|
||||
})).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ value: null, message: 'service implementation must be an object' },
|
||||
{ value: 42, message: 'service implementation must be an object' },
|
||||
{ value: {}, message: 'service implementation must expose method "run"' },
|
||||
{ value: { run() {}, kind: '' }, message: 'service implementation must expose non-empty string "kind"' },
|
||||
])('rejects invalid structural service implementations: $message', ({ value, message }) => {
|
||||
expect(serviceShapeViolation(value, {
|
||||
methods: ['run'],
|
||||
stringProperties: ['kind'],
|
||||
})).toBe(message)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user