From 9c82b3d4ca4e4573514cf3837d712e833c0b5b11 Mon Sep 17 00:00:00 2001 From: Kfir Amar Date: Sat, 14 Mar 2026 22:18:41 +0200 Subject: [PATCH] fix(auth): clear stale provider error metadata Clear errorCode, lastErrorType, and lastErrorSource when an account recovers so provider state returns to a fully clean active status. Add a focused regression test for recovered-account cleanup. --- src/sse/services/auth.ts | 10 +++- tests/unit/auth-clear-account-error.test.mjs | 63 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/unit/auth-clear-account-error.test.mjs diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index a106fb6a55..185eb1fd30 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -572,9 +572,12 @@ export async function markAccountUnavailable( export async function clearAccountError(connectionId: string, currentConnection: any) { // Only update if currently has error status const hasError = - currentConnection.testStatus === "unavailable" || + (currentConnection.testStatus && currentConnection.testStatus !== "active") || currentConnection.lastError || - currentConnection.rateLimitedUntil; + currentConnection.rateLimitedUntil || + currentConnection.errorCode || + currentConnection.lastErrorType || + currentConnection.lastErrorSource; if (!hasError) return; // Skip if already clean @@ -582,6 +585,9 @@ export async function clearAccountError(connectionId: string, currentConnection: testStatus: "active", lastError: null, lastErrorAt: null, + lastErrorType: null, + lastErrorSource: null, + errorCode: null, rateLimitedUntil: null, backoffLevel: 0, }); diff --git a/tests/unit/auth-clear-account-error.test.mjs b/tests/unit/auth-clear-account-error.test.mjs new file mode 100644 index 0000000000..6744d37bd5 --- /dev/null +++ b/tests/unit/auth-clear-account-error.test.mjs @@ -0,0 +1,63 @@ +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"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-auth-clear-")); +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 auth = await import("../../src/sse/services/auth.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("clearAccountError clears stale provider error metadata after recovery", async () => { + await resetStorage(); + core.getDbInstance().exec('ALTER TABLE provider_connections ADD COLUMN "group" TEXT'); + + const created = await providersDb.createProviderConnection({ + provider: "codex", + authType: "oauth", + email: "recover@example.com", + accessToken: "access", + refreshToken: "refresh", + testStatus: "error", + lastError: "Health check: token refresh failed", + lastErrorType: "token_refresh_failed", + lastErrorSource: "oauth", + errorCode: "refresh_failed", + rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(), + backoffLevel: 2, + }); + + await auth.clearAccountError(created.id, { + ...created, + testStatus: "error", + lastError: "Health check: token refresh failed", + lastErrorType: "token_refresh_failed", + lastErrorSource: "oauth", + errorCode: "refresh_failed", + rateLimitedUntil: new Date(Date.now() + 60_000).toISOString(), + backoffLevel: 2, + }); + + const updated = await providersDb.getProviderConnectionById(created.id); + assert.equal(updated.testStatus, "active"); + assert.equal(updated.lastError, undefined); + assert.equal(updated.lastErrorType, undefined); + assert.equal(updated.lastErrorSource, undefined); + assert.equal(updated.errorCode, undefined); + assert.equal(updated.rateLimitedUntil, undefined); + assert.equal(updated.backoffLevel, 0); +});