| ... | @@ -121,6 +121,46 @@ function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens | ... | @@ -121,6 +121,46 @@ function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens |
| 121 | } | 121 | } |
| 122 | | 122 | |
| 123 | describe("UsageService", () => { | 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 | it.live("does not read the macOS Cursor Keychain before account usage is enabled", () => | 164 | it.live("does not read the macOS Cursor Keychain before account usage is enabled", () => |
| 125 | Effect.gen(function* () { | 165 | Effect.gen(function* () { |
| 126 | const { settings, home } = yield* setup; | 166 | const { settings, home } = yield* setup; |
| ... | @@ -233,10 +273,7 @@ describe("UsageService", () => { | ... | @@ -233,10 +273,7 @@ describe("UsageService", () => { |
| 233 | const summary = yield* service.readSummary(WINDOW); | 273 | const summary = yield* service.readSummary(WINDOW); |
| 234 | assert.strictEqual(summary.buckets[0]?.provider, "opencode"); | 274 | assert.strictEqual(summary.buckets[0]?.provider, "opencode"); |
| 235 | assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor")); | 275 | assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor")); |
| 236 | assert.strictEqual( | 276 | assert.isFalse(summary.sources.some((source) => source.fingerprint.provider === "cursor")); |
| 237 | summary.sources.find((source) => source.fingerprint.provider === "cursor")?.status, | | |
| 238 | "missing", | | |
| 239 | ); | | |
| 240 | assert.strictEqual( | 277 | assert.strictEqual( |
| 241 | summary.buckets[0]?.sourcePath, | 278 | summary.buckets[0]?.sourcePath, |
| 242 | yield* Effect.promise(() => NodeFSP.realpath(root)), | 279 | yield* Effect.promise(() => NodeFSP.realpath(root)), |
| ... | @@ -247,10 +284,6 @@ describe("UsageService", () => { | ... | @@ -247,10 +284,6 @@ describe("UsageService", () => { |
| 247 | ?.distinctSessions, | 284 | ?.distinctSessions, |
| 248 | 1, | 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 | }).pipe(Effect.scoped), | 287 | }).pipe(Effect.scoped), |
| 255 | ); | 288 | ); |
| 256 | | 289 | |