diff --git a/src/app/api/providers/[id]/test/oauthTestConfig.ts b/src/app/api/providers/[id]/test/oauthTestConfig.ts index 84ffac324d..2ba98472bf 100644 --- a/src/app/api/providers/[id]/test/oauthTestConfig.ts +++ b/src/app/api/providers/[id]/test/oauthTestConfig.ts @@ -51,6 +51,18 @@ export const OAUTH_TEST_CONFIG = { authPrefix: "Bearer ", refreshable: true, }, + // `agy` is a separate connection id that shares the Antigravity backend and the same + // Google OAuth token lifecycle (tokenRefresh.ts routes it to refreshGoogleToken), but + // it was missing here — so "Test Connection" fell through to "Provider test not + // supported", recorded testStatus="error", and painted the home topology node red on a + // perfectly good account. Probe the same userinfo endpoint as antigravity. + agy: { + url: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", + method: "GET", + authHeader: "Authorization", + authPrefix: "Bearer ", + refreshable: true, + }, xai: { url: "https://api.x.ai/v1/chat/completions", method: "POST", @@ -112,6 +124,16 @@ export const OAUTH_TEST_CONFIG = { checkExpiry: true, refreshable: true, }, + "devin-cli": { + // Same gap as grok-cli #7610: absent from this table, so "Test Connection" + // always fell through to "Provider test not supported" and left a working + // connection showing a red ERR badge. There is no HTTP probe to hit — the + // executor drives the local `devin` binary over ACP stdio and the binary + // owns its own credentials (`devin auth login`), so there is no refresh + // token to rotate either. Validate on token presence/expiry; real + // connectivity is proven by every chat/completions request. + checkExpiry: true, + }, "grok-cli": { // #7610: was entirely absent from OAUTH_TEST_CONFIG, so "Test Connection" // always fell through to the generic "Provider test not supported" branch diff --git a/tests/unit/oauth-test-config-8408.test.ts b/tests/unit/oauth-test-config-8408.test.ts new file mode 100644 index 0000000000..7bec8a8576 --- /dev/null +++ b/tests/unit/oauth-test-config-8408.test.ts @@ -0,0 +1,46 @@ +// #8408: Guard against missing OAUTH_TEST_CONFIG entries for OAuth providers +import test from "node:test"; +import assert from "node:assert/strict"; +import { OAUTH_PROVIDERS } from "../../src/shared/constants/providers/oauth.ts"; +import { OAUTH_TEST_CONFIG } from "../../src/app/api/providers/[id]/test/oauthTestConfig.ts"; + +// NOT a design decision — this is a grandfathered backlog. These six ids are simply the +// providers that still lack an OAUTH_TEST_CONFIG entry today, captured so this guard can be +// enforced from now on without a big-bang change. Each one is a candidate for the same +// treatment devin-cli and agy get here; removing an id from this list is the fix, not a +// regression. Do not add new ids to it — a provider added without a test config should fail +// this test at the time it is added, which is the entire point. +const GRANDFATHERED_WITHOUT_TEST_CONFIG = new Set([ + "qoder", + "zed", + "zed-hosted", + "trae", + "windsurf", + "xai-oauth", +]); + +test("#8408: devin-cli and agy are present in OAUTH_TEST_CONFIG", () => { + assert.ok( + (OAUTH_TEST_CONFIG as Record)["devin-cli"], + "devin-cli must have an entry in OAUTH_TEST_CONFIG" + ); + assert.ok( + (OAUTH_TEST_CONFIG as Record)["agy"], + "agy must have an entry in OAUTH_TEST_CONFIG" + ); +}); + +test("#8408: every OAuth provider ID has an OAUTH_TEST_CONFIG entry (or is grandfathered)", () => { + const providerIds = Object.keys(OAUTH_PROVIDERS); + const testConfigKeys = new Set(Object.keys(OAUTH_TEST_CONFIG)); + + for (const providerId of providerIds) { + const isCovered = + testConfigKeys.has(providerId) || GRANDFATHERED_WITHOUT_TEST_CONFIG.has(providerId); + assert.ok( + isCovered, + `OAuth provider '${providerId}' must have an entry in OAUTH_TEST_CONFIG. ` + + 'Without one, Test Connection persists testStatus="error" on a healthy account (#8408).' + ); + } +});