mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-08 02:17:46 +08:00
feat(pricing): support processing tier multipliers
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp, defineComponent, h, nextTick, ref, type App, type Ref } from 'vue'
|
||||
|
||||
import type { RequestDetail } from '@/api/dashboard'
|
||||
import RequestDetailDrawer from '../RequestDetailDrawer.vue'
|
||||
|
||||
const apiMocks = vi.hoisted(() => ({
|
||||
getRequestDetail: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/dashboard', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/api/dashboard')>()
|
||||
return {
|
||||
...actual,
|
||||
dashboardApi: {
|
||||
...actual.dashboardApi,
|
||||
getRequestDetail: apiMocks.getRequestDetail,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
|
||||
|
||||
afterEach(() => {
|
||||
for (const { app, root } of mountedApps.splice(0)) {
|
||||
app.unmount()
|
||||
root.remove()
|
||||
}
|
||||
apiMocks.getRequestDetail.mockReset()
|
||||
})
|
||||
|
||||
function buildEmbeddingDetail(): RequestDetail {
|
||||
return {
|
||||
id: 'usage-embedding-1',
|
||||
request_id: 'req-embedding-1',
|
||||
user: {
|
||||
id: 'user-1',
|
||||
username: 'embedding-user',
|
||||
email: '[email protected]',
|
||||
},
|
||||
api_key: {
|
||||
id: 'key-1',
|
||||
name: 'test-key',
|
||||
display: 'test-key',
|
||||
},
|
||||
provider: 'embedding-provider',
|
||||
model: 'embedding-model',
|
||||
tokens: { input: 100, output: 0, total: 100 },
|
||||
cost: { input: 0.00001, output: 0, total: 0.00001 },
|
||||
request_type: 'embedding',
|
||||
is_stream: false,
|
||||
status: 'completed',
|
||||
status_code: 200,
|
||||
response_time_ms: 10,
|
||||
created_at: '2026-07-16T00:00:00Z',
|
||||
request_headers: { 'content-type': 'application/json' },
|
||||
settlement: {
|
||||
settlement_snapshot: {
|
||||
pricing_snapshot: {
|
||||
pricing_source: 'global_default',
|
||||
tiered_pricing: {
|
||||
tiers: [{ up_to: null, input_price_per_1m: 0.1 }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('RequestDetailDrawer settlement pricing', () => {
|
||||
it('renders an input-only embedding tier without treating the missing output price as zero', async () => {
|
||||
apiMocks.getRequestDetail.mockResolvedValue(buildEmbeddingDetail())
|
||||
|
||||
let isOpen!: Ref<boolean>
|
||||
const Host = defineComponent({
|
||||
setup() {
|
||||
isOpen = ref(false)
|
||||
return () => h(RequestDetailDrawer, {
|
||||
isOpen: isOpen.value,
|
||||
requestId: 'usage-embedding-1',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(Host)
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
|
||||
isOpen.value = true
|
||||
await nextTick()
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(document.body.textContent).toContain('输入 $0.1/M')
|
||||
expect(document.body.textContent).toContain('输出 -')
|
||||
})
|
||||
expect(document.body.textContent).not.toContain('输出 $0/M')
|
||||
})
|
||||
})
|
||||
@@ -33,9 +33,75 @@ describe('ServiceTierFacts', () => {
|
||||
'计费层级',
|
||||
])
|
||||
expect([...root.querySelectorAll('dd')].map(node => node.textContent?.trim())).toEqual([
|
||||
'priority',
|
||||
'Fast',
|
||||
'-',
|
||||
'flex',
|
||||
])
|
||||
expect([...root.querySelectorAll('dd')].map(node => node.getAttribute('title'))).toEqual([
|
||||
'Fast',
|
||||
'-',
|
||||
'flex',
|
||||
])
|
||||
})
|
||||
|
||||
it('uses the same Fast label for raw priority and fast facts', () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp({
|
||||
render: () => h(ServiceTierFacts, {
|
||||
requested: 'priority',
|
||||
actual: 'fast',
|
||||
billing: 'priority',
|
||||
}),
|
||||
})
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
|
||||
expect([...root.querySelectorAll('dd')].map(node => node.textContent?.trim())).toEqual([
|
||||
'Fast',
|
||||
'Fast',
|
||||
'Fast',
|
||||
])
|
||||
expect([...root.querySelectorAll('dd')].map(node => node.getAttribute('title'))).toEqual([
|
||||
'Fast',
|
||||
'Fast',
|
||||
'Fast',
|
||||
])
|
||||
})
|
||||
|
||||
it('renders the processing-tier multiplier with the billing tier label', () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp({
|
||||
render: () => h(ServiceTierFacts, {
|
||||
requested: 'priority',
|
||||
actual: 'fast',
|
||||
billing: 'fast',
|
||||
priceMultiplier: 2.5,
|
||||
}),
|
||||
})
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
|
||||
const multiplier = root.querySelector('[data-testid="service-tier-price-multiplier"]')
|
||||
expect(multiplier?.textContent).toContain('Fast 倍率')
|
||||
expect(multiplier?.textContent).toContain('2.5×')
|
||||
})
|
||||
|
||||
it('does not render an empty or invalid processing-tier multiplier', () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp({
|
||||
render: () => h(ServiceTierFacts, {
|
||||
requested: 'priority',
|
||||
actual: null,
|
||||
billing: null,
|
||||
priceMultiplier: null,
|
||||
}),
|
||||
})
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
|
||||
expect(root.querySelector('[data-testid="service-tier-price-multiplier"]')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -162,11 +162,12 @@ function mountUsageRecordsTable(records: UsageRecord[], overrides: Record<string
|
||||
return root
|
||||
}
|
||||
|
||||
function expectServiceTierBadge(root: HTMLElement, label: string) {
|
||||
const labels = [...root.querySelectorAll('span')]
|
||||
.map((element) => element.textContent?.trim())
|
||||
function expectServiceTierBadge(root: HTMLElement, label: string): HTMLElement {
|
||||
const badge = [...root.querySelectorAll<HTMLElement>('span')]
|
||||
.find(element => element.textContent?.trim() === label)
|
||||
|
||||
expect(labels).toContain(label)
|
||||
expect(badge).toBeDefined()
|
||||
return badge as HTMLElement
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
@@ -295,13 +296,26 @@ describe('UsageRecordsTable', () => {
|
||||
expect(root.textContent).toContain('xhigh')
|
||||
})
|
||||
|
||||
it('shows confirmed fast when requested and actual service tiers are priority', () => {
|
||||
it.each([
|
||||
['priority', 'priority'],
|
||||
['fast', 'fast'],
|
||||
['priority', 'fast'],
|
||||
['fast', 'priority'],
|
||||
])('shows confirmed Fast for requested %s and actual %s', (requested, actual) => {
|
||||
const root = mountUsageRecordsTable([buildRecord({
|
||||
service_tier: 'priority',
|
||||
actual_service_tier: 'priority',
|
||||
service_tier: requested,
|
||||
actual_service_tier: actual,
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast')
|
||||
const badge = expectServiceTierBadge(root, 'Fast')
|
||||
expect(badge.getAttribute('title')).toBe([
|
||||
'请求档位:Fast',
|
||||
'实际档位:Fast',
|
||||
'计费档位:Fast',
|
||||
].join('\n'))
|
||||
expect(badge.getAttribute('aria-label')).toBe(
|
||||
'请求档位:Fast,实际档位:Fast,计费档位:Fast',
|
||||
)
|
||||
})
|
||||
|
||||
it('shows fast to standard when the provider downgrades a priority request', () => {
|
||||
@@ -310,7 +324,12 @@ describe('UsageRecordsTable', () => {
|
||||
actual_service_tier: 'default',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast → standard')
|
||||
const badge = expectServiceTierBadge(root, 'Fast → standard')
|
||||
expect(badge.getAttribute('title')).toBe([
|
||||
'请求档位:Fast',
|
||||
'实际档位:default',
|
||||
'计费档位:standard',
|
||||
].join('\n'))
|
||||
})
|
||||
|
||||
it('shows fast to flex when the provider moves a priority request to flex', () => {
|
||||
@@ -319,7 +338,7 @@ describe('UsageRecordsTable', () => {
|
||||
actual_service_tier: 'flex',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast → flex')
|
||||
expectServiceTierBadge(root, 'Fast → flex')
|
||||
})
|
||||
|
||||
it('shows standard to fast when the provider upgrades a default request', () => {
|
||||
@@ -328,7 +347,7 @@ describe('UsageRecordsTable', () => {
|
||||
actual_service_tier: 'priority',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'standard → fast')
|
||||
expectServiceTierBadge(root, 'standard → Fast')
|
||||
})
|
||||
|
||||
it.each(['pending', 'streaming'] as const)(
|
||||
@@ -340,7 +359,7 @@ describe('UsageRecordsTable', () => {
|
||||
status,
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast · 待确认')
|
||||
expectServiceTierBadge(root, 'Fast · 待确认')
|
||||
},
|
||||
)
|
||||
|
||||
@@ -351,7 +370,7 @@ describe('UsageRecordsTable', () => {
|
||||
status: 'completed',
|
||||
})])
|
||||
|
||||
expectServiceTierBadge(root, 'fast · 未确认')
|
||||
expectServiceTierBadge(root, 'Fast · 未确认')
|
||||
})
|
||||
|
||||
it('offers embedding API formats in the usage record filter', () => {
|
||||
|
||||
Reference in New Issue
Block a user