1import { assert, describe, it } from "@effect/vitest";
2import * as Deferred from "effect/Deferred";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Fiber from "effect/Fiber";
6import * as TestClock from "effect/testing/TestClock";
7
8import * as CursorKeychain from "./CursorKeychain.ts";
9
10describe("CursorKeychain", () => {
11 it.effect("shares concurrent reads and rechecks after the cache expires", () =>
12 Effect.gen(function* () {
13 let reads = 0;
14 const keychain = CursorKeychain.make(
15 Effect.sync(() => {
16 reads++;
17 return `token-${reads}`;
18 }),
19 );
20 assert.deepStrictEqual(
21 yield* Effect.all([keychain.accessToken, keychain.accessToken], {
22 concurrency: "unbounded",
23 }),
24 ["token-1", "token-1"],
25 );
26 assert.strictEqual(yield* keychain.accessToken, "token-1");
27 assert.strictEqual(reads, 1);
28 yield* TestClock.adjust(Duration.minutes(5));
29 assert.strictEqual(yield* keychain.accessToken, "token-2");
30 }),
31 );
32
33 it.effect("gives up on an unanswered prompt and reuses it on the next read", () =>
34 Effect.gen(function* () {
35 let reads = 0;
36 const allow = yield* Deferred.make<string>();
37 const keychain = CursorKeychain.make(
38 Effect.suspend(() => {
39 reads++;
40 return Deferred.await(allow);
41 }),
42 Duration.millis(1),
43 );
44 const timedOut = yield* keychain.accessToken.pipe(Effect.forkChild);
45 yield* TestClock.adjust(Duration.millis(1));
46 const error = yield* Fiber.join(timedOut).pipe(Effect.flip);
47 assert.isTrue(CursorKeychain.isCursorKeychainTimeoutError(error));
48 const retry = yield* keychain.accessToken.pipe(Effect.forkChild);
49 yield* Deferred.succeed(allow, "token");
50 assert.strictEqual(yield* Fiber.join(retry), "token");
51 assert.strictEqual(reads, 1);
52 }),
53 );
54});