mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-10 03:09:50 +08:00
Merge remote-tracking branch 'origin/main' into codex/usage-pending-reasoning-reset-expiry-20260712
# Conflicts: # crates/aether-ai-formats/src/formats/openai/responses/mod.rs # crates/aether-usage/runtime/src/runtime.rs # frontend/src/features/usage/components/UsageRecordsTable.vue # frontend/src/features/usage/components/__tests__/UsageRecordsTable.spec.ts
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { createApp, defineComponent, h } from 'vue'
|
||||
|
||||
import ProxyNodeSelect from '@/features/providers/components/ProxyNodeSelect.vue'
|
||||
import { createI18n } from '@/i18n'
|
||||
|
||||
const proxyNodesStore = vi.hoisted(() => ({
|
||||
loading: false,
|
||||
nodes: [],
|
||||
onlineNodes: [],
|
||||
ensureLoaded: vi.fn(() => Promise.resolve()),
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/proxy-nodes', () => ({
|
||||
useProxyNodesStore: () => proxyNodesStore,
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui', async () => {
|
||||
const { defineComponent, h } = await import('vue')
|
||||
const passthrough = (name: string) => defineComponent({
|
||||
name,
|
||||
setup(_, { slots }) {
|
||||
return () => h('div', slots.default?.())
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
Select: passthrough('SelectStub'),
|
||||
SelectTrigger: passthrough('SelectTriggerStub'),
|
||||
SelectValue: passthrough('SelectValueStub'),
|
||||
SelectContent: passthrough('SelectContentStub'),
|
||||
SelectItem: passthrough('SelectItemStub'),
|
||||
}
|
||||
})
|
||||
|
||||
describe('ProxyNodeSelect', () => {
|
||||
it('loads proxy nodes when mounted', () => {
|
||||
const root = document.createElement('div')
|
||||
const app = createApp(defineComponent({
|
||||
setup() {
|
||||
return () => h(ProxyNodeSelect, { modelValue: '' })
|
||||
},
|
||||
}))
|
||||
|
||||
app.use(createI18n())
|
||||
app.mount(root)
|
||||
|
||||
expect(proxyNodesStore.ensureLoaded).toHaveBeenCalledTimes(1)
|
||||
|
||||
app.unmount()
|
||||
})
|
||||
})
|
||||
+49
@@ -1,14 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
createCodexResetCreditIdempotencyKey,
|
||||
formatCodexResetCreditCount,
|
||||
formatCodexResetCreditExpiresAt,
|
||||
getCodexResetCreditAvailableCount,
|
||||
getVisibleCodexResetCreditItems,
|
||||
mergeCodexQuotaDisplays,
|
||||
} from '@/features/providers/components/codex-reset-credit-display'
|
||||
import type { QuotaResetCreditsSnapshot } from '@/api/endpoints/types'
|
||||
|
||||
describe('codex reset credit display helpers', () => {
|
||||
it('keeps reset credits and usage windows when snapshot sources are partially populated', () => {
|
||||
const merged = mergeCodexQuotaDisplays(
|
||||
{
|
||||
updated_at: 1_700_000_100,
|
||||
primary_used_percent: 25,
|
||||
reset_credits: {
|
||||
available_count: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
updated_at: 1_700_000_000,
|
||||
secondary_used_percent: 40,
|
||||
reset_credits: {
|
||||
credits: [{ id: 'credit-1', expires_at: 1_700_086_400 }],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(merged).toMatchObject({
|
||||
updated_at: 1_700_000_100,
|
||||
primary_used_percent: 25,
|
||||
secondary_used_percent: 40,
|
||||
reset_credits: {
|
||||
available_count: 2,
|
||||
credits: [{ id: 'credit-1', expires_at: 1_700_086_400 }],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps zero available credits displayable but non-positive detail items hidden', () => {
|
||||
const snapshot: QuotaResetCreditsSnapshot = {
|
||||
available_count: 0,
|
||||
@@ -90,4 +121,22 @@ describe('codex reset credit display helpers', () => {
|
||||
expect(getVisibleCodexResetCreditItems(snapshot, 1_700_000_300)[0]?.expiresAt)
|
||||
.toBe(1_700_000_600)
|
||||
})
|
||||
|
||||
it('generates a UUID v4 with secure random bytes when randomUUID is unavailable', () => {
|
||||
const idempotencyKey = createCodexResetCreditIdempotencyKey({
|
||||
getRandomValues(array) {
|
||||
array.set(Array.from({ length: 16 }, (_, index) => index))
|
||||
return array
|
||||
},
|
||||
})
|
||||
|
||||
expect(idempotencyKey).toBe('00010203-0405-4607-8809-0a0b0c0d0e0f')
|
||||
})
|
||||
|
||||
it('prefers the browser randomUUID implementation when available', () => {
|
||||
expect(createCodexResetCreditIdempotencyKey({
|
||||
randomUUID: () => 'existing-random-uuid',
|
||||
getRandomValues: array => array,
|
||||
})).toBe('existing-random-uuid')
|
||||
})
|
||||
})
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
function readSource(path: string): string {
|
||||
return readFileSync(resolve(process.cwd(), path), 'utf8')
|
||||
}
|
||||
|
||||
describe('provider key batch import UI contract', () => {
|
||||
it('shows the entry only for custom providers', () => {
|
||||
const source = readSource('src/features/providers/components/ProviderDetailDrawer.vue')
|
||||
|
||||
expect(source).toContain("provider.provider_type === 'custom'")
|
||||
expect(source).toContain('<ProviderKeyBatchImportDialog')
|
||||
expect(source).toContain('keyBatchImportDialogOpen')
|
||||
})
|
||||
|
||||
it('uses a three-step flow with per-key review overrides', () => {
|
||||
const source = readSource('src/features/providers/components/ProviderKeyBatchImportDialog.vue')
|
||||
|
||||
expect(source).toContain('parseProviderKeyBatchImport')
|
||||
expect(source).toContain("{ id: 3, label: '逐项确认' }")
|
||||
expect(source).toContain('ProviderKeyImportSettingsFields')
|
||||
expect(source).toContain('REVIEW_PAGE_SIZE')
|
||||
expect(source).toContain('item.customized')
|
||||
expect(source).toContain('api_formats: item.apiFormats')
|
||||
expect(source).toContain('batchImportPoolKeys')
|
||||
expect(source).not.toContain('MAX_BATCH_IMPORT_KEYS')
|
||||
|
||||
const fieldsSource = readSource('src/features/providers/components/ProviderKeyImportSettingsFields.vue')
|
||||
expect(fieldsSource).toContain('settings.max_probe_interval_minutes')
|
||||
expect(fieldsSource).toContain('settings.proxy_node_id')
|
||||
})
|
||||
|
||||
it('uses selective update_settings in pool batch management', () => {
|
||||
const source = readSource('src/features/pool/components/PoolAccountBatchDialog.vue')
|
||||
|
||||
expect(source).toContain("selectedAction === 'update_settings'")
|
||||
expect(source).toContain('buildPoolKeySettingsPatch')
|
||||
expect(source).toContain("confirmAndExecuteAction('update_settings')")
|
||||
expect(source).toContain('仅更新已勾选字段')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user