|
|
@@ -2,19 +2,12 @@ import { describe, expect, it, vi } from 'vitest'
|
|
|
import { Context, Service } from 'cordis'
|
|
|
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 {
|
|
|
@@ -28,12 +21,6 @@ class InvariantProbeService extends Service {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class WatchedInvariantProbeService extends Service {
|
|
|
- constructor(ctx: Context) {
|
|
|
- super(ctx, 'watchedInvariantProbe')
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
interface RuntimeRegistration extends PromiseLike<() => void> {
|
|
|
(): void | Promise<void>
|
|
|
}
|
|
|
@@ -299,277 +286,3 @@ describe('InvariantService lifecycle', () => {
|
|
|
expect(() => service.register('@deepseek-ai/dsh-session', () => {})).toThrow(/inactive/i)
|
|
|
})
|
|
|
})
|
|
|
-
|
|
|
-describe('package-owned invariant helpers', () => {
|
|
|
- interface InvariantDisposer {
|
|
|
- (): void | Promise<void>
|
|
|
- }
|
|
|
-
|
|
|
- async function registerInstaller(
|
|
|
- ctx: Context,
|
|
|
- packageName: string,
|
|
|
- installer: InvariantInstaller,
|
|
|
- ): Promise<InvariantDisposer> {
|
|
|
- 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('multiplexes same-runtime plugin checks through one root listener pair and disposes each owner', async () => {
|
|
|
- const { ctx } = await setup()
|
|
|
- const firstValidation = vi.fn(() => undefined)
|
|
|
- const secondValidation = vi.fn(() => undefined)
|
|
|
- const first = await registerInstaller(ctx, '@deepseek-ai/dsh-shared-plugin-first', (child, fail) => {
|
|
|
- observePluginInvariant(child, fail, { name: 'shared-plugin-probe', validate: firstValidation })
|
|
|
- })
|
|
|
- const second = await registerInstaller(ctx, '@deepseek-ai/dsh-shared-plugin-second', (child, fail) => {
|
|
|
- observePluginInvariant(child, fail, { name: 'shared-plugin-probe', validate: secondValidation })
|
|
|
- })
|
|
|
- const rootEffectLabels = ctx.fiber.getEffects().map(effect => effect.label)
|
|
|
- expect(rootEffectLabels.filter(label => label === 'ctx.on("internal/plugin")')).toHaveLength(1)
|
|
|
- expect(rootEffectLabels.filter(label => label === 'ctx.on("internal/status")')).toHaveLength(1)
|
|
|
-
|
|
|
- const plugin = effectPlugin({ name: 'shared-plugin-probe' })
|
|
|
- const firstFiber = await ctx.plugin(plugin)
|
|
|
- expect(firstValidation).toHaveBeenCalledOnce()
|
|
|
- expect(secondValidation).toHaveBeenCalledOnce()
|
|
|
-
|
|
|
- await first()
|
|
|
- await firstFiber.dispose()
|
|
|
- const secondFiber = await ctx.plugin(plugin)
|
|
|
- expect(firstValidation).toHaveBeenCalledOnce()
|
|
|
- expect(secondValidation).toHaveBeenCalledTimes(2)
|
|
|
-
|
|
|
- await second()
|
|
|
- await secondFiber.dispose()
|
|
|
- await ctx.plugin(plugin)
|
|
|
- expect(firstValidation).toHaveBeenCalledOnce()
|
|
|
- expect(secondValidation).toHaveBeenCalledTimes(2)
|
|
|
- })
|
|
|
-
|
|
|
- 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('multiplexes same-name service checks through one root listener and disposes each owner', async () => {
|
|
|
- const { ctx } = await setup()
|
|
|
- const firstValidation = vi.fn(() => undefined)
|
|
|
- const secondValidation = vi.fn(() => undefined)
|
|
|
- const first = await registerInstaller(ctx, '@deepseek-ai/dsh-shared-service-first', (child, fail) => {
|
|
|
- observeServiceInvariant(child, fail, 'watchedInvariantProbe', firstValidation)
|
|
|
- })
|
|
|
- const second = await registerInstaller(ctx, '@deepseek-ai/dsh-shared-service-second', (child, fail) => {
|
|
|
- observeServiceInvariant(child, fail, 'watchedInvariantProbe', secondValidation)
|
|
|
- })
|
|
|
- const rootEffectLabels = ctx.fiber.getEffects().map(effect => effect.label)
|
|
|
- expect(rootEffectLabels.filter(label => label === 'ctx.on("internal/service")')).toHaveLength(1)
|
|
|
-
|
|
|
- const firstFiber = await ctx.plugin(WatchedInvariantProbeService)
|
|
|
- expect(firstValidation).toHaveBeenCalledOnce()
|
|
|
- expect(secondValidation).toHaveBeenCalledOnce()
|
|
|
-
|
|
|
- await first()
|
|
|
- const firstCallsAfterDisposal = firstValidation.mock.calls.length
|
|
|
- const secondCallsBeforeRemount = secondValidation.mock.calls.length
|
|
|
- await firstFiber.dispose()
|
|
|
- const secondFiber = await ctx.plugin(WatchedInvariantProbeService)
|
|
|
- expect(firstValidation).toHaveBeenCalledTimes(firstCallsAfterDisposal)
|
|
|
- expect(secondValidation.mock.calls.length).toBeGreaterThan(secondCallsBeforeRemount)
|
|
|
-
|
|
|
- await second()
|
|
|
- const firstCallsAfterBothDisposals = firstValidation.mock.calls.length
|
|
|
- const secondCallsAfterBothDisposals = secondValidation.mock.calls.length
|
|
|
- await secondFiber.dispose()
|
|
|
- await ctx.plugin(WatchedInvariantProbeService)
|
|
|
- expect(firstValidation).toHaveBeenCalledTimes(firstCallsAfterBothDisposals)
|
|
|
- expect(secondValidation).toHaveBeenCalledTimes(secondCallsAfterBothDisposals)
|
|
|
- })
|
|
|
-
|
|
|
- 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')
|
|
|
- })
|
|
|
- await 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)
|
|
|
- })
|
|
|
-})
|