test(oauth): prove refresh_token preserved on real gemini-cli/antigravity dispatch (#3850) (#3869)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-06-14 21:45:36 -03:00
committed by GitHub
parent 9613025219
commit e7014de65f
2 changed files with 60 additions and 1 deletions

View File

@@ -8,6 +8,7 @@
### 🐛 Fixed
- **test(oauth): prove refresh_token preservation for the real gemini-cli / antigravity dispatch** — the #3679/#3766 regression test used a synthetic provider that routes through the generic `tokenUrl` path, so the fix was never proven for the actual Google-family providers, which dispatch through `refreshGoogleToken()` against the hardcoded `OAUTH_ENDPOINTS.google.token`. Added a test that drives `checkConnection` through the real `gemini-cli`/`antigravity` path (redirecting the Google token endpoint to a local server returning `invalid_grant`) and asserts the `refresh_token` is preserved (not nulled) — confirming these connections are not spuriously destroyed on a failed refresh. ([#3850](https://github.com/diegosouzapw/OmniRoute/issues/3850) — thanks @3xa228148)
- **fix(oauth): clear setup message for GitLab Duo instead of "Internal server error"** — adding a GitLab Duo connection without a registered OAuth client returned an opaque `Internal server error` at the Add Connection step. `buildAuthUrl` **threw** when `GITLAB_DUO_OAUTH_CLIENT_ID` was missing, and the route swallowed it into a generic 500. It now returns `null` (mirroring the Qoder provider) and the authorize route surfaces an actionable message: register an OAuth app at `https://gitlab.com/-/profile/applications` with redirect URI `http://localhost:20128/callback` and scopes `ai_features read_user`, then set `GITLAB_DUO_OAUTH_CLIENT_ID`. ([#3861](https://github.com/diegosouzapw/OmniRoute/issues/3861) — thanks @sidinsearch)
- **fix(db): persist the "Keep latest backups" retention setting** — changing the backup-retention count in Settings → Database backup retention had no effect: it always snapped back to 20 on refresh (and editing `.env` post-start was ignored too, since `process.env` isn't reloaded). `getDbBackupMaxFiles()` only read the `DB_BACKUP_MAX_FILES` env var — there was no setter and no persisted value. The value now round-trips through a dedicated `key_value` store (`getDbBackupMaxFiles` precedence: env override → persisted UI value → default 20), and the "Clean old backups" action persists the chosen count. Existing installs keep the historical default of 20 until explicitly changed. ([#3834](https://github.com/diegosouzapw/OmniRoute/issues/3834) — thanks @netstratego)
- **fix(sse): clamp Gemini thinking budget to the model's real cap (`reasoning_effort`/`effort=high` 400)** — translating OpenAI `reasoning_effort=high` (and Claude-Code `output_config.effort=high`) to a Gemini target sent a hardcoded `thinkingBudget: 32768`, which exceeds Flash-tier Gemini's real max of 24576 → upstream HTTP 400 (the `thinkingLevel=high` path already used 24576 and worked on the same model). `gemini-2.5-flash` now declares its real `thinkingBudgetCap` (24576) so the existing `capThinkingBudget()` chokepoint actually clamps, and the Claude→Gemini `output_config.effort` path — which previously sent the raw value with no cap at all — now routes through the same clamp (pro-tier, real cap 32768, is left untouched). ([#3842](https://github.com/diegosouzapw/OmniRoute/issues/3842) — thanks @andrea-kingautomation)

View File

@@ -14,7 +14,7 @@ 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 settingsDb = await import("../../src/lib/db/settings.ts");
const { PROVIDERS } = await import("../../open-sse/config/constants.ts");
const { PROVIDERS, OAUTH_ENDPOINTS } = await import("../../open-sse/config/constants.ts");
const tokenHealthCheck = await import("../../src/lib/tokenHealthCheck.ts");
async function resetStorage() {
@@ -526,3 +526,61 @@ test("checkConnection preserves refresh_token for non-rotating providers on unre
}
);
});
// Regression for #3850 (continuation of #3679): the #3679 test above uses a SYNTHETIC
// provider that routes through the generic refreshAccessToken/tokenUrl path. The real
// Google-family providers (gemini-cli / antigravity) instead dispatch through
// refreshGoogleToken() against the HARDCODED OAUTH_ENDPOINTS.google.token — a path the
// synthetic test never exercised, which left #3766's correctness unproven for the
// actual reported providers. This drives checkConnection through the REAL gemini-cli /
// antigravity dispatch and asserts the refresh_token is preserved (NOT nulled) when
// Google rejects the refresh with invalid_grant.
for (const providerId of ["gemini-cli", "antigravity"]) {
test(`checkConnection preserves refresh_token for ${providerId} on invalid_grant (#3850)`, async () => {
await resetStorage();
let refreshCount = 0;
const originalGoogleTokenUrl = OAUTH_ENDPOINTS.google.token;
await withHttpServer(
(_req, res) => {
refreshCount += 1;
// Google returns invalid_grant → isUnrecoverableRefreshError() is true.
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ error: "invalid_grant", error_description: "Bad Request" }));
},
async (tokenServer) => {
// gemini-cli / antigravity refresh hits OAUTH_ENDPOINTS.google.token directly
// (not a per-provider tokenUrl), so redirect that hardcoded endpoint.
OAUTH_ENDPOINTS.google.token = `${tokenServer.url}/token`;
try {
const connection = await providersDb.createProviderConnection({
provider: providerId,
authType: "oauth",
name: `${providerId} Account`,
email: `${providerId}@example.com`,
accessToken: "expired-access-token",
refreshToken: "rt-keep-3850",
// Already expired → proactive refresh runs AND the still-valid guard fails,
// so execution reaches the deactivation branch.
expiresAt: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
isActive: true,
});
await tokenHealthCheck.checkConnection(connection);
const updated = await providersDb.getProviderConnectionById((connection as any).id);
assert.equal(refreshCount, 1, "the expired token must trigger one refresh attempt");
assert.equal(updated?.testStatus, "expired", "should reach the unrecoverable branch");
assert.equal(
updated?.refreshToken,
"rt-keep-3850",
`${providerId} (non-rotating) must keep its refresh_token for recovery`
);
} finally {
OAUTH_ENDPOINTS.google.token = originalGoogleTokenUrl;
}
}
);
});
}