"...src/components/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "26060e702f210d4c19115ea4559229927c67923f"
Unverified Commit 21f349c0 authored by Wesley Liddick's avatar Wesley Liddick Committed by GitHub
Browse files

Merge pull request #1095 from LvyuanW/lvyuan/dev

fix(admin/accounts): reset edit modal state on reopen
parents 28e36f79 0772d925
...@@ -1980,271 +1980,281 @@ const normalizePoolModeRetryCount = (value: number) => { ...@@ -1980,271 +1980,281 @@ const normalizePoolModeRetryCount = (value: number) => {
return normalized return normalized
} }
watch( const syncFormFromAccount = (newAccount: Account | null) => {
() => props.account, if (!newAccount) {
(newAccount) => { return
if (newAccount) { }
antigravityMixedChannelConfirmed.value = false antigravityMixedChannelConfirmed.value = false
showMixedChannelWarning.value = false showMixedChannelWarning.value = false
mixedChannelWarningDetails.value = null mixedChannelWarningDetails.value = null
mixedChannelWarningRawMessage.value = '' mixedChannelWarningRawMessage.value = ''
mixedChannelWarningAction.value = null mixedChannelWarningAction.value = null
form.name = newAccount.name form.name = newAccount.name
form.notes = newAccount.notes || '' form.notes = newAccount.notes || ''
form.proxy_id = newAccount.proxy_id form.proxy_id = newAccount.proxy_id
form.concurrency = newAccount.concurrency form.concurrency = newAccount.concurrency
form.load_factor = newAccount.load_factor ?? null form.load_factor = newAccount.load_factor ?? null
form.priority = newAccount.priority form.priority = newAccount.priority
form.rate_multiplier = newAccount.rate_multiplier ?? 1 form.rate_multiplier = newAccount.rate_multiplier ?? 1
form.status = (newAccount.status === 'active' || newAccount.status === 'inactive' || newAccount.status === 'error') form.status = (newAccount.status === 'active' || newAccount.status === 'inactive' || newAccount.status === 'error')
? newAccount.status ? newAccount.status
: 'active' : 'active'
form.group_ids = newAccount.group_ids || [] form.group_ids = newAccount.group_ids || []
form.expires_at = newAccount.expires_at ?? null form.expires_at = newAccount.expires_at ?? null
// Load intercept warmup requests setting (applies to all account types) // Load intercept warmup requests setting (applies to all account types)
const credentials = newAccount.credentials as Record<string, unknown> | undefined const credentials = newAccount.credentials as Record<string, unknown> | undefined
interceptWarmupRequests.value = credentials?.intercept_warmup_requests === true interceptWarmupRequests.value = credentials?.intercept_warmup_requests === true
autoPauseOnExpired.value = newAccount.auto_pause_on_expired === true autoPauseOnExpired.value = newAccount.auto_pause_on_expired === true
// Load mixed scheduling setting (only for antigravity accounts) // Load mixed scheduling setting (only for antigravity accounts)
mixedScheduling.value = false mixedScheduling.value = false
allowOverages.value = false allowOverages.value = false
const extra = newAccount.extra as Record<string, unknown> | undefined const extra = newAccount.extra as Record<string, unknown> | undefined
mixedScheduling.value = extra?.mixed_scheduling === true mixedScheduling.value = extra?.mixed_scheduling === true
allowOverages.value = extra?.allow_overages === true allowOverages.value = extra?.allow_overages === true
// Load OpenAI passthrough toggle (OpenAI OAuth/API Key) // Load OpenAI passthrough toggle (OpenAI OAuth/API Key)
openaiPassthroughEnabled.value = false openaiPassthroughEnabled.value = false
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
codexCLIOnlyEnabled.value = false codexCLIOnlyEnabled.value = false
anthropicPassthroughEnabled.value = false anthropicPassthroughEnabled.value = false
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) { if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'apikey')) {
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
openaiOAuthResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, { openaiOAuthResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
modeKey: 'openai_oauth_responses_websockets_v2_mode', modeKey: 'openai_oauth_responses_websockets_v2_mode',
enabledKey: 'openai_oauth_responses_websockets_v2_enabled', enabledKey: 'openai_oauth_responses_websockets_v2_enabled',
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'], fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
defaultMode: OPENAI_WS_MODE_OFF defaultMode: OPENAI_WS_MODE_OFF
}) })
openaiAPIKeyResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, { openaiAPIKeyResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
modeKey: 'openai_apikey_responses_websockets_v2_mode', modeKey: 'openai_apikey_responses_websockets_v2_mode',
enabledKey: 'openai_apikey_responses_websockets_v2_enabled', enabledKey: 'openai_apikey_responses_websockets_v2_enabled',
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'], fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'],
defaultMode: OPENAI_WS_MODE_OFF defaultMode: OPENAI_WS_MODE_OFF
}) })
if (newAccount.type === 'oauth') { if (newAccount.type === 'oauth') {
codexCLIOnlyEnabled.value = extra?.codex_cli_only === true codexCLIOnlyEnabled.value = extra?.codex_cli_only === true
} }
} }
if (newAccount.platform === 'anthropic' && newAccount.type === 'apikey') { if (newAccount.platform === 'anthropic' && newAccount.type === 'apikey') {
anthropicPassthroughEnabled.value = extra?.anthropic_passthrough === true anthropicPassthroughEnabled.value = extra?.anthropic_passthrough === true
} }
// Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above) // Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
if (newAccount.type === 'apikey' || newAccount.type === 'bedrock') { if (newAccount.type === 'apikey' || newAccount.type === 'bedrock') {
const quotaVal = extra?.quota_limit as number | undefined const quotaVal = extra?.quota_limit as number | undefined
editQuotaLimit.value = (quotaVal && quotaVal > 0) ? quotaVal : null editQuotaLimit.value = (quotaVal && quotaVal > 0) ? quotaVal : null
const dailyVal = extra?.quota_daily_limit as number | undefined const dailyVal = extra?.quota_daily_limit as number | undefined
editQuotaDailyLimit.value = (dailyVal && dailyVal > 0) ? dailyVal : null editQuotaDailyLimit.value = (dailyVal && dailyVal > 0) ? dailyVal : null
const weeklyVal = extra?.quota_weekly_limit as number | undefined const weeklyVal = extra?.quota_weekly_limit as number | undefined
editQuotaWeeklyLimit.value = (weeklyVal && weeklyVal > 0) ? weeklyVal : null editQuotaWeeklyLimit.value = (weeklyVal && weeklyVal > 0) ? weeklyVal : null
// Load quota reset mode config // Load quota reset mode config
editDailyResetMode.value = (extra?.quota_daily_reset_mode as 'rolling' | 'fixed') || null editDailyResetMode.value = (extra?.quota_daily_reset_mode as 'rolling' | 'fixed') || null
editDailyResetHour.value = (extra?.quota_daily_reset_hour as number) ?? null editDailyResetHour.value = (extra?.quota_daily_reset_hour as number) ?? null
editWeeklyResetMode.value = (extra?.quota_weekly_reset_mode as 'rolling' | 'fixed') || null editWeeklyResetMode.value = (extra?.quota_weekly_reset_mode as 'rolling' | 'fixed') || null
editWeeklyResetDay.value = (extra?.quota_weekly_reset_day as number) ?? null editWeeklyResetDay.value = (extra?.quota_weekly_reset_day as number) ?? null
editWeeklyResetHour.value = (extra?.quota_weekly_reset_hour as number) ?? null editWeeklyResetHour.value = (extra?.quota_weekly_reset_hour as number) ?? null
editResetTimezone.value = (extra?.quota_reset_timezone as string) || null editResetTimezone.value = (extra?.quota_reset_timezone as string) || null
} else { } else {
editQuotaLimit.value = null editQuotaLimit.value = null
editQuotaDailyLimit.value = null editQuotaDailyLimit.value = null
editQuotaWeeklyLimit.value = null editQuotaWeeklyLimit.value = null
editDailyResetMode.value = null editDailyResetMode.value = null
editDailyResetHour.value = null editDailyResetHour.value = null
editWeeklyResetMode.value = null editWeeklyResetMode.value = null
editWeeklyResetDay.value = null editWeeklyResetDay.value = null
editWeeklyResetHour.value = null editWeeklyResetHour.value = null
editResetTimezone.value = null editResetTimezone.value = null
} }
// Load antigravity model mapping (Antigravity 只支持映射模式) // Load antigravity model mapping (Antigravity 只支持映射模式)
if (newAccount.platform === 'antigravity') { if (newAccount.platform === 'antigravity') {
const credentials = newAccount.credentials as Record<string, unknown> | undefined const credentials = newAccount.credentials as Record<string, unknown> | undefined
// Antigravity 始终使用映射模式 // Antigravity 始终使用映射模式
antigravityModelRestrictionMode.value = 'mapping' antigravityModelRestrictionMode.value = 'mapping'
antigravityWhitelistModels.value = [] antigravityWhitelistModels.value = []
// 从 model_mapping 读取映射配置 // 从 model_mapping 读取映射配置
const rawAgMapping = credentials?.model_mapping as Record<string, string> | undefined const rawAgMapping = credentials?.model_mapping as Record<string, string> | undefined
if (rawAgMapping && typeof rawAgMapping === 'object') { if (rawAgMapping && typeof rawAgMapping === 'object') {
const entries = Object.entries(rawAgMapping) const entries = Object.entries(rawAgMapping)
// 无论是白名单样式(key===value)还是真正的映射,都统一转换为映射列表 // 无论是白名单样式(key===value)还是真正的映射,都统一转换为映射列表
antigravityModelMappings.value = entries.map(([from, to]) => ({ from, to })) antigravityModelMappings.value = entries.map(([from, to]) => ({ from, to }))
} else { } else {
// 兼容旧数据:从 model_whitelist 读取,转换为映射格式 // 兼容旧数据:从 model_whitelist 读取,转换为映射格式
const rawWhitelist = credentials?.model_whitelist const rawWhitelist = credentials?.model_whitelist
if (Array.isArray(rawWhitelist) && rawWhitelist.length > 0) { if (Array.isArray(rawWhitelist) && rawWhitelist.length > 0) {
antigravityModelMappings.value = rawWhitelist antigravityModelMappings.value = rawWhitelist
.map((v) => String(v).trim()) .map((v) => String(v).trim())
.filter((v) => v.length > 0) .filter((v) => v.length > 0)
.map((m) => ({ from: m, to: m })) .map((m) => ({ from: m, to: m }))
} else {
antigravityModelMappings.value = []
}
}
} else { } else {
antigravityModelRestrictionMode.value = 'mapping'
antigravityWhitelistModels.value = []
antigravityModelMappings.value = [] antigravityModelMappings.value = []
} }
}
} else {
antigravityModelRestrictionMode.value = 'mapping'
antigravityWhitelistModels.value = []
antigravityModelMappings.value = []
}
// Load quota control settings (Anthropic OAuth/SetupToken only) // Load quota control settings (Anthropic OAuth/SetupToken only)
loadQuotaControlSettings(newAccount) loadQuotaControlSettings(newAccount)
loadTempUnschedRules(credentials) loadTempUnschedRules(credentials)
// Initialize API Key fields for apikey type // Initialize API Key fields for apikey type
if (newAccount.type === 'apikey' && newAccount.credentials) { if (newAccount.type === 'apikey' && newAccount.credentials) {
const credentials = newAccount.credentials as Record<string, unknown> const credentials = newAccount.credentials as Record<string, unknown>
const platformDefaultUrl = const platformDefaultUrl =
newAccount.platform === 'openai' || newAccount.platform === 'sora' newAccount.platform === 'openai' || newAccount.platform === 'sora'
? 'https://api.openai.com' ? 'https://api.openai.com'
: newAccount.platform === 'gemini' : newAccount.platform === 'gemini'
? 'https://generativelanguage.googleapis.com' ? 'https://generativelanguage.googleapis.com'
: 'https://api.anthropic.com' : 'https://api.anthropic.com'
editBaseUrl.value = (credentials.base_url as string) || platformDefaultUrl editBaseUrl.value = (credentials.base_url as string) || platformDefaultUrl
// Load model mappings and detect mode // Load model mappings and detect mode
const existingMappings = credentials.model_mapping as Record<string, string> | undefined const existingMappings = credentials.model_mapping as Record<string, string> | undefined
if (existingMappings && typeof existingMappings === 'object') { if (existingMappings && typeof existingMappings === 'object') {
const entries = Object.entries(existingMappings) const entries = Object.entries(existingMappings)
// Detect if this is whitelist mode (all from === to) or mapping mode // Detect if this is whitelist mode (all from === to) or mapping mode
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to) const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
if (isWhitelistMode) { if (isWhitelistMode) {
// Whitelist mode: populate allowedModels // Whitelist mode: populate allowedModels
modelRestrictionMode.value = 'whitelist' modelRestrictionMode.value = 'whitelist'
allowedModels.value = entries.map(([from]) => from) allowedModels.value = entries.map(([from]) => from)
modelMappings.value = [] modelMappings.value = []
} else { } else {
// Mapping mode: populate modelMappings // Mapping mode: populate modelMappings
modelRestrictionMode.value = 'mapping' modelRestrictionMode.value = 'mapping'
modelMappings.value = entries.map(([from, to]) => ({ from, to })) modelMappings.value = entries.map(([from, to]) => ({ from, to }))
allowedModels.value = [] allowedModels.value = []
} }
} else { } else {
// No mappings: default to whitelist mode with empty selection (allow all) // No mappings: default to whitelist mode with empty selection (allow all)
modelRestrictionMode.value = 'whitelist' modelRestrictionMode.value = 'whitelist'
modelMappings.value = [] modelMappings.value = []
allowedModels.value = [] allowedModels.value = []
} }
// Load pool mode // Load pool mode
poolModeEnabled.value = credentials.pool_mode === true poolModeEnabled.value = credentials.pool_mode === true
poolModeRetryCount.value = normalizePoolModeRetryCount( poolModeRetryCount.value = normalizePoolModeRetryCount(
Number(credentials.pool_mode_retry_count ?? DEFAULT_POOL_MODE_RETRY_COUNT) Number(credentials.pool_mode_retry_count ?? DEFAULT_POOL_MODE_RETRY_COUNT)
) )
// Load custom error codes // Load custom error codes
customErrorCodesEnabled.value = credentials.custom_error_codes_enabled === true customErrorCodesEnabled.value = credentials.custom_error_codes_enabled === true
const existingErrorCodes = credentials.custom_error_codes as number[] | undefined const existingErrorCodes = credentials.custom_error_codes as number[] | undefined
if (existingErrorCodes && Array.isArray(existingErrorCodes)) { if (existingErrorCodes && Array.isArray(existingErrorCodes)) {
selectedErrorCodes.value = [...existingErrorCodes] selectedErrorCodes.value = [...existingErrorCodes]
} else { } else {
selectedErrorCodes.value = [] selectedErrorCodes.value = []
} }
} else if (newAccount.type === 'bedrock' && newAccount.credentials) { } else if (newAccount.type === 'bedrock' && newAccount.credentials) {
const bedrockCreds = newAccount.credentials as Record<string, unknown> const bedrockCreds = newAccount.credentials as Record<string, unknown>
const authMode = (bedrockCreds.auth_mode as string) || 'sigv4' const authMode = (bedrockCreds.auth_mode as string) || 'sigv4'
editBedrockRegion.value = (bedrockCreds.aws_region as string) || '' editBedrockRegion.value = (bedrockCreds.aws_region as string) || ''
editBedrockForceGlobal.value = (bedrockCreds.aws_force_global as string) === 'true' editBedrockForceGlobal.value = (bedrockCreds.aws_force_global as string) === 'true'
if (authMode === 'apikey') { if (authMode === 'apikey') {
editBedrockApiKeyValue.value = '' editBedrockApiKeyValue.value = ''
} else { } else {
editBedrockAccessKeyId.value = (bedrockCreds.aws_access_key_id as string) || '' editBedrockAccessKeyId.value = (bedrockCreds.aws_access_key_id as string) || ''
editBedrockSecretAccessKey.value = '' editBedrockSecretAccessKey.value = ''
editBedrockSessionToken.value = '' editBedrockSessionToken.value = ''
} }
// Load pool mode for bedrock // Load pool mode for bedrock
poolModeEnabled.value = bedrockCreds.pool_mode === true poolModeEnabled.value = bedrockCreds.pool_mode === true
const retryCount = bedrockCreds.pool_mode_retry_count const retryCount = bedrockCreds.pool_mode_retry_count
poolModeRetryCount.value = (typeof retryCount === 'number' && retryCount >= 0) ? retryCount : DEFAULT_POOL_MODE_RETRY_COUNT poolModeRetryCount.value = (typeof retryCount === 'number' && retryCount >= 0) ? retryCount : DEFAULT_POOL_MODE_RETRY_COUNT
// Load quota limits for bedrock // Load quota limits for bedrock
const bedrockExtra = (newAccount.extra as Record<string, unknown>) || {} const bedrockExtra = (newAccount.extra as Record<string, unknown>) || {}
editQuotaLimit.value = typeof bedrockExtra.quota_limit === 'number' ? bedrockExtra.quota_limit : null editQuotaLimit.value = typeof bedrockExtra.quota_limit === 'number' ? bedrockExtra.quota_limit : null
editQuotaDailyLimit.value = typeof bedrockExtra.quota_daily_limit === 'number' ? bedrockExtra.quota_daily_limit : null editQuotaDailyLimit.value = typeof bedrockExtra.quota_daily_limit === 'number' ? bedrockExtra.quota_daily_limit : null
editQuotaWeeklyLimit.value = typeof bedrockExtra.quota_weekly_limit === 'number' ? bedrockExtra.quota_weekly_limit : null editQuotaWeeklyLimit.value = typeof bedrockExtra.quota_weekly_limit === 'number' ? bedrockExtra.quota_weekly_limit : null
// Load model mappings for bedrock // Load model mappings for bedrock
const existingMappings = bedrockCreds.model_mapping as Record<string, string> | undefined const existingMappings = bedrockCreds.model_mapping as Record<string, string> | undefined
if (existingMappings && typeof existingMappings === 'object') { if (existingMappings && typeof existingMappings === 'object') {
const entries = Object.entries(existingMappings) const entries = Object.entries(existingMappings)
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to) const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
if (isWhitelistMode) { if (isWhitelistMode) {
modelRestrictionMode.value = 'whitelist' modelRestrictionMode.value = 'whitelist'
allowedModels.value = entries.map(([from]) => from) allowedModels.value = entries.map(([from]) => from)
modelMappings.value = [] modelMappings.value = []
} else {
modelRestrictionMode.value = 'mapping'
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
allowedModels.value = []
}
} else {
modelRestrictionMode.value = 'whitelist'
modelMappings.value = []
allowedModels.value = []
}
} else if (newAccount.type === 'upstream' && newAccount.credentials) {
const credentials = newAccount.credentials as Record<string, unknown>
editBaseUrl.value = (credentials.base_url as string) || ''
} else { } else {
const platformDefaultUrl = modelRestrictionMode.value = 'mapping'
newAccount.platform === 'openai' || newAccount.platform === 'sora' modelMappings.value = entries.map(([from, to]) => ({ from, to }))
? 'https://api.openai.com' allowedModels.value = []
: newAccount.platform === 'gemini' }
? 'https://generativelanguage.googleapis.com' } else {
: 'https://api.anthropic.com' modelRestrictionMode.value = 'whitelist'
editBaseUrl.value = platformDefaultUrl modelMappings.value = []
allowedModels.value = []
// Load model mappings for OpenAI OAuth accounts }
if (newAccount.platform === 'openai' && newAccount.credentials) { } else if (newAccount.type === 'upstream' && newAccount.credentials) {
const oauthCredentials = newAccount.credentials as Record<string, unknown> const credentials = newAccount.credentials as Record<string, unknown>
const existingMappings = oauthCredentials.model_mapping as Record<string, string> | undefined editBaseUrl.value = (credentials.base_url as string) || ''
if (existingMappings && typeof existingMappings === 'object') { } else {
const entries = Object.entries(existingMappings) const platformDefaultUrl =
const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to) newAccount.platform === 'openai' || newAccount.platform === 'sora'
if (isWhitelistMode) { ? 'https://api.openai.com'
modelRestrictionMode.value = 'whitelist' : newAccount.platform === 'gemini'
allowedModels.value = entries.map(([from]) => from) ? 'https://generativelanguage.googleapis.com'
modelMappings.value = [] : 'https://api.anthropic.com'
} else { editBaseUrl.value = platformDefaultUrl
modelRestrictionMode.value = 'mapping'
modelMappings.value = entries.map(([from, to]) => ({ from, to })) // Load model mappings for OpenAI OAuth accounts
allowedModels.value = [] if (newAccount.platform === 'openai' && newAccount.credentials) {
} const oauthCredentials = newAccount.credentials as Record<string, unknown>
} else { const existingMappings = oauthCredentials.model_mapping as Record<string, string> | undefined
modelRestrictionMode.value = 'whitelist' if (existingMappings && typeof existingMappings === 'object') {
modelMappings.value = [] const entries = Object.entries(existingMappings)
allowedModels.value = [] const isWhitelistMode = entries.length > 0 && entries.every(([from, to]) => from === to)
} if (isWhitelistMode) {
} else {
modelRestrictionMode.value = 'whitelist' modelRestrictionMode.value = 'whitelist'
allowedModels.value = entries.map(([from]) => from)
modelMappings.value = [] modelMappings.value = []
} else {
modelRestrictionMode.value = 'mapping'
modelMappings.value = entries.map(([from, to]) => ({ from, to }))
allowedModels.value = [] allowedModels.value = []
} }
poolModeEnabled.value = false } else {
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT modelRestrictionMode.value = 'whitelist'
customErrorCodesEnabled.value = false modelMappings.value = []
selectedErrorCodes.value = [] allowedModels.value = []
} }
editApiKey.value = '' } else {
modelRestrictionMode.value = 'whitelist'
modelMappings.value = []
allowedModels.value = []
}
poolModeEnabled.value = false
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT
customErrorCodesEnabled.value = false
selectedErrorCodes.value = []
}
editApiKey.value = ''
}
watch(
[() => props.show, () => props.account],
([show, newAccount], [wasShow, previousAccount]) => {
if (!show || !newAccount) {
return
}
if (!wasShow || newAccount !== previousAccount) {
syncFormFromAccount(newAccount)
} }
}, },
{ immediate: true } { immediate: true }
......
import { describe, expect, it, vi } from 'vitest'
import { defineComponent } from 'vue'
import { mount } from '@vue/test-utils'
const { updateAccountMock, checkMixedChannelRiskMock } = vi.hoisted(() => ({
updateAccountMock: vi.fn(),
checkMixedChannelRiskMock: vi.fn()
}))
vi.mock('@/stores/app', () => ({
useAppStore: () => ({
showError: vi.fn(),
showSuccess: vi.fn(),
showInfo: vi.fn()
})
}))
vi.mock('@/stores/auth', () => ({
useAuthStore: () => ({
isSimpleMode: true
})
}))
vi.mock('@/api/admin', () => ({
adminAPI: {
accounts: {
update: updateAccountMock,
checkMixedChannelRisk: checkMixedChannelRiskMock
}
}
}))
vi.mock('@/api/admin/accounts', () => ({
getAntigravityDefaultModelMapping: vi.fn()
}))
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({
t: (key: string) => key
})
}
})
import EditAccountModal from '../EditAccountModal.vue'
const BaseDialogStub = defineComponent({
name: 'BaseDialog',
props: {
show: {
type: Boolean,
default: false
}
},
template: '<div v-if="show"><slot /><slot name="footer" /></div>'
})
const ModelWhitelistSelectorStub = defineComponent({
name: 'ModelWhitelistSelector',
props: {
modelValue: {
type: Array,
default: () => []
}
},
emits: ['update:modelValue'],
template: `
<div>
<button
type="button"
data-testid="rewrite-to-snapshot"
@click="$emit('update:modelValue', ['gpt-5.2-2025-12-11'])"
>
rewrite
</button>
<span data-testid="model-whitelist-value">
{{ Array.isArray(modelValue) ? modelValue.join(',') : '' }}
</span>
</div>
`
})
function buildAccount() {
return {
id: 1,
name: 'OpenAI Key',
notes: '',
platform: 'openai',
type: 'apikey',
credentials: {
api_key: 'sk-test',
base_url: 'https://api.openai.com',
model_mapping: {
'gpt-5.2': 'gpt-5.2'
}
},
extra: {},
proxy_id: null,
concurrency: 1,
priority: 1,
rate_multiplier: 1,
status: 'active',
group_ids: [],
expires_at: null,
auto_pause_on_expired: false
} as any
}
function mountModal(account = buildAccount()) {
return mount(EditAccountModal, {
props: {
show: true,
account,
proxies: [],
groups: []
},
global: {
stubs: {
BaseDialog: BaseDialogStub,
Select: true,
Icon: true,
ProxySelector: true,
GroupSelector: true,
ModelWhitelistSelector: ModelWhitelistSelectorStub
}
}
})
}
describe('EditAccountModal', () => {
it('reopening the same account rehydrates the OpenAI whitelist from props', async () => {
const account = buildAccount()
updateAccountMock.mockReset()
checkMixedChannelRiskMock.mockReset()
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
updateAccountMock.mockResolvedValue(account)
const wrapper = mountModal(account)
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2')
await wrapper.get('[data-testid="rewrite-to-snapshot"]').trigger('click')
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2-2025-12-11')
await wrapper.setProps({ show: false })
await wrapper.setProps({ show: true })
expect(wrapper.get('[data-testid="model-whitelist-value"]').text()).toBe('gpt-5.2')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.credentials?.model_mapping).toEqual({
'gpt-5.2': 'gpt-5.2'
})
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment