| ... | ... | @@ -121,6 +121,46 @@ function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | describe("UsageService", () => { |
| 124 | it.live("omits Cursor account usage when no file login is saved", () => |
| 125 | Effect.gen(function* () { |
| 126 | const { settings, home } = yield* setup; |
| 127 | for (const platform of ["linux", "win32", "darwin"] as const) { |
| 128 | const service = yield* UsageService.make.pipe( |
| 129 | Effect.provide( |
| 130 | serviceLayers({ |
| 131 | prefix: `usage-service-cursor-no-login-${platform}`, |
| 132 | home, |
| 133 | settings, |
| 134 | platform, |
| 135 | environment: { AGENT_CLI_CREDENTIAL_STORE: "file" }, |
| 136 | }), |
| 137 | ), |
| 138 | ); |
| 139 | const summary = yield* service.readSummary(WINDOW); |
| 140 | assert.isFalse(summary.sources.some((source) => source.fingerprint.provider === "cursor")); |
| 141 | } |
| 142 | }).pipe(Effect.scoped), |
| 143 | ); |
| 144 | |
| 145 | it.live("keeps Cursor credential errors visible when a saved login cannot be read", () => |
| 146 | Effect.gen(function* () { |
| 147 | const { settings, home } = yield* setup; |
| 148 | const authPath = NodePath.join(home, "config", "cursor", "auth.json"); |
| 149 | yield* Effect.promise(async () => { |
| 150 | await NodeFSP.mkdir(NodePath.dirname(authPath), { recursive: true }); |
| 151 | await NodeFSP.writeFile(authPath, "invalid json"); |
| 152 | }); |
| 153 | const service = yield* UsageService.make.pipe( |
| 154 | Effect.provide( |
| 155 | serviceLayers({ prefix: "usage-service-cursor-invalid-login", home, settings }), |
| 156 | ), |
| 157 | ); |
| 158 | const summary = yield* service.readSummary(WINDOW); |
| 159 | const cursor = summary.sources.find((source) => source.fingerprint.provider === "cursor"); |
| 160 | assert.strictEqual(cursor?.message, "Cursor credentials could not be read."); |
| 161 | }).pipe(Effect.scoped), |
| 162 | ); |
| 163 | |
| 124 | 164 | it.live("does not read the macOS Cursor Keychain before account usage is enabled", () => |
| 125 | 165 | Effect.gen(function* () { |
| 126 | 166 | const { settings, home } = yield* setup; |
| ... | ... | @@ -233,10 +273,7 @@ describe("UsageService", () => { |
| 233 | 273 | const summary = yield* service.readSummary(WINDOW); |
| 234 | 274 | assert.strictEqual(summary.buckets[0]?.provider, "opencode"); |
| 235 | 275 | assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor")); |
| 236 | | assert.strictEqual( |
| 237 | | summary.sources.find((source) => source.fingerprint.provider === "cursor")?.status, |
| 238 | | "missing", |
| 239 | | ); |
| 276 | assert.isFalse(summary.sources.some((source) => source.fingerprint.provider === "cursor")); |
| 240 | 277 | assert.strictEqual( |
| 241 | 278 | summary.buckets[0]?.sourcePath, |
| 242 | 279 | yield* Effect.promise(() => NodeFSP.realpath(root)), |
| ... | ... | @@ -247,10 +284,6 @@ describe("UsageService", () => { |
| 247 | 284 | ?.distinctSessions, |
| 248 | 285 | 1, |
| 249 | 286 | ); |
| 250 | | assert.include( |
| 251 | | summary.sources.find((source) => source.fingerprint.provider === "cursor")?.message ?? "", |
| 252 | | "Cursor account history needs a Cursor CLI login", |
| 253 | | ); |
| 254 | 287 | }).pipe(Effect.scoped), |
| 255 | 288 | ); |
| 256 | 289 | |