registry.test.ts 4.0 KB

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