Skip to content
Source
Sign inSign up

credentialStore.test.ts

142 lines5.3 KB

1import type { StoredSdkCredentials } from "@cursor/sdk";
2import * as NodeServices from "@effect/platform-node/NodeServices";
3import { assert, it } from "@effect/vitest";
4import { ProviderInstanceId } from "@t3tools/contracts";
5import * as Effect from "effect/Effect";
6import * as FileSystem from "effect/FileSystem";
7import * as Path from "effect/Path";
8​
9import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
10import * as TestProviderHost from "@t3tools/provider-testing/TestProviderHost";
11import * as Layer from "effect/Layer";
12import { makeCursorCredentialStore } from "./credentialStore.ts";
13​
14const layerHost = Layer.provideMerge(TestProviderHost.layer(), NodeServices.layer);
15const legacyFileText = `{
16 "version": 1,
17 "backendUrl": "https://api.cursor.com",
18 "apiKey": "test-only-legacy-key",
19 "createdAtMs": 100
20}`;
21​
22it.effect.each([
23 {
24 name: "imports a legacy sign-in",
25 legacy: legacyFileText,
26 stored: undefined,
27 expected: "test-only-legacy-key",
28 },
29 {
30 name: "keeps a newer stored sign-in",
31 legacy: legacyFileText,
32 stored: legacyFileText.replace("test-only-legacy-key", "test-only-current-key"),
33 expected: "test-only-current-key",
34 },
35 {
36 name: "replaces a damaged stored sign-in",
37 legacy: legacyFileText,
38 stored: "damaged",
39 expected: "test-only-legacy-key",
40 },
41 {
42 name: "discards a damaged legacy file",
43 legacy: "damaged",
44 stored: undefined,
45 expected: undefined,
46 },
47])("$name and deletes the legacy file", ({ legacy, stored, expected }) =>
48 Effect.gen(function* () {
49 const fileSystem = yield* FileSystem.FileSystem;
50 const path = yield* Path.Path;
51 const legacyFile = path.join(yield* fileSystem.makeTempDirectoryScoped(), "cursor.json");
52 yield* fileSystem.writeFileString(legacyFile, legacy);
53 const host = yield* ProviderHost.ProviderHost;
54 const instanceId = ProviderInstanceId.make("personal");
55 if (stored !== undefined) {
56 yield* (yield* host.credentials("cursor", instanceId)).set(new TextEncoder().encode(stored));
57 }
58 const migrated = yield* makeCursorCredentialStore(instanceId, legacyFile);
59 assert.strictEqual((yield* Effect.promise(() => migrated.store.load()))?.apiKey, expected);
60 assert.isFalse(yield* fileSystem.exists(legacyFile));
61 }).pipe(Effect.scoped, Effect.provide(layerHost)),
62);
63​
64it.effect(
65 "restores SDK credentials in a new controller and keeps another account when signing out",
66 () =>
67 Effect.gen(function* () {
68 const host = yield* ProviderHost.ProviderHost;
69 const makeStore = (id: string) => makeCursorCredentialStore(ProviderInstanceId.make(id));
70 const personal = yield* makeStore("personal");
71 const work = yield* makeStore("work");
72 const credentials = {
73 version: 1 as const,
74 backendUrl: "https://api.cursor.com",
75 apiKey: "test-only-key",
76 createdAtMs: 100,
77 apiKeyExpiresAtMs: 1000,
78 email: "test@example.com",
79 };
80 yield* Effect.promise(() => personal.store.save(credentials));
81 yield* Effect.promise(() =>
82 work.store.save({ ...credentials, apiKey: "test-only-work-key" }),
83 );
84 const restored = yield* makeStore("personal");
85 assert.deepEqual(yield* Effect.promise(() => restored.store.load()), credentials);
86 yield* (yield* host.credentials("cursor", "personal")).set(
87 new TextEncoder().encode("damaged credential"),
88 );
89 assert.isUndefined(yield* Effect.promise(() => restored.store.load()));
90 yield* Effect.promise(() => restored.store.save(credentials));
91 yield* Effect.promise(() => restored.store.clear());
92 assert.isUndefined(yield* Effect.promise(() => personal.store.load()));
93 assert.strictEqual(
94 (yield* Effect.promise(() => work.store.load()))?.apiKey,
95 "test-only-work-key",
96 );
97 }).pipe(Effect.provide(layerHost)),
98);
99​
100it.effect.each([
101 {
102 name: "an undefined email",
103 optionalFields: { email: undefined, apiKeyExpiresAtMs: 1000 },
104 expectedFields: { apiKeyExpiresAtMs: 1000 },
105 },
106 {
107 name: "an undefined expiry",
108 optionalFields: { email: "test@example.com", apiKeyExpiresAtMs: undefined },
109 expectedFields: { email: "test@example.com" },
110 },
111 {
112 name: "both optional fields undefined",
113 optionalFields: { email: undefined, apiKeyExpiresAtMs: undefined },
114 expectedFields: {},
115 },
116 {
117 name: "both optional fields omitted",
118 optionalFields: {},
119 expectedFields: {},
120 },
121])("restores SDK credentials with $name in a new store", ({ optionalFields, expectedFields }) =>
122 Effect.gen(function* () {
123 const instanceId = ProviderInstanceId.make("personal");
124 const { store } = yield* makeCursorCredentialStore(instanceId);
125 const requiredFields: StoredSdkCredentials = {
126 version: 1,
127 backendUrl: "https://api.cursor.com",
128 apiKey: "test-only-key",
129 createdAtMs: 100,
130 };
131 const credentials = { ...requiredFields };
132 // Match the SDK's runtime values without relying on its stricter declarations.
133 Object.assign(credentials, optionalFields);
134 yield* Effect.promise(() => store.save(credentials));
135​
136 const restored = yield* makeCursorCredentialStore(instanceId);
137 assert.deepEqual(yield* Effect.promise(() => restored.store.load()), {
138 ...requiredFields,
139 ...expectedFields,
140 });
141 }).pipe(Effect.provide(layerHost)),
142);