fix: address latest coderabbit review findings

This commit is contained in:
Cita
2026-03-23 15:24:00 +08:00
parent 426d9a538d
commit 764b9aa0e6
21 changed files with 472 additions and 46 deletions
@@ -1084,4 +1084,65 @@ describe('refreshModelsForAccount credential discovery', () => {
'gemini-3-pro-preview',
]);
});
it('continues antigravity discovery after fetch errors and trims the oauth project id before posting', async () => {
getApiTokenMock.mockResolvedValue(null);
getModelsMock.mockRejectedValue(new Error('antigravity oauth discovery should not call adapter.getModels'));
undiciFetchMock
.mockRejectedValueOnce(new Error('network boom'))
.mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({
models: {
'gemini-3-pro-preview': { displayName: 'Gemini 3 Pro Preview' },
},
}),
text: async () => JSON.stringify({ ok: true }),
});
const site = await db.insert(schema.sites).values({
name: 'antigravity-site-trimmed',
url: 'https://cloudcode-pa.googleapis.com',
platform: 'antigravity',
status: 'active',
}).returning().get();
const account = await db.insert(schema.accounts).values({
siteId: site.id,
username: 'antigravity-trimmed@example.com',
accessToken: 'antigravity-access-token',
apiToken: null,
status: 'active',
oauthProvider: 'antigravity',
oauthAccountKey: 'antigravity-trimmed@example.com',
oauthProjectId: ' project-demo ',
extraConfig: JSON.stringify({
credentialMode: 'session',
oauth: {
provider: 'antigravity',
email: 'antigravity-trimmed@example.com',
},
}),
}).returning().get();
const result = await refreshModelsForAccount(account.id);
expect(result).toMatchObject({
accountId: account.id,
refreshed: true,
status: 'success',
errorCode: null,
tokenScanned: 0,
discoveredByCredential: true,
discoveredApiToken: false,
modelCount: 1,
modelsPreview: ['gemini-3-pro-preview'],
});
expect(undiciFetchMock).toHaveBeenCalledTimes(2);
expect(String(undiciFetchMock.mock.calls[1]?.[0] || '')).toBe('https://daily-cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels');
expect(JSON.parse(String(undiciFetchMock.mock.calls[1]?.[1]?.body || '{}'))).toEqual({
project: 'project-demo',
});
});
});
+52 -2
View File
@@ -4,10 +4,11 @@ import {
buildOauthInfoFromAccount,
buildStoredOauthStateFromAccount,
getOauthInfoFromAccount,
isOauthProvider,
} from './oauthAccount.js';
describe('oauth account identity helpers', () => {
it('prefers structured oauth identity columns over extraConfig metadata', () => {
it('prefers structured oauth provider/accountKey/projectId columns while preserving a legacy accountId', () => {
const oauth = getOauthInfoFromAccount({
oauthProvider: 'gemini-cli',
oauthAccountKey: 'structured-user@example.com',
@@ -25,7 +26,7 @@ describe('oauth account identity helpers', () => {
expect(oauth).toEqual(expect.objectContaining({
provider: 'gemini-cli',
accountId: 'structured-user@example.com',
accountId: 'json-user',
accountKey: 'structured-user@example.com',
projectId: 'structured-project',
refreshToken: 'refresh-token',
@@ -82,6 +83,29 @@ describe('oauth account identity helpers', () => {
}));
});
it('preserves legacy accountId when structured oauthAccountKey differs', () => {
const oauth = getOauthInfoFromAccount({
oauthProvider: 'codex',
oauthAccountKey: 'structured-account-key',
oauthProjectId: null,
extraConfig: JSON.stringify({
oauth: {
provider: 'codex',
accountId: 'legacy-account-id',
accountKey: 'legacy-account-key',
refreshToken: 'refresh-token',
},
}),
});
expect(oauth).toEqual(expect.objectContaining({
provider: 'codex',
accountId: 'legacy-account-id',
accountKey: 'structured-account-key',
refreshToken: 'refresh-token',
}));
});
it('builds patched oauth state from structured identity columns and current runtime state', () => {
const oauth = buildOauthInfoFromAccount({
oauthProvider: 'codex',
@@ -135,6 +159,32 @@ describe('oauth account identity helpers', () => {
});
});
it('still recognizes oauth providers from account rows after persisted state strips identity fields', () => {
const account = {
oauthProvider: 'codex',
oauthAccountKey: 'structured-account',
oauthProjectId: 'structured-project',
extraConfig: JSON.stringify({
oauth: buildStoredOauthStateFromAccount({
oauthProvider: 'codex',
oauthAccountKey: 'structured-account',
oauthProjectId: 'structured-project',
extraConfig: JSON.stringify({
oauth: {
provider: 'codex',
accountId: 'legacy-account',
refreshToken: 'refresh-token',
},
}),
}),
}),
};
expect(isOauthProvider(account)).toBe(true);
expect(isOauthProvider(account, 'codex')).toBe(true);
expect(isOauthProvider(account, 'gemini-cli')).toBe(false);
});
it('builds a structured identity backfill patch from legacy oauth metadata only for missing columns', () => {
expect(buildOauthIdentityBackfillPatch({
oauthProvider: null,
+6 -6
View File
@@ -179,11 +179,12 @@ export function getOauthInfoFromAccount(account?: OauthIdentityCarrier | null):
if (!provider) return null;
const structuredAccountKey = asTrimmedString(account.oauthAccountKey);
const accountKey = structuredAccountKey || storedIdentity?.accountKey || storedIdentity?.accountId;
const accountId = storedIdentity?.accountId || structuredAccountKey || accountKey;
const projectId = asTrimmedString(account.oauthProjectId) || storedIdentity?.projectId;
return {
...(storedRuntime || {}),
provider,
accountId: structuredAccountKey || storedIdentity?.accountId || accountKey,
accountId,
accountKey,
projectId,
};
@@ -275,13 +276,12 @@ export function buildStoredOauthStateFromAccount(
}
export function isOauthProvider(
account: Pick<typeof schema.accounts.$inferSelect, 'extraConfig'> | string | null | undefined,
account: OauthIdentityCarrier | string | null | undefined,
provider?: string,
): boolean {
const extraConfig = typeof account === 'string' || account == null
? account
: account.extraConfig;
const oauth = getOauthInfoFromExtraConfig(extraConfig);
const oauth = typeof account === 'string' || account == null
? getOauthInfoFromExtraConfig(account)
: getOauthInfoFromAccount(account);
if (!oauth) return false;
if (!provider) return true;
return oauth.provider === provider;
@@ -218,34 +218,39 @@ export async function discoverAntigravityModelsFromCloud(input: {
}
const oauth = getOauthInfoFromAccount(input.account);
const requestBody = oauth?.projectId ? { project: oauth.projectId } : {};
const projectId = (oauth?.projectId || '').trim();
const requestBody = projectId ? { project: projectId } : {};
let lastError = '';
for (const baseUrl of buildAntigravityDiscoveryBaseUrls(input.site.url || ANTIGRAVITY_UPSTREAM_BASE_URL)) {
const response = await fetch(
`${baseUrl}/v1internal:fetchAvailableModels`,
withSiteRecordProxyRequestInit(input.site, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': ANTIGRAVITY_MODELS_USER_AGENT,
},
body: JSON.stringify(requestBody),
}),
);
if (!response.ok) {
lastError = await response.text().catch(() => '') || `HTTP ${response.status}`;
continue;
}
try {
const response = await fetch(
`${baseUrl}/v1internal:fetchAvailableModels`,
withSiteRecordProxyRequestInit(input.site, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': ANTIGRAVITY_MODELS_USER_AGENT,
},
body: JSON.stringify(requestBody),
}),
);
if (!response.ok) {
lastError = await response.text().catch(() => '') || `HTTP ${response.status}`;
continue;
}
const payload = await response.json();
const models = normalizeDiscoveredModels(extractAntigravityModelIds(payload));
if (models.length > 0) {
return models;
const payload = await response.json();
const models = normalizeDiscoveredModels(extractAntigravityModelIds(payload));
if (models.length > 0) {
return models;
}
lastError = '未获取到可用模型';
} catch (error) {
lastError = error instanceof Error ? `${baseUrl}: ${error.message}` : String(error);
}
lastError = '未获取到可用模型';
}
throw new Error(lastError || '未获取到可用模型');