mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 23:02:10 +03:00
This commit is contained in:
committed by
GitHub
parent
9613025219
commit
e7014de65f
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user