import { describe, expect, it, vi, beforeEach } from 'vitest' import { mount } from '@vue/test-utils' import { nextTick } from 'vue' import UsageTable from '../UsageTable.vue' const messages: Record = { 'usage.costDetails': 'Cost Breakdown', 'admin.usage.inputCost': 'Input Cost', 'admin.usage.outputCost': 'Output Cost', 'admin.usage.cacheCreationCost': 'Cache Creation Cost', 'admin.usage.cacheReadCost': 'Cache Read Cost', 'usage.inputTokenPrice': 'Input price', 'usage.outputTokenPrice': 'Output price', 'usage.perMillionTokens': '/ 1M tokens', 'usage.serviceTier': 'Service tier', 'usage.serviceTierPriority': 'Fast', 'usage.serviceTierFlex': 'Flex', 'usage.serviceTierStandard': 'Standard', 'usage.rate': 'Rate', 'usage.accountMultiplier': 'Account rate', 'usage.original': 'Original', 'usage.userBilled': 'User billed', 'usage.accountBilled': 'Account billed', 'usage.imageUnit': ' images', 'usage.imageCount': 'Image count', 'usage.imageBillingSize': 'Billing size', 'usage.imageInputSize': 'Input size', 'usage.imageOutputSize': 'Output size', 'usage.imageSizeSource': 'Size source', 'usage.imageSizeBreakdown': 'Size breakdown', 'usage.imageSizeSourceOutput': 'Upstream output', 'usage.imageSizeSourceInput': 'Request input', 'usage.imageSizeSourceDefault': 'Default billing tier', 'usage.imageSizeSourceLegacy': 'Legacy record', 'usage.imageSizeSourceMissing': 'Not recorded', 'usage.imageSizeNotRecorded': 'not recorded', 'usage.imageSizeLegacyUnstandardized': 'legacy unstandardized', 'usage.imageSizeUnknown': 'unknown', 'usage.imageUnitPrice': 'Per-image price', 'usage.imageTotalPrice': 'Image total price', 'admin.usage.billingModeToken': 'Token', 'admin.usage.billingModePerRequest': 'Per request', 'admin.usage.billingModeImage': 'Image', } vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: () => ({ t: (key: string) => messages[key] ?? key, }), } }) const DataTableStub = { props: ['data'], template: `
`, } const baseImageRow = { request_id: 'req-admin-image', model: 'gpt-image-2', actual_cost: 0.4, total_cost: 0.4, account_rate_multiplier: 1, rate_multiplier: 1, service_tier: null, input_cost: 0, output_cost: 0, cache_creation_cost: 0, cache_read_cost: 0, input_tokens: 0, output_tokens: 0, cache_creation_tokens: 0, cache_read_tokens: 0, cache_creation_5m_tokens: 0, cache_creation_1h_tokens: 0, cache_ttl_overridden: false, billing_mode: 'image', image_count: 2, image_size: '2K', image_input_size: null, image_output_size: null, image_size_source: null, image_size_breakdown: null, } describe('admin UsageTable tooltip', () => { beforeEach(() => { vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockReturnValue({ x: 0, y: 0, top: 20, left: 20, right: 120, bottom: 40, width: 100, height: 20, toJSON: () => ({}), } as DOMRect) }) it('shows service tier and billing breakdown in cost tooltip', async () => { const row = { request_id: 'req-admin-1', actual_cost: 0.092883, total_cost: 0.092883, account_rate_multiplier: 1, rate_multiplier: 1, service_tier: 'priority', input_cost: 0.020285, output_cost: 0.00303, cache_creation_cost: 0, cache_read_cost: 0.069568, input_tokens: 4057, output_tokens: 101, } const wrapper = mount(UsageTable, { props: { data: [row], loading: false, columns: [], }, global: { stubs: { DataTable: DataTableStub, EmptyState: true, Icon: true, Teleport: true, }, }, }) const tooltipTriggers = wrapper.findAll('.group.relative') await tooltipTriggers[tooltipTriggers.length - 1].trigger('mouseenter') await nextTick() const text = wrapper.text() expect(text).toContain('Service tier') expect(text).toContain('Fast') expect(text).toContain('Rate') expect(text).toContain('1.00x') expect(text).toContain('Account rate') expect(text).toContain('User billed') expect(text).toContain('Account billed') expect(text).toContain('$0.092883') expect(text).toContain('$5.0000 / 1M tokens') expect(text).toContain('$30.0000 / 1M tokens') expect(text).toContain('$0.069568') }) it('shows requested and upstream models separately for admin rows', () => { const row = { request_id: 'req-admin-model-1', model: 'claude-sonnet-4', upstream_model: 'claude-sonnet-4-20250514', actual_cost: 0, total_cost: 0, account_rate_multiplier: 1, rate_multiplier: 1, input_cost: 0, output_cost: 0, cache_creation_cost: 0, cache_read_cost: 0, input_tokens: 0, output_tokens: 0, } const wrapper = mount(UsageTable, { props: { data: [row], loading: false, columns: [], }, global: { stubs: { DataTable: DataTableStub, EmptyState: true, Icon: true, Teleport: true, }, }, }) const text = wrapper.text() expect(text).toContain('claude-sonnet-4') expect(text).toContain('claude-sonnet-4-20250514') }) it.each([ { name: 'defaulted row', row: { ...baseImageRow, request_id: 'req-admin-default-image', image_size: '2K', image_input_size: 'auto', image_output_size: null, image_size_source: 'default', }, expected: ['2K', 'Default billing tier', 'auto', 'unknown'], }, { name: 'output-sourced row', row: { ...baseImageRow, request_id: 'req-admin-output-image', image_size: '4K', image_input_size: '1024x1024', image_output_size: '3840x2160', image_size_source: 'output', image_size_breakdown: { '4K': 1 }, }, expected: ['4K', 'Upstream output', '1024x1024', '3840x2160', '4K x 1'], }, { name: 'input-sourced row', row: { ...baseImageRow, request_id: 'req-admin-input-image', image_size: '1K', image_input_size: '1024x1024', image_output_size: null, image_size_source: 'input', }, expected: ['1K', 'Request input', '1024x1024', 'unknown'], }, { name: 'legacy unstandardized row', row: { ...baseImageRow, request_id: 'req-admin-legacy-unstandardized-image', image_size: '512x512', image_input_size: null, image_output_size: null, image_size_source: null, }, expected: ['legacy unstandardized: 512x512', 'Legacy record', 'unknown'], }, ])('shows image usage metadata for $name', async ({ row, expected }) => { const wrapper = mount(UsageTable, { props: { data: [row], loading: false, columns: [], }, global: { stubs: { DataTable: DataTableStub, EmptyState: true, Icon: true, Teleport: true, }, }, }) await wrapper.find('.group.relative').trigger('mouseenter') await nextTick() const text = wrapper.text() expect(text).toContain('Image count') expect(text).toContain('Billing size') expect(text).toContain('Size source') expect(text).toContain('Input size') expect(text).toContain('Output size') expect(text).toContain('Per-image price') expect(text).toContain('Image total price') for (const value of expected) { expect(text).toContain(value) } }) it('displays historical image rows with missing billing_mode as image usage without a 2K fallback', async () => { const wrapper = mount(UsageTable, { props: { data: [ { ...baseImageRow, request_id: 'req-admin-legacy-missing-image', billing_mode: null, image_size: null, image_input_size: null, image_output_size: null, image_size_source: null, image_size_breakdown: null, }, ], loading: false, columns: [], }, global: { stubs: { DataTable: DataTableStub, EmptyState: true, Icon: true, Teleport: true, }, }, }) await wrapper.find('.group.relative').trigger('mouseenter') await nextTick() const text = wrapper.text() expect(text).toContain('Image') expect(text).toContain('Image count') expect(text).toContain('Per-image price') expect(text).toContain('not recorded') expect(text).not.toContain('(2K)') }) })