invariant.spec.ts 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. /**
  2. * Webserver invariant companion: the boot-graph consistency audit — every
  3. * fetch-arrival graph row must resolve a clientPath, checked on fiber
  4. * lifecycle events against the assembly-published 'webPlugins' context key.
  5. */
  6. import { Context } from 'cordis'
  7. import { describe, expect, it } from 'vitest'
  8. import InvariantService from '@deepseek-ai/dsh-invariants'
  9. import * as WebserverInvariant from '../src/invariant.ts'
  10. interface RegistryStub {
  11. graph(): { entries: { id: string; url: string }[] }
  12. clientPath(id: string): string | undefined
  13. }
  14. async function setup(registry?: RegistryStub): Promise<Context> {
  15. const ctx = new Context()
  16. await ctx.plugin(InvariantService, { enabled: true })
  17. await ctx.plugin(WebserverInvariant).await()
  18. if (registry !== undefined) ctx.reflect.provide('webPlugins', registry)
  19. return ctx
  20. }
  21. /** Fire the audit trigger directly (same technique as the scope invariant
  22. * spec): a synchronous emit propagates the fail() throw to the caller. */
  23. function trigger(ctx: Context): void {
  24. ;(ctx.emit as (event: string, ...args: unknown[]) => void)('internal/plugin', ctx.fiber)
  25. }
  26. describe('webserver manifest invariant', () => {
  27. it('stays silent without a registry (carrier-only deployment) and with a consistent table', async () => {
  28. const bare = await setup()
  29. expect(() => { trigger(bare) }).not.toThrow() // no 'webPlugins' key published
  30. const consistent = await setup({
  31. graph: () => ({ entries: [{ id: 'p1', url: '/plugins/p1/client.js?rev=abc' }] }),
  32. clientPath: id => id === 'p1' ? '/tmp/p1/lib/client.js' : undefined,
  33. })
  34. expect(() => { trigger(consistent) }).not.toThrow()
  35. })
  36. it('throws on a graph row whose bundle path no longer resolves', async () => {
  37. const ctx = await setup({
  38. graph: () => ({ entries: [{ id: 'ghost', url: '/plugins/ghost/client.js?rev=abc' }] }),
  39. clientPath: () => undefined,
  40. })
  41. expect(() => { trigger(ctx) })
  42. .toThrow(/graph row "ghost".*resolves no client bundle path/)
  43. })
  44. })