fix: 修复 Antigravity 账号 intercept_warmup_requests 配置无法保存

提取 applyInterceptWarmup 纯函数统一所有调用点:
- 修复 upstream 创建时遗漏写入 intercept_warmup_requests
- 修复 apikey 编辑时缺少 else 清除逻辑
- 添加前后端单元测试
- 修复 vitest.config.ts mergeConfig 兼容性问题
This commit is contained in:
erio
2026-02-22 23:28:11 +08:00
parent 7eb3b23ddf
commit 0ad20c9489
6 changed files with 173 additions and 54 deletions

View File

@@ -0,0 +1,66 @@
//go:build unit
package service
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAccount_IsInterceptWarmupEnabled(t *testing.T) {
tests := []struct {
name string
credentials map[string]any
expected bool
}{
{
name: "nil credentials",
credentials: nil,
expected: false,
},
{
name: "empty map",
credentials: map[string]any{},
expected: false,
},
{
name: "field not present",
credentials: map[string]any{"access_token": "tok"},
expected: false,
},
{
name: "field is true",
credentials: map[string]any{"intercept_warmup_requests": true},
expected: true,
},
{
name: "field is false",
credentials: map[string]any{"intercept_warmup_requests": false},
expected: false,
},
{
name: "field is string true",
credentials: map[string]any{"intercept_warmup_requests": "true"},
expected: false,
},
{
name: "field is int 1",
credentials: map[string]any{"intercept_warmup_requests": 1},
expected: false,
},
{
name: "field is nil",
credentials: map[string]any{"intercept_warmup_requests": nil},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Account{Credentials: tt.credentials}
result := a.IsInterceptWarmupEnabled()
require.Equal(t, tt.expected, result)
})
}
}

View File

