1import type { SDKModel, SDKUser } from "@cursor/sdk";
2import { describe, expect, it } from "@effect/vitest";
3import * as Effect from "effect/Effect";
4import * as Exit from "effect/Exit";
5
6import * as CursorSdkCatalog from "./CursorSdkCatalog.ts";
7
8const user = {
9 apiKeyName: "test-key",
10 userEmail: "cursor@example.com",
11 createdAt: "2026-01-01T00:00:00.000Z",
12} satisfies SDKUser;
13
14const model = {
15 id: "claude-opus-4-8",
16 displayName: "Opus 4.8",
17 parameters: [],
18} satisfies SDKModel;
19
20const catalogError = () =>
21 new CursorSdkCatalog.CursorSdkCatalogError({
22 authenticationFailure: false,
23 cause: new Error("catalog unavailable"),
24 });
25
26describe("CursorSdkCatalog", () => {
27 it.effect("keeps the user probe fresh while caching models by API key", () =>
28 Effect.gen(function* () {
29 let userCalls = 0;
30 let modelCalls = 0;
31 const catalog = yield* CursorSdkCatalog.makeCursorSdkCatalog({
32 readUser: () => Effect.sync(() => ((userCalls += 1), user)),
33 readModels: () => Effect.sync(() => ((modelCalls += 1), [model])),
34 });
35
36 yield* catalog.read("first-key");
37 yield* catalog.read("first-key");
38 yield* catalog.read("second-key");
39
40 expect(userCalls).toBe(3);
41 expect(modelCalls).toBe(2);
42 }),
43 );
44
45 it.effect("does not cache failed or empty model discovery", () =>
46 Effect.gen(function* () {
47 let modelCalls = 0;
48 const catalog = yield* CursorSdkCatalog.makeCursorSdkCatalog({
49 readUser: () => Effect.succeed(user),
50 readModels: () =>
51 Effect.suspend(() => {
52 modelCalls += 1;
53 if (modelCalls === 1) return Effect.fail(catalogError());
54 if (modelCalls === 2) return Effect.succeed([]);
55 return Effect.succeed([model]);
56 }),
57 });
58
59 expect(Exit.isFailure(yield* Effect.exit(catalog.read("test-key")))).toBe(true);
60 expect((yield* catalog.read("test-key")).models).toEqual([]);
61 expect((yield* catalog.read("test-key")).models).toEqual([model]);
62 expect((yield* catalog.read("test-key")).models).toEqual([model]);
63 expect(modelCalls).toBe(3);
64 }),
65 );
66});