feat(cursor): proactively renews Cursor sessions in the sweep

Adds src/lib/tokenHealthCheckCursor.ts, sweep-side glue that calls
the renewal orchestrator and persists the result, wired into
tokenHealthCheck.ts's checkConnection() via a new Cursor-specific
branch placed ahead of the generic no-refresh-token fallthrough.
Carves out a non-terminal exception for a Cursor connection that
already landed at testStatus "expired" via the request-time 401
path, excluding permanently-dead account_deactivated connections.
Extends buildRefreshFailureUpdate() with an overrides param so
Cursor's failure path can use a distinct, non-terminal errorCode
instead of the generic refresh_failed/expired taxonomy.
This commit is contained in:
Will Gordon
2026-07-31 10:22:22 -04:00
committed by diegosouzapw
parent 6ff2eceb9e
commit 71f1804e99
5 changed files with 693 additions and 7 deletions

View File

@@ -28,6 +28,7 @@ import {
import { pickMaskedDisplayValue } from "@/shared/utils/maskEmail";
import { isAutomatedTestProcess } from "@/shared/utils/testProcess";
import { refreshGithubCopilotSubTokenIfNeeded } from "@/lib/tokenHealthCheckCopilot";
import { checkCursorConnectionIfNeeded } from "@/lib/tokenHealthCheckCursor";
const LOG_PREFIX = "[HealthCheck]";
const TRUE_ENV_VALUES = new Set(["1", "true", "yes", "on"]);
@@ -119,7 +120,16 @@ export function isInRefreshBackoff(conn: any, nowMs: number): boolean {
return Number.isFinite(untilMs) && untilMs > nowMs;
}
export function buildRefreshFailureUpdate(conn: any, now: string) {
export function buildRefreshFailureUpdate(
conn: any,
now: string,
overrides?: {
errorCode?: string;
lastError?: string;
lastErrorType?: string;
testStatus?: string;
}
) {
const wasExpired = conn.testStatus === "expired";
const retryCount = (conn.expiredRetryCount ?? 0) + (wasExpired ? 1 : 0);
@@ -145,6 +155,7 @@ export function buildRefreshFailureUpdate(conn: any, now: string) {
refreshCircuit: { streak, until: getRefreshBackoffUntil(streak, now), lastFailAt: now },
},
...(wasExpired ? { expiredRetryCount: retryCount, expiredRetryAt: now } : {}),
...(overrides || {}),
};
}
@@ -417,11 +428,24 @@ export async function checkConnection(conn) {
conn.testStatus === "expired" &&
conn.errorCode === "no_refresh_token" &&
isGitHubAccessTokenOnlyConnection(conn);
// Cursor has no refresh_token by design — an existing REQUEST-TIME path
// (resolveTerminalConnectionStatus() in src/sse/services/auth.ts) can land
// a Cursor connection at testStatus "expired" on a live 401 before the
// Cursor renewal branch below ever runs. Un-terminal it so the sweep can
// still attempt a renewal, UNLESS the account is genuinely dead
// (lastErrorType "account_deactivated" is documented as permanently dead
// and must not be retried — doing so would repeatedly nudge cursor-agent
// and re-scrape against a dead account).
const isRecoverableCursorExpired =
conn.testStatus === "expired" &&
String(conn.provider || "").toLowerCase() === "cursor" &&
conn.lastErrorType !== "account_deactivated";
const terminalStatuses = new Set(["credits_exhausted", "banned", "expired"]);
if (
typeof conn.testStatus === "string" &&
terminalStatuses.has(conn.testStatus.toLowerCase()) &&
!isRecoverableGithubCopilotNoRefresh
!isRecoverableGithubCopilotNoRefresh &&
!isRecoverableCursorExpired
) {
return;
}
@@ -453,6 +477,30 @@ export async function checkConnection(conn) {
return;
}
// Cursor's refreshToken is always null (no refresh_token by design), so
// falling into the generic !conn.refreshToken block below was always a
// silent no-op for Cursor. Explicit provider dispatch here is clearer than
// relying on that fallthrough.
if (String(conn.provider || "").toLowerCase() === "cursor") {
const tokenExpiresAt = getEffectiveTokenExpiryMs(conn);
const isAboutToExpire = tokenExpiresAt > 0 && tokenExpiresAt - Date.now() < TOKEN_EXPIRY_BUFFER;
if (tokenExpiresAt > 0 && !isAboutToExpire) return;
if (isInRefreshBackoff(conn, Date.now())) return;
const now = new Date().toISOString();
await checkCursorConnectionIfNeeded({
conn,
now,
buildRefreshFailureUpdate,
log,
logWarn,
logError,
getConnectionLogLabel,
logPrefix: LOG_PREFIX,
});
return;
}
if (!conn.refreshToken || typeof conn.refreshToken !== "string") {
if (isGitHubAccessTokenOnlyConnection(conn)) {
const now = new Date().toISOString();

View File

@@ -0,0 +1,63 @@
/**
* Cursor-specific sweep-glue for the proactive token health check. Cursor has
* no refresh_token by design — its ~24h import-token is renewed via a
* cursor-agent nudge + IDE/agent credential re-scrape (see
* src/lib/cursor/renewal.ts), not a standard OAuth refresh_token exchange.
*
* Sibling to tokenHealthCheckCopilot.ts (same injection-to-avoid-circular-
* import technique — tokenHealthCheck.ts-private helpers passed as params
* rather than imported, and updateProviderConnection imported directly from
* @/lib/localDb) but greenfield: type-checked normally, no @ts-nocheck.
*/
import { updateProviderConnection } from "@/lib/localDb";
import {
renewCursorConnection,
buildCursorRenewedUpdate,
runCursorRenewalExclusive,
} from "@/lib/cursor/renewal";
import type { buildRefreshFailureUpdate } from "@/lib/tokenHealthCheck";
export async function checkCursorConnectionIfNeeded(params: {
conn: any;
now: string;
buildRefreshFailureUpdate: typeof buildRefreshFailureUpdate;
log: (message: string, ...args: any[]) => void;
logWarn: (message: string, ...args: any[]) => void;
logError: (message: string, ...args: any[]) => void;
getConnectionLogLabel: (conn: { name?: string; email?: string; id?: string }) => string;
logPrefix: string;
}): Promise<void> {
const { conn, now, buildRefreshFailureUpdate, log, logWarn, getConnectionLogLabel, logPrefix } =
params;
await runCursorRenewalExclusive(conn.id, async () => {
const result = await renewCursorConnection({
accessToken: conn.accessToken,
machineId: conn.providerSpecificData?.machineId ?? null,
});
if (result.status === "renewed") {
await updateProviderConnection(conn.id, buildCursorRenewedUpdate(conn, result, now));
log(
`${logPrefix} ✓ Cursor session renewed for ${getConnectionLogLabel(conn)} (source: ${result.source})`
);
return;
}
const message =
result.status === "error"
? `Cursor session renewal failed: ${result.error}`
: "Cursor session unchanged — no newer token found on this host.";
await updateProviderConnection(
conn.id,
buildRefreshFailureUpdate(conn, now, {
errorCode: "cursor_session_stale",
lastErrorType: "cursor_session_stale",
lastError: message,
testStatus: "active",
})
);
logWarn(`${logPrefix} ✗ Cursor session stale for ${getConnectionLogLabel(conn)}: ${message}`);
});
}

View File

@@ -79,11 +79,48 @@ test("isInRefreshBackoff false when no circuit recorded", () => {
});
test("expired connections still track expiredRetryCount AND the circuit", () => {
const update = buildRefreshFailureUpdate(
{ testStatus: "expired", expiredRetryCount: 1 },
NOW
);
const update = buildRefreshFailureUpdate({ testStatus: "expired", expiredRetryCount: 1 }, NOW);
assert.equal(update.testStatus, "expired");
assert.equal(update.expiredRetryCount, 2);
assert.equal(update.providerSpecificData.refreshCircuit.streak, 1);
});
// Cursor renewal plan, Task 3 Step 1: buildRefreshFailureUpdate() gained an
// optional 3rd `overrides` param so Cursor's failure path (which has no
// refresh_token by design) can use a distinct errorCode ("cursor_session_stale"
// instead of "refresh_failed") and force testStatus:"active" even when the
// connection's prior testStatus was already "expired" — without touching any
// other provider's error taxonomy or default behavior.
test("buildRefreshFailureUpdate applies overrides on top of the defaults, leaving every existing caller (which passes no 3rd arg) byte-identical", () => {
const withOverrides = buildRefreshFailureUpdate(
{ testStatus: "expired", expiredRetryCount: 0 },
NOW,
{
errorCode: "cursor_session_stale",
lastErrorType: "cursor_session_stale",
lastError: "Cursor session unchanged — no newer token found on this host.",
testStatus: "active",
}
);
assert.equal(withOverrides.errorCode, "cursor_session_stale");
assert.equal(withOverrides.lastErrorType, "cursor_session_stale");
assert.equal(
withOverrides.lastError,
"Cursor session unchanged — no newer token found on this host."
);
assert.equal(
withOverrides.testStatus,
"active",
"the override must force non-terminal status even though wasExpired (prior testStatus) was true"
);
// wasExpired-derived bookkeeping (retry count, circuit streak) is untouched
// by the testStatus override — only the persisted field itself is replaced.
assert.equal(withOverrides.expiredRetryCount, 1);
assert.equal(withOverrides.providerSpecificData.refreshCircuit.streak, 1);
const withoutOverrides = buildRefreshFailureUpdate({ testStatus: "active" }, NOW);
assert.equal(withoutOverrides.errorCode, "refresh_failed");
assert.equal(withoutOverrides.lastErrorType, "token_refresh_failed");
assert.equal(withoutOverrides.lastError, "Health check: token refresh failed");
assert.equal(withoutOverrides.testStatus, "active");
});

View File

@@ -0,0 +1,537 @@
/**
* Task 3 — wiring the Cursor renewal orchestrator (src/lib/cursor/renewal.ts,
* Task 2) into the proactive token health-check sweep (src/lib/tokenHealthCheck.ts
* + src/lib/tokenHealthCheckCursor.ts).
*
* Real DB, real checkConnection()/checkCursorConnectionIfNeeded() — matching
* this file family's existing convention (see
* tests/unit/token-health-check.test.ts, tests/unit/token-health-no-refresh-token-expired-5326.test.ts):
* a real SQLite DB under a temp DATA_DIR, real createProviderConnection()/
* updateProviderConnection()/getProviderConnectionById() round-trips, no
* mocking of the DB layer.
*
* checkCursorConnectionIfNeeded() calls the REAL renewCursorConnection()
* (Task 2) with no deps override, so — exactly as in
* tests/unit/cursor-renewal.test.ts — its dependencies are driven via real
* HOME-relative fixture files and a real fake `cursor-agent` binary, never a
* mock. This ALSO means the ambient real cursor-agent install on some dev
* hosts (see tests/unit/cursor-agent-models.test.ts) must always be shadowed
* by a fake binary here too, so no test in this file ever risks invoking a
* real cursor-agent process.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
process.env.NODE_ENV = "test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-hc-cursor-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const tokenHealthCheck = await import("../../src/lib/tokenHealthCheck.ts");
async function resetStorage() {
core.resetDbInstance();
for (let attempt = 0; attempt < 10; attempt++) {
try {
if (fs.existsSync(TEST_DATA_DIR)) {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
break;
} catch (error: unknown) {
const code =
error && typeof error === "object" && "code" in error
? (error as { code?: unknown }).code
: null;
if ((code === "EBUSY" || code === "EPERM") && attempt < 9) {
await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
} else {
throw error;
}
}
}
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
function getId(connection: { id?: unknown }): string {
assert.equal(typeof connection.id, "string");
return connection.id as string;
}
async function freshConn(id: string) {
const conn = await providersDb.getProviderConnectionById(id);
assert.ok(conn, `expected connection ${id} to exist`);
return conn as Record<string, unknown>;
}
// ---- Real fake cursor-agent binary + IDE/agent fixture helpers, mirroring
// tests/unit/cursor-renewal.test.ts and tests/unit/cursor-token-extractor.test.ts ----
const FAKE_CURSOR_AGENT_SCRIPT = `#!/usr/bin/env node
const fs = require("fs");
const args = process.argv.slice(2);
if (process.env.FAKE_CURSOR_AGENT_LOG) {
fs.appendFileSync(process.env.FAKE_CURSOR_AGENT_LOG, JSON.stringify(args) + "\\n");
}
if (args[0] === "status") {
const mode = process.env.FAKE_CURSOR_AGENT_STATUS_MODE || "unauthenticated";
if (mode === "authenticated") {
process.stdout.write(JSON.stringify({ status: "authenticated", isAuthenticated: true }));
} else {
process.stdout.write(JSON.stringify({ status: "unauthenticated", isAuthenticated: false }));
}
}
`;
function writeFakeCursorAgentBinary(destPath: string): void {
fs.mkdirSync(path.dirname(destPath), { recursive: true });
fs.writeFileSync(destPath, FAKE_CURSOR_AGENT_SCRIPT, { mode: 0o755 });
fs.chmodSync(destPath, 0o755);
}
function readLoggedInvocations(logPath: string): string[][] {
if (!fs.existsSync(logPath)) return [];
return fs
.readFileSync(logPath, "utf-8")
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line));
}
interface CursorEnv {
tmpHome: string;
logPath: string;
writeIdeToken(accessToken: string, machineId?: string): Promise<void>;
writeAgentToken(accessToken: string): void;
cleanup(): void;
}
async function withCursorEnv<T>(fn: (env: CursorEnv) => Promise<T>): Promise<T> {
const originalHome = process.env.HOME;
const originalUserProfile = process.env.USERPROFILE;
const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform");
Object.defineProperty(process, "platform", { value: "darwin", configurable: true });
const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-hc-cursor-env-"));
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome;
const logPath = path.join(tmpHome, "log.jsonl");
process.env.FAKE_CURSOR_AGENT_LOG = logPath;
// Never authenticated by default — the point of these tests is the sweep
// wiring/DB-update shapes (Task 2 already covers the nudge itself), and
// this also guarantees the real ambient cursor-agent install some hosts
// have is never the one actually resolved (this fake one always shadows
// it, since it's the first fixed candidate resolveCursorAgentBinary checks).
process.env.FAKE_CURSOR_AGENT_STATUS_MODE = "unauthenticated";
writeFakeCursorAgentBinary(path.join(tmpHome, ".local", "bin", "cursor-agent"));
const env: CursorEnv = {
tmpHome,
logPath,
async writeIdeToken(accessToken, machineId) {
const { openDatabaseAsync } = await import("../../src/lib/db/adapters/driverFactory.ts");
const dbPath = path.join(
tmpHome,
"Library/Application Support/Cursor/User/globalStorage/state.vscdb"
);
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
const seed = await openDatabaseAsync(dbPath);
seed.exec("CREATE TABLE itemTable (key TEXT PRIMARY KEY, value TEXT)");
seed
.prepare("INSERT INTO itemTable (key, value) VALUES (?, ?)")
.run("cursorAuth/accessToken", accessToken);
if (machineId) {
seed
.prepare("INSERT INTO itemTable (key, value) VALUES (?, ?)")
.run("storage.serviceMachineId", machineId);
}
seed.close();
},
writeAgentToken(accessToken) {
const authDir = path.join(tmpHome, ".config", "cursor");
fs.mkdirSync(authDir, { recursive: true });
fs.writeFileSync(path.join(authDir, "auth.json"), JSON.stringify({ accessToken }));
},
cleanup() {
if (originalPlatformDescriptor) {
Object.defineProperty(process, "platform", originalPlatformDescriptor);
}
process.env.HOME = originalHome;
if (originalUserProfile !== undefined) process.env.USERPROFILE = originalUserProfile;
else delete process.env.USERPROFILE;
delete process.env.FAKE_CURSOR_AGENT_LOG;
delete process.env.FAKE_CURSOR_AGENT_STATUS_MODE;
fs.rmSync(tmpHome, { recursive: true, force: true });
},
};
try {
return await fn(env);
} finally {
env.cleanup();
}
}
const NEAR_EXPIRY_ISO = new Date(Date.now() + 60_000).toISOString(); // 1 min out (< 5 min buffer)
const PAST_EXPIRY_ISO = new Date(Date.now() - 60 * 60 * 1000).toISOString(); // 1h ago
const FAR_FUTURE_ISO = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
async function createCursorConnection(overrides: Record<string, unknown> = {}) {
const connection = await providersDb.createProviderConnection({
provider: "cursor",
authType: "oauth",
email: "cursor-healthcheck@example.com",
accessToken: "old-token",
refreshToken: null,
isActive: true,
testStatus: "active",
...overrides,
});
return getId(connection);
}
// ============================================================================
// Step 2: checkCursorConnectionIfNeeded DB-update shapes
// ============================================================================
test("checkConnection: Cursor renewed-via-IDE result persists accessToken, ~24h expiry, active status, cleared error fields", async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: NEAR_EXPIRY_ISO,
expiresAt: NEAR_EXPIRY_ISO,
providerSpecificData: { machineId: "old-machine" },
});
await env.writeIdeToken("new-ide-token", "new-machine");
const before = Date.now();
await tokenHealthCheck.checkConnection(await freshConn(id));
const after = Date.now();
const updated = await freshConn(id);
assert.equal(updated.accessToken, "new-ide-token");
assert.equal(updated.testStatus, "active");
assert.equal(updated.lastError ?? null, null);
assert.equal(updated.lastErrorAt ?? null, null);
assert.equal(updated.lastErrorType ?? null, null);
assert.equal(updated.errorCode ?? null, null);
assert.equal(updated.expiredRetryCount ?? null, null);
assert.equal(updated.expiredRetryAt ?? null, null);
assert.equal(updated.expiresAt, updated.tokenExpiresAt);
const expiresAtMs = new Date(updated.expiresAt as string).getTime();
assert.ok(
expiresAtMs >= before + 24 * 60 * 60 * 1000 - 5000 &&
expiresAtMs <= after + 24 * 60 * 60 * 1000 + 5000,
`expected expiresAt ~24h out, got ${updated.expiresAt}`
);
const psd = updated.providerSpecificData as Record<string, unknown>;
assert.equal(psd.machineId, "new-machine");
});
});
test('checkConnection: Cursor "unchanged" result marks cursor_session_stale, stays non-terminal (testStatus active, not expired)', async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: NEAR_EXPIRY_ISO,
expiresAt: NEAR_EXPIRY_ISO,
});
// No writeIdeToken/writeAgentToken -> both tokenExtractor functions
// gracefully report {found:false} -> renewCursorConnection() returns "unchanged".
void env;
await tokenHealthCheck.checkConnection(await freshConn(id));
const updated = await freshConn(id);
assert.equal(updated.errorCode, "cursor_session_stale");
assert.equal(updated.lastErrorType, "cursor_session_stale");
assert.match(updated.lastError as string, /Cursor session unchanged/);
assert.equal(
updated.testStatus,
"active",
"must NOT be terminal — future sweeps must keep retrying"
);
assert.ok(updated.lastHealthCheckAt);
const psd = updated.providerSpecificData as Record<string, unknown>;
assert.equal((psd.refreshCircuit as { streak?: number })?.streak, 1);
});
});
test(
'checkConnection: Cursor "error" result -> DB update shape',
{
skip:
"Same root-cause testability gap as tests/unit/cursor-renewal.test.ts's original " +
"case (d), one level up: checkCursorConnectionIfNeeded() (src/lib/tokenHealthCheckCursor.ts) " +
"calls the real renewCursorConnection() with NO deps override, so there is no way to force " +
'it to return {status:"error"} from here (tryIdeAuth()/tryAgentAuth() never throw for real — ' +
"verified in Task 1/2's tests — and this harness has no mock.module() support). " +
"renewCursorConnection()'s OWN error-mapping (sanitizeErrorMessage wiring) is already " +
"covered directly in tests/unit/cursor-renewal.test.ts's case (d), using its deps seam. " +
"The remaining untested surface is narrowly this file's message-building line " +
"(`Cursor session renewal failed: ${result.error}`) plus the cursor_session_stale/testStatus:" +
'"active" override — both of which ARE exercised by the "unchanged" test above via the ' +
"identical buildRefreshFailureUpdate call site (only the interpolated message text differs). " +
"Flagged to the team lead/reviewer: forwarding an optional deps param from " +
"checkCursorConnectionIfNeeded() through to its internal renewCursorConnection() call " +
"(mirroring the seam already added to renewCursorConnection() itself for Task 2) would close " +
"this specific gap with a small, additive change.",
},
async () => {}
);
// ============================================================================
// Step 1: buildRefreshFailureUpdate's overrides param — DB-shape-adjacent proof
// (the pure-function unit test lives in tests/unit/token-health-check-circuit-breaker.test.ts;
// this asserts the SAME override plumbing end-to-end through the real DB write above)
// ============================================================================
test("checkConnection: the cursor_session_stale override forces testStatus:active even for a connection whose PRIOR testStatus was expired", async () => {
await resetStorage();
await withCursorEnv(async () => {
// A Cursor connection that landed at "expired" via the pre-existing
// request-time path (src/sse/services/auth.ts::resolveTerminalConnectionStatus)
// — Task 3 Step 3's carve-out lets this reach the branch; Step 1/2's override
// must then force it back to non-terminal, not leave/re-derive "expired".
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: PAST_EXPIRY_ISO,
expiresAt: PAST_EXPIRY_ISO,
testStatus: "expired",
});
await tokenHealthCheck.checkConnection(await freshConn(id));
const updated = await freshConn(id);
assert.equal(
updated.testStatus,
"active",
'buildRefreshFailureUpdate\'s default wasExpired-derived testStatus:"expired" must be overridden'
);
assert.equal(updated.errorCode, "cursor_session_stale");
});
});
// ============================================================================
// Step 3: terminal-status carve-out (isRecoverableCursorExpired)
// ============================================================================
test("checkConnection: Cursor + expired + no lastErrorType is NOT permanently skipped (reaches the Cursor branch)", async () => {
await resetStorage();
await withCursorEnv(async () => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: PAST_EXPIRY_ISO,
expiresAt: PAST_EXPIRY_ISO,
testStatus: "expired",
// no lastErrorType at all
});
await tokenHealthCheck.checkConnection(await freshConn(id));
const updated = await freshConn(id);
assert.ok(
updated.lastHealthCheckAt,
"must have reached the Cursor branch (which always writes lastHealthCheckAt)"
);
assert.equal(updated.testStatus, "active");
});
});
test("checkConnection: Cursor + expired + lastErrorType:account_deactivated STAYS permanently skipped", async () => {
await resetStorage();
await withCursorEnv(async () => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: PAST_EXPIRY_ISO,
expiresAt: PAST_EXPIRY_ISO,
testStatus: "expired",
lastErrorType: "account_deactivated",
lastHealthCheckAt: null,
});
const before = await freshConn(id);
await tokenHealthCheck.checkConnection(before);
const after = await freshConn(id);
assert.deepEqual(after, before, "a permanently-dead account must not be touched at all");
});
});
test("checkConnection: a banned Cursor connection stays skipped regardless of lastErrorType", async () => {
await resetStorage();
await withCursorEnv(async () => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: PAST_EXPIRY_ISO,
expiresAt: PAST_EXPIRY_ISO,
testStatus: "banned",
lastErrorType: "some_other_reason",
});
const before = await freshConn(id);
await tokenHealthCheck.checkConnection(before);
const after = await freshConn(id);
assert.deepEqual(after, before);
});
});
test("checkConnection: a credits_exhausted Cursor connection stays skipped regardless of lastErrorType", async () => {
await resetStorage();
await withCursorEnv(async () => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: PAST_EXPIRY_ISO,
expiresAt: PAST_EXPIRY_ISO,
testStatus: "credits_exhausted",
});
const before = await freshConn(id);
await tokenHealthCheck.checkConnection(before);
const after = await freshConn(id);
assert.deepEqual(after, before);
});
});
// ============================================================================
// Step 4: due/backoff dispatch
// ============================================================================
test("checkConnection: Cursor connection NOT near expiry is left completely untouched (no renewal attempt)", async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: FAR_FUTURE_ISO,
expiresAt: FAR_FUTURE_ISO,
});
const before = await freshConn(id);
await tokenHealthCheck.checkConnection(before);
const after = await freshConn(id);
assert.deepEqual(after, before);
assert.equal(
readLoggedInvocations(env.logPath).length,
0,
"must never spawn cursor-agent when not due"
);
});
});
test("checkConnection: Cursor connection near expiry and NOT in backoff triggers a renewal attempt", async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: NEAR_EXPIRY_ISO,
expiresAt: NEAR_EXPIRY_ISO,
});
await tokenHealthCheck.checkConnection(await freshConn(id));
assert.ok(
readLoggedInvocations(env.logPath).length >= 1,
"expected a cursor-agent availability check"
);
const updated = await freshConn(id);
assert.ok(updated.lastHealthCheckAt);
});
});
test("checkConnection: Cursor connection near expiry but currently in backoff is skipped", async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
tokenExpiresAt: NEAR_EXPIRY_ISO,
expiresAt: NEAR_EXPIRY_ISO,
providerSpecificData: {
refreshCircuit: { streak: 1, until: new Date(Date.now() + 10 * 60 * 1000).toISOString() },
},
});
const before = await freshConn(id);
await tokenHealthCheck.checkConnection(before);
const after = await freshConn(id);
assert.deepEqual(after, before);
assert.equal(readLoggedInvocations(env.logPath).length, 0, "must not spawn while in backoff");
});
});
test("checkConnection: a Cursor connection with no known expiry at all is treated as due", async () => {
await resetStorage();
await withCursorEnv(async (env) => {
const id = await createCursorConnection({
accessToken: "old-token",
// no tokenExpiresAt/expiresAt at all
});
await tokenHealthCheck.checkConnection(await freshConn(id));
assert.ok(
readLoggedInvocations(env.logPath).length >= 1,
"an unknown expiry must be treated as due, not permanently skipped"
);
});
});
// ============================================================================
// Step 5: full sweep-level regression — non-Cursor behavior must be unaffected
// ============================================================================
test("checkConnection: a refresh-capable non-Cursor provider missing its refresh token is still marked expired/no_refresh_token (#5326 unaffected)", async () => {
await resetStorage();
const connection = await providersDb.createProviderConnection({
provider: "antigravity",
authType: "oauth",
name: "Antigravity No-Refresh Account (Cursor-plan regression)",
email: "antigravity-cursor-regression@example.com",
accessToken: "access-token-only",
refreshToken: null,
testStatus: "active",
isActive: true,
});
await tokenHealthCheck.checkConnection(connection);
const updated = await providersDb.getProviderConnectionById(getId(connection));
assert.equal(updated?.testStatus, "expired");
assert.equal(updated?.errorCode, "no_refresh_token");
});
test("checkConnection: a banned non-Cursor connection is still skipped (terminal-status guard unaffected by the Cursor carve-out)", async () => {
await resetStorage();
const connection = await providersDb.createProviderConnection({
provider: "openai",
authType: "oauth",
email: "openai-banned-regression@example.com",
accessToken: "access-token",
refreshToken: "refresh-token",
testStatus: "banned",
isActive: true,
});
const before = await providersDb.getProviderConnectionById(getId(connection));
await tokenHealthCheck.checkConnection(before);
const after = await providersDb.getProviderConnectionById(getId(connection));
assert.deepEqual(after, before);
});

View File

@@ -34,7 +34,8 @@
"open-sse/mcp-server/scopeEnforcement.ts",
"open-sse/translator/registry.ts",
"open-sse/handlers/responseSanitizer.ts",
"open-sse/handlers/responseTranslator.ts"
"open-sse/handlers/responseTranslator.ts",
"src/lib/tokenHealthCheckCursor.ts"
],
"exclude": ["node_modules", ".next", "app.__qa_backup", "vscode-extension"]
}