@@ -2018,6 +2018,7 @@ import Icon from '@/components/icons/Icon.vue'
import ProxySelector from '@/components/common/ProxySelector.vue' import ProxySelector from '@/components/common/ProxySelector.vue'
import GroupSelector from '@/components/common/GroupSelector.vue' import GroupSelector from '@/components/common/GroupSelector.vue'
import ModelWhitelistSelector from '@/components/account/ModelWhitelistSelector.vue' import ModelWhitelistSelector from '@/components/account/ModelWhitelistSelector.vue'
import { applyInterceptWarmup } from '@/components/account/credentialsBuilder'
import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format' import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format'
import OAuthAuthorizationFlow from './OAuthAuthorizationFlow.vue' import OAuthAuthorizationFlow from './OAuthAuthorizationFlow.vue'
@@ -2832,6 +2833,8 @@ const handleSubmit = async () => {
credentials.model_mapping = antigravityModelMapping credentials.model_mapping = antigravityModelMapping
} }
applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
const extra = mixedScheduling.value ? { mixed_scheduling: true } : undefined const extra = mixedScheduling.value ? { mixed_scheduling: true } : undefined
await createAccountAndFinish(form.platform, 'apikey', credentials, extra) await createAccountAndFinish(form.platform, 'apikey', credentials, extra)
return return
@@ -2872,10 +2875,7 @@ const handleSubmit = async () => {
credentials.custom_error_codes = [...selectedErrorCodes.value] credentials.custom_error_codes = [...selectedErrorCodes.value]
} }
// Add intercept warmup requests setting applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
if (interceptWarmupRequests.value) {
credentials.intercept_warmup_requests = true
}
if (!applyTempUnschedConfig(credentials)) { if (!applyTempUnschedConfig(credentials)) {
return return
} }
@@ -3220,6 +3220,7 @@ const handleAntigravityExchange = async (authCode: string) => {
if (!tokenInfo) return if (!tokenInfo) return
const credentials = antigravityOAuth.buildCredentials(tokenInfo) const credentials = antigravityOAuth.buildCredentials(tokenInfo)
applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
// Antigravity 只使用映射模式 // Antigravity 只使用映射模式
const antigravityModelMapping = buildModelMappingObject( const antigravityModelMapping = buildModelMappingObject(
'mapping', 'mapping',
@@ -3291,10 +3292,8 @@ const handleAnthropicExchange = async (authCode: string) => {
extra.cache_ttl_override_target = cacheTTLOverrideTarget.value extra.cache_ttl_override_target = cacheTTLOverrideTarget.value
} }
const credentials = { const credentials: Record<string, unknown> = { ...tokenInfo }
...tokenInfo, applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
...(interceptWarmupRequests.value ? { intercept_warmup_requests: true } : {})
}
await createAccountAndFinish(form.platform, addMethod.value as AccountType, credentials, extra) await createAccountAndFinish(form.platform, addMethod.value as AccountType, credentials, extra)
} catch (error: any) { } catch (error: any) {
oauth.error.value = error.response?.data?.detail || t('admin.accounts.oauth.authFailed') oauth.error.value = error.response?.data?.detail || t('admin.accounts.oauth.authFailed')
@@ -3392,11 +3391,8 @@ const handleCookieAuth = async (sessionKey: string) => {
const accountName = keys.length > 1 ? `${form.name} #${i + 1}` : form.name const accountName = keys.length > 1 ? `${form.name} #${i + 1}` : form.name
// Merge interceptWarmupRequests into credentials const credentials: Record<string, unknown> = { ...tokenInfo }
const credentials: Record<string, unknown> = { applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
...tokenInfo,
...(interceptWarmupRequests.value ? { intercept_warmup_requests: true } : {})
}
if (tempUnschedEnabled.value) { if (tempUnschedEnabled.value) {
credentials.temp_unschedulable_enabled = true credentials.temp_unschedulable_enabled = true
credentials.temp_unschedulable_rules = tempUnschedPayload credentials.temp_unschedulable_rules = tempUnschedPayload

View File

@@ -1033,6 +1033,7 @@ import Icon from '@/components/icons/Icon.vue'
import ProxySelector from '@/components/common/ProxySelector.vue' import ProxySelector from '@/components/common/ProxySelector.vue'
import GroupSelector from '@/components/common/GroupSelector.vue' import GroupSelector from '@/components/common/GroupSelector.vue'
import ModelWhitelistSelector from '@/components/account/ModelWhitelistSelector.vue' import ModelWhitelistSelector from '@/components/account/ModelWhitelistSelector.vue'
import { applyInterceptWarmup } from '@/components/account/credentialsBuilder'
import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format' import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format'
import { import {
getPresetMappingsByPlatform, getPresetMappingsByPlatform,
@@ -1748,9 +1749,7 @@ const handleSubmit = async () => {
} }
// Add intercept warmup requests setting // Add intercept warmup requests setting
if (interceptWarmupRequests.value) { applyInterceptWarmup(newCredentials, interceptWarmupRequests.value, 'edit')
newCredentials.intercept_warmup_requests = true
}
if (!applyTempUnschedConfig(newCredentials)) { if (!applyTempUnschedConfig(newCredentials)) {
return return
} }
@@ -1766,6 +1765,9 @@ const handleSubmit = async () => {
newCredentials.api_key = editApiKey.value.trim() newCredentials.api_key = editApiKey.value.trim()
} }
// Add intercept warmup requests setting
applyInterceptWarmup(newCredentials, interceptWarmupRequests.value, 'edit')
if (!applyTempUnschedConfig(newCredentials)) { if (!applyTempUnschedConfig(newCredentials)) {
return return
} }
@@ -1776,11 +1778,7 @@ const handleSubmit = async () => {
const currentCredentials = (props.account.credentials as Record<string, unknown>) || {} const currentCredentials = (props.account.credentials as Record<string, unknown>) || {}
const newCredentials: Record<string, unknown> = { ...currentCredentials } const newCredentials: Record<string, unknown> = { ...currentCredentials }
if (interceptWarmupRequests.value) { applyInterceptWarmup(newCredentials, interceptWarmupRequests.value, 'edit')
newCredentials.intercept_warmup_requests = true
} else {
delete newCredentials.intercept_warmup_requests
}
if (!applyTempUnschedConfig(newCredentials)) { if (!applyTempUnschedConfig(newCredentials)) {
return return
} }

View File

@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest'
import { applyInterceptWarmup } from '../credentialsBuilder'
describe('applyInterceptWarmup', () => {
it('create + enabled=true: should set intercept_warmup_requests to true', () => {
const creds: Record<string, unknown> = { access_token: 'tok' }
applyInterceptWarmup(creds, true, 'create')
expect(creds.intercept_warmup_requests).toBe(true)
})
it('create + enabled=false: should not add the field', () => {
const creds: Record<string, unknown> = { access_token: 'tok' }
applyInterceptWarmup(creds, false, 'create')
expect('intercept_warmup_requests' in creds).toBe(false)
})
it('edit + enabled=true: should set intercept_warmup_requests to true', () => {
const creds: Record<string, unknown> = { api_key: 'sk' }
applyInterceptWarmup(creds, true, 'edit')
expect(creds.intercept_warmup_requests).toBe(true)
})
it('edit + enabled=false + field exists: should delete the field', () => {
const creds: Record<string, unknown> = { api_key: 'sk', intercept_warmup_requests: true }
applyInterceptWarmup(creds, false, 'edit')
expect('intercept_warmup_requests' in creds).toBe(false)
})
it('edit + enabled=false + field absent: should not throw', () => {
const creds: Record<string, unknown> = { api_key: 'sk' }
applyInterceptWarmup(creds, false, 'edit')
expect('intercept_warmup_requests' in creds).toBe(false)
})
it('should not affect other fields', () => {
const creds: Record<string, unknown> = {
api_key: 'sk',
base_url: 'url',
intercept_warmup_requests: true
}
applyInterceptWarmup(creds, false, 'edit')
expect(creds.api_key).toBe('sk')
expect(creds.base_url).toBe('url')
expect('intercept_warmup_requests' in creds).toBe(false)
})
})

View File

@@ -0,0 +1,11 @@
export function applyInterceptWarmup(
credentials: Record<string, unknown>,
enabled: boolean,
mode: 'create' | 'edit'
): void {
if (enabled) {
credentials.intercept_warmup_requests = true
} else if (mode === 'edit') {
delete credentials.intercept_warmup_requests
}
}

View File

@@ -1,9 +1,13 @@
import { defineConfig, mergeConfig } from 'vitest/config' import { defineConfig } from 'vitest/config'
import viteConfig from './vite.config' import { resolve } from 'path'
export default mergeConfig( export default defineConfig({
viteConfig, resolve: {
defineConfig({ alias: {
'@': resolve(__dirname, 'src'),
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
}
},
test: { test: {
globals: true, globals: true,
environment: 'jsdom', environment: 'jsdom',
@@ -28,8 +32,6 @@ export default mergeConfig(
lines: 80 lines: 80
} }
} }
},
setupFiles: ['./src/__tests__/setup.ts']
} }
}) }
) })