| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | import { assert, describe, it } from "@effect/vitest"; | 1 | import { assert, describe, it } from "@effect/vitest"; |
| | 2 | import * as Cause from "effect/Cause"; |
| 2 | import * as Effect from "effect/Effect"; | 3 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; | 4 | import * as Layer from "effect/Layer"; |
| 4 | import { vi } from "vite-plus/test"; | 5 | import { beforeEach, vi } from "vite-plus/test"; |
| 5 | | 6 | |
| 6 | const { createClerkBridgeMock, storageAdapter, storageMock } = vi.hoisted(() => ({ | 7 | const { createClerkBridgeMock, storageAdapter, storageMock } = vi.hoisted(() => ({ |
| 7 | createClerkBridgeMock: vi.fn(), | 8 | createClerkBridgeMock: vi.fn(), |
| ... | @@ -24,7 +25,23 @@ vi.mock("@clerk/electron/storage", () => ({ | ... | @@ -24,7 +25,23 @@ vi.mock("@clerk/electron/storage", () => ({ |
| 24 | import * as DesktopClerk from "./DesktopClerk.ts"; | 25 | import * as DesktopClerk from "./DesktopClerk.ts"; |
| 25 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; | 26 | import * as DesktopEnvironment from "./DesktopEnvironment.ts"; |
| 26 | | 27 | |
| | 28 | const makeDesktopClerkLayer = (isDevelopment = true) => { |
| | 29 | const environment = DesktopEnvironment.DesktopEnvironment.of({ |
| | 30 | stateDir: "/tmp/t3-state", |
| | 31 | isDevelopment, |
| | 32 | } as unknown as DesktopEnvironment.DesktopEnvironment["Service"]); |
| | 33 | |
| | 34 | return DesktopClerk.layer.pipe( |
| | 35 | Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)), |
| | 36 | ); |
| | 37 | }; |
| | 38 | |
| 27 | describe("DesktopClerk", () => { | 39 | describe("DesktopClerk", () => { |
| | 40 | beforeEach(() => { |
| | 41 | createClerkBridgeMock.mockReset(); |
| | 42 | storageMock.mockReset(); |
| | 43 | }); |
| | 44 | |
| 28 | it("derives the Clerk Frontend API hostname used by the desktop CSP", () => { | 45 | it("derives the Clerk Frontend API hostname used by the desktop CSP", () => { |
| 29 | const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`; | 46 | const publishableKey = `pk_test_${btoa("clerk.t3.codes$")}`; |
| 30 | | 47 | |
| ... | @@ -40,19 +57,9 @@ describe("DesktopClerk", () => { | ... | @@ -40,19 +57,9 @@ describe("DesktopClerk", () => { |
| 40 | const cleanup = vi.fn(); | 57 | const cleanup = vi.fn(); |
| 41 | storageMock.mockReturnValue(storageAdapter); | 58 | storageMock.mockReturnValue(storageAdapter); |
| 42 | createClerkBridgeMock.mockReturnValue({ cleanup }); | 59 | createClerkBridgeMock.mockReturnValue({ cleanup }); |
| 43 | const environment = DesktopEnvironment.DesktopEnvironment.of({ | | |
| 44 | stateDir: "/tmp/t3-state", | | |
| 45 | isDevelopment: true, | | |
| 46 | } as unknown as DesktopEnvironment.DesktopEnvironment["Service"]); | | |
| 47 | | 60 | |
| 48 | return Effect.gen(function* () { | 61 | return Effect.gen(function* () { |
| 49 | yield* Effect.scoped( | 62 | yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())); |
| 50 | Layer.build( | | |
| 51 | DesktopClerk.layer.pipe( | | |
| 52 | Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)), | | |
| 53 | ), | | |
| 54 | ), | | |
| 55 | ); | | |
| 56 | | 63 | |
| 57 | assert.deepEqual(createClerkBridgeMock.mock.calls, [ | 64 | assert.deepEqual(createClerkBridgeMock.mock.calls, [ |
| 58 | [ | 65 | [ |
| ... | @@ -69,6 +76,54 @@ describe("DesktopClerk", () => { | ... | @@ -69,6 +76,54 @@ describe("DesktopClerk", () => { |
| 69 | }); | 76 | }); |
| 70 | }); | 77 | }); |
| 71 | | 78 | |
| | 79 | it.effect("preserves bridge initialization failures", () => { |
| | 80 | const cause = new Error("bridge initialization failed"); |
| | 81 | storageMock.mockReturnValue(storageAdapter); |
| | 82 | createClerkBridgeMock.mockImplementationOnce(() => { |
| | 83 | throw cause; |
| | 84 | }); |
| | 85 | |
| | 86 | return Effect.gen(function* () { |
| | 87 | const error = yield* Effect.scoped(Layer.build(makeDesktopClerkLayer())).pipe(Effect.flip); |
| | 88 | |
| | 89 | assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeInitializationError); |
| | 90 | assert.equal(error.stateDir, "/tmp/t3-state"); |
| | 91 | assert.equal(error.isDevelopment, true); |
| | 92 | assert.strictEqual(error.cause, cause); |
| | 93 | assert.equal( |
| | 94 | error.message, |
| | 95 | 'Failed to initialize the desktop Clerk bridge for state directory "/tmp/t3-state" (development: true).', |
| | 96 | ); |
| | 97 | }); |
| | 98 | }); |
| | 99 | |
| | 100 | it.effect("preserves bridge cleanup failures", () => { |
| | 101 | const cause = new Error("bridge cleanup failed"); |
| | 102 | storageMock.mockReturnValue(storageAdapter); |
| | 103 | createClerkBridgeMock.mockReturnValue({ |
| | 104 | cleanup: () => { |
| | 105 | throw cause; |
| | 106 | }, |
| | 107 | }); |
| | 108 | |
| | 109 | return Effect.gen(function* () { |
| | 110 | const exit = yield* Effect.exit(Effect.scoped(Layer.build(makeDesktopClerkLayer(false)))); |
| | 111 | |
| | 112 | assert.equal(exit._tag, "Failure"); |
| | 113 | if (exit._tag === "Failure") { |
| | 114 | const error = Cause.squash(exit.cause); |
| | 115 | assert.instanceOf(error, DesktopClerk.DesktopClerkBridgeCleanupError); |
| | 116 | assert.equal(error.stateDir, "/tmp/t3-state"); |
| | 117 | assert.equal(error.isDevelopment, false); |
| | 118 | assert.strictEqual(error.cause, cause); |
| | 119 | assert.equal( |
| | 120 | error.message, |
| | 121 | 'Failed to clean up the desktop Clerk bridge for state directory "/tmp/t3-state" (development: false).', |
| | 122 | ); |
| | 123 | } |
| | 124 | }); |
| | 125 | }); |
| | 126 | |
| 72 | it.each([ | 127 | it.each([ |
| 73 | { isDevelopment: true, scheme: "t3code-dev" }, | 128 | { isDevelopment: true, scheme: "t3code-dev" }, |
| 74 | { isDevelopment: false, scheme: "t3code" }, | 129 | { isDevelopment: false, scheme: "t3code" }, |