runtime.test.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { afterEach, describe, expect, test } from "bun:test"
  2. import { Effect } from "effect"
  3. import { runtime, runPromiseInstance } from "../../src/effect/runtime"
  4. import { Auth } from "../../src/auth/effect"
  5. import { Instances } from "../../src/effect/instances"
  6. import { Instance } from "../../src/project/instance"
  7. import { ProviderAuth } from "../../src/provider/auth"
  8. import { Vcs } from "../../src/project/vcs"
  9. import { Question } from "../../src/question"
  10. import { tmpdir } from "../fixture/fixture"
  11. /**
  12. * Integration tests for the Effect runtime and LayerMap-based instance system.
  13. *
  14. * Each instance service layer has `.pipe(Layer.fresh)` at its definition site
  15. * so it is always rebuilt per directory, while shared dependencies are provided
  16. * outside the fresh boundary and remain memoizable.
  17. *
  18. * These tests verify the invariants using object identity (===) on the real
  19. * production services — not mock services or return-value checks.
  20. */
  21. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  22. const grabInstance = (service: any) => runPromiseInstance(service.use(Effect.succeed))
  23. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  24. const grabGlobal = (service: any) => runtime.runPromise(service.use(Effect.succeed))
  25. describe("effect/runtime", () => {
  26. afterEach(async () => {
  27. await Instance.disposeAll()
  28. })
  29. test("global services are shared across directories", async () => {
  30. await using one = await tmpdir({ git: true })
  31. await using two = await tmpdir({ git: true })
  32. // Auth is a global service — it should be the exact same object
  33. // regardless of which directory we're in.
  34. const authOne = await Instance.provide({
  35. directory: one.path,
  36. fn: () => grabGlobal(Auth.Service),
  37. })
  38. const authTwo = await Instance.provide({
  39. directory: two.path,
  40. fn: () => grabGlobal(Auth.Service),
  41. })
  42. expect(authOne).toBe(authTwo)
  43. })
  44. test("instance services with global deps share the global (ProviderAuth → Auth)", async () => {
  45. await using one = await tmpdir({ git: true })
  46. await using two = await tmpdir({ git: true })
  47. // ProviderAuth depends on Auth via defaultLayer.
  48. // The instance service itself should be different per directory,
  49. // but the underlying Auth should be shared.
  50. const paOne = await Instance.provide({
  51. directory: one.path,
  52. fn: () => grabInstance(ProviderAuth.Service),
  53. })
  54. const paTwo = await Instance.provide({
  55. directory: two.path,
  56. fn: () => grabInstance(ProviderAuth.Service),
  57. })
  58. // Different directories → different ProviderAuth instances.
  59. expect(paOne).not.toBe(paTwo)
  60. // But the global Auth is the same object in both.
  61. const authOne = await Instance.provide({
  62. directory: one.path,
  63. fn: () => grabGlobal(Auth.Service),
  64. })
  65. const authTwo = await Instance.provide({
  66. directory: two.path,
  67. fn: () => grabGlobal(Auth.Service),
  68. })
  69. expect(authOne).toBe(authTwo)
  70. })
  71. test("instance services are shared within the same directory", async () => {
  72. await using tmp = await tmpdir({ git: true })
  73. await Instance.provide({
  74. directory: tmp.path,
  75. fn: async () => {
  76. expect(await grabInstance(Vcs.Service)).toBe(await grabInstance(Vcs.Service))
  77. expect(await grabInstance(Question.Service)).toBe(await grabInstance(Question.Service))
  78. },
  79. })
  80. })
  81. test("different directories get different service instances", async () => {
  82. await using one = await tmpdir({ git: true })
  83. await using two = await tmpdir({ git: true })
  84. const vcsOne = await Instance.provide({
  85. directory: one.path,
  86. fn: () => grabInstance(Vcs.Service),
  87. })
  88. const vcsTwo = await Instance.provide({
  89. directory: two.path,
  90. fn: () => grabInstance(Vcs.Service),
  91. })
  92. expect(vcsOne).not.toBe(vcsTwo)
  93. })
  94. test("disposal rebuilds services with a new instance", async () => {
  95. await using tmp = await tmpdir({ git: true })
  96. await Instance.provide({
  97. directory: tmp.path,
  98. fn: async () => {
  99. const before = await grabInstance(Question.Service)
  100. await runtime.runPromise(Instances.use((map) => map.invalidate(Instance.directory)))
  101. const after = await grabInstance(Question.Service)
  102. expect(after).not.toBe(before)
  103. },
  104. })
  105. })
  106. })