mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 02:17:46 +08:00
fix(routing): harden routed pool scheduling
This commit is contained in:
@@ -1,22 +1,27 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { moveStrategyItem } from '@/features/pool/utils/poolSchedulingDialog'
|
||||
import {
|
||||
mergePoolAdvancedPatch,
|
||||
moveStrategyItem,
|
||||
normalizeMutexSelection,
|
||||
} from '@/features/pool/utils/poolSchedulingDialog'
|
||||
|
||||
interface TestPresetItem {
|
||||
preset: string
|
||||
mutexGroup: string | null
|
||||
enabled: boolean
|
||||
applicable: boolean
|
||||
}
|
||||
|
||||
function buildItems(): TestPresetItem[] {
|
||||
return [
|
||||
{ preset: 'cache_affinity', mutexGroup: 'distribution_mode', enabled: false },
|
||||
{ preset: 'lru', mutexGroup: 'distribution_mode', enabled: true },
|
||||
{ preset: 'single_account', mutexGroup: 'distribution_mode', enabled: false },
|
||||
{ preset: 'load_balance', mutexGroup: 'distribution_mode', enabled: false },
|
||||
{ preset: 'recent_refresh', mutexGroup: null, enabled: true },
|
||||
{ preset: 'quota_balanced', mutexGroup: null, enabled: false },
|
||||
{ preset: 'priority_first', mutexGroup: null, enabled: true },
|
||||
{ preset: 'cache_affinity', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
|
||||
{ preset: 'lru', mutexGroup: 'distribution_mode', enabled: true, applicable: true },
|
||||
{ preset: 'single_account', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
|
||||
{ preset: 'load_balance', mutexGroup: 'distribution_mode', enabled: false, applicable: true },
|
||||
{ preset: 'recent_refresh', mutexGroup: null, enabled: true, applicable: true },
|
||||
{ preset: 'quota_balanced', mutexGroup: null, enabled: false, applicable: true },
|
||||
{ preset: 'priority_first', mutexGroup: null, enabled: true, applicable: true },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -69,4 +74,49 @@ describe('poolSchedulingDialog', () => {
|
||||
|
||||
expect(moved.map(item => item.preset)).toEqual(original.map(item => item.preset))
|
||||
})
|
||||
|
||||
it('keeps the first enabled distribution from the saved order', () => {
|
||||
const items = buildItems()
|
||||
items[0].enabled = true
|
||||
items[1].enabled = false
|
||||
items[3].enabled = true
|
||||
const savedOrder = [items[3], items[0], ...items.slice(1, 3), ...items.slice(4)]
|
||||
|
||||
const normalized = normalizeMutexSelection(savedOrder)
|
||||
|
||||
expect(normalized.find(item => item.preset === 'load_balance')?.enabled).toBe(true)
|
||||
expect(normalized.find(item => item.preset === 'cache_affinity')?.enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('does not invent a distribution mode when all are disabled', () => {
|
||||
const items = buildItems().map(item => ({
|
||||
...item,
|
||||
enabled: item.mutexGroup ? false : item.enabled,
|
||||
}))
|
||||
|
||||
const normalized = normalizeMutexSelection(items)
|
||||
|
||||
expect(normalized.filter(item => item.mutexGroup).every(item => !item.enabled)).toBe(true)
|
||||
})
|
||||
|
||||
it('preserves pool fields that the current dialog does not edit', () => {
|
||||
const merged = mergePoolAdvancedPatch({
|
||||
sticky_session_ttl_seconds: 900,
|
||||
cost_window_seconds: 7200,
|
||||
cost_limit_per_key_tokens: 100_000,
|
||||
probing_target_percent: 25,
|
||||
global_priority: 7,
|
||||
}, {
|
||||
score_top_n: 256,
|
||||
})
|
||||
|
||||
expect(merged).toEqual({
|
||||
sticky_session_ttl_seconds: 900,
|
||||
cost_window_seconds: 7200,
|
||||
cost_limit_per_key_tokens: 100_000,
|
||||
probing_target_percent: 25,
|
||||
global_priority: 7,
|
||||
score_top_n: 256,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,47 @@ export interface SchedulingDialogPresetLike {
|
||||
mutexGroup: string | null
|
||||
}
|
||||
|
||||
export interface SchedulingDialogSelectablePresetLike extends SchedulingDialogPresetLike {
|
||||
enabled: boolean
|
||||
applicable: boolean
|
||||
}
|
||||
|
||||
export function mergePoolAdvancedPatch(
|
||||
current: unknown,
|
||||
patch: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
const currentRecord = typeof current === 'object' && current !== null && !Array.isArray(current)
|
||||
? current as Record<string, unknown>
|
||||
: {}
|
||||
return {
|
||||
...currentRecord,
|
||||
...patch,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeMutexSelection<T extends SchedulingDialogSelectablePresetLike>(
|
||||
items: readonly T[],
|
||||
): T[] {
|
||||
const next = items.map(item => ({ ...item }))
|
||||
const groups = new Map<string, number[]>()
|
||||
|
||||
next.forEach((item, index) => {
|
||||
if (!item.mutexGroup) return
|
||||
const indexes = groups.get(item.mutexGroup) ?? []
|
||||
indexes.push(index)
|
||||
groups.set(item.mutexGroup, indexes)
|
||||
})
|
||||
|
||||
for (const indexes of groups.values()) {
|
||||
const winner = indexes.find(index => next[index].enabled && next[index].applicable)
|
||||
indexes.forEach((index) => {
|
||||
next[index].enabled = winner !== undefined && index === winner && next[index].applicable
|
||||
})
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
export function moveStrategyItem<T extends SchedulingDialogPresetLike>(
|
||||
items: readonly T[],
|
||||
itemIndex: number,
|
||||
|
||||
Reference in New Issue
Block a user