registry.test.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { describe, expect } from "bun:test"
  2. import { Cause, Effect, Exit, Schema, Scope } from "effect"
  3. import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
  4. import { SystemContext } from "@opencode-ai/core/system-context"
  5. import { SystemContextRegistry } from "@opencode-ai/core/system-context/registry"
  6. import { testEffect } from "../lib/effect"
  7. const entry = (key: string, text: string, sourceKey = key) => ({
  8. key: SystemContext.Key.make(key),
  9. load: Effect.succeed(
  10. SystemContext.make({
  11. key: SystemContext.Key.make(sourceKey),
  12. codec: Schema.toCodecJson(Schema.String),
  13. load: Effect.succeed(text),
  14. baseline: String,
  15. update: (_previous, current) => current,
  16. }),
  17. ),
  18. })
  19. const it = testEffect(AppNodeBuilder.build(SystemContextRegistry.node))
  20. describe("SystemContextRegistry", () => {
  21. it.effect("loads empty system context when there are no entries", () =>
  22. Effect.gen(function* () {
  23. const registry = yield* SystemContextRegistry.Service
  24. expect(yield* SystemContext.initialize(yield* registry.load())).toEqual({ baseline: "", snapshot: {} })
  25. }),
  26. )
  27. it.effect("loads scoped entries in stable key order", () =>
  28. Effect.gen(function* () {
  29. const registry = yield* SystemContextRegistry.Service
  30. yield* registry.register(entry("test/second", "second"))
  31. yield* registry.register(entry("test/first", "first"))
  32. expect((yield* SystemContext.initialize(yield* registry.load())).baseline).toBe("first\n\nsecond")
  33. }),
  34. )
  35. it.effect("re-evaluates entry producers on each load", () =>
  36. Effect.gen(function* () {
  37. const registry = yield* SystemContextRegistry.Service
  38. let loads = 0
  39. yield* registry.register({
  40. key: SystemContext.Key.make("test/dynamic"),
  41. load: Effect.sync(() => {
  42. loads++
  43. return SystemContext.empty
  44. }),
  45. })
  46. yield* registry.load()
  47. yield* registry.load()
  48. expect(loads).toBe(2)
  49. }),
  50. )
  51. it.effect("propagates entry producer failures", () =>
  52. Effect.gen(function* () {
  53. const registry = yield* SystemContextRegistry.Service
  54. const failure = new Error("entry failed")
  55. yield* registry.register({ key: SystemContext.Key.make("test/failure"), load: Effect.die(failure) })
  56. const exit = yield* registry.load().pipe(Effect.exit)
  57. expect(Exit.isFailure(exit)).toBe(true)
  58. if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBe(failure)
  59. }),
  60. )
  61. it.effect("rejects duplicate source keys from separate entries", () =>
  62. Effect.gen(function* () {
  63. const registry = yield* SystemContextRegistry.Service
  64. yield* registry.register(entry("test/first", "first", "test/duplicate"))
  65. yield* registry.register(entry("test/second", "second", "test/duplicate"))
  66. const exit = yield* registry.load().pipe(Effect.exit)
  67. expect(Exit.isFailure(exit)).toBe(true)
  68. if (Exit.isFailure(exit)) {
  69. expect(Cause.squash(exit.cause)).toBeInstanceOf(SystemContext.DuplicateKeyError)
  70. expect(Cause.squash(exit.cause)).toMatchObject({ key: SystemContext.Key.make("test/duplicate") })
  71. }
  72. }),
  73. )
  74. it.effect("rejects duplicate entry keys", () =>
  75. Effect.gen(function* () {
  76. const registry = yield* SystemContextRegistry.Service
  77. yield* registry.register(entry("test/duplicate", "first"))
  78. const exit = yield* registry.register(entry("test/duplicate", "second", "test/other")).pipe(Effect.exit)
  79. expect(Exit.isFailure(exit)).toBe(true)
  80. if (Exit.isFailure(exit)) expect(Cause.pretty(exit.cause)).toContain("Duplicate system context entry key")
  81. }),
  82. )
  83. it.effect("removes an entry when its owning scope closes", () =>
  84. Effect.gen(function* () {
  85. const registry = yield* SystemContextRegistry.Service
  86. const scope = yield* Scope.make()
  87. yield* registry.register(entry("test/scoped", "scoped")).pipe(Scope.provide(scope))
  88. expect((yield* SystemContext.initialize(yield* registry.load())).baseline).toBe("scoped")
  89. yield* Scope.close(scope, Exit.void)
  90. expect(yield* SystemContext.initialize(yield* registry.load())).toEqual({ baseline: "", snapshot: {} })
  91. }),
  92. )
  93. })