import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' import { nextTick } from 'vue' import KeyUsageView from '../KeyUsageView.vue' const { showInfo, showSuccess, showError, fetchPublicSettings } = vi.hoisted(() => ({ showInfo: vi.fn(), showSuccess: vi.fn(), showError: vi.fn(), fetchPublicSettings: vi.fn(), })) const messages: Record = { 'keyUsage.title': 'API Key Usage', 'keyUsage.subtitle': 'Usage status', 'keyUsage.placeholder': 'sk-test', 'keyUsage.query': 'Query', 'keyUsage.querying': 'Querying...', 'keyUsage.privacyNote': 'Privacy note', 'keyUsage.dateRange': 'Date Range:', 'keyUsage.dateRangeToday': 'Today', 'keyUsage.dateRange7d': '7 Days', 'keyUsage.dateRange30d': '30 Days', 'keyUsage.dateRange90d': '90 Days', 'keyUsage.dateRangeCustom': 'Custom', 'keyUsage.apply': 'Apply', 'keyUsage.used': 'Used', 'keyUsage.detailInfo': 'Detail Information', 'keyUsage.tokenStats': 'Token Statistics', 'keyUsage.dailyDetail': 'Daily Detail', 'keyUsage.date': 'Date', 'keyUsage.requests': 'Requests', 'keyUsage.inputTokens': 'Input Tokens', 'keyUsage.outputTokens': 'Output Tokens', 'keyUsage.cacheReadTokens': 'Cache Read', 'keyUsage.cacheWriteTokens': 'Cache Write', 'keyUsage.cost': 'Cost', 'keyUsage.quotaMode': 'Key Quota Mode', 'keyUsage.walletBalance': 'Wallet Balance', 'keyUsage.totalQuota': 'Total Quota', 'keyUsage.limit5h': '5-Hour Limit', 'keyUsage.limitDaily': 'Daily Limit', 'keyUsage.limit7d': '7-Day Limit', 'keyUsage.limitWeekly': 'Weekly Limit', 'keyUsage.limitMonthly': 'Monthly Limit', 'keyUsage.remainingQuota': 'Remaining Quota', 'keyUsage.usedQuota': 'Used Quota', 'keyUsage.subscriptionType': 'Subscription Type', 'keyUsage.todayRequests': 'Today Requests', 'keyUsage.todayInputTokens': 'Today Input', 'keyUsage.todayOutputTokens': 'Today Output', 'keyUsage.todayTokens': 'Today Tokens', 'keyUsage.todayCacheCreation': 'Today Cache Creation', 'keyUsage.todayCacheRead': 'Today Cache Read', 'keyUsage.todayCost': 'Today Cost', 'keyUsage.rpmTpm': 'RPM / TPM', 'keyUsage.totalRequests': 'Total Requests', 'keyUsage.totalInputTokens': 'Total Input', 'keyUsage.totalOutputTokens': 'Total Output', 'keyUsage.totalTokensLabel': 'Total Tokens', 'keyUsage.totalCacheCreation': 'Total Cache Creation', 'keyUsage.totalCacheRead': 'Total Cache Read', 'keyUsage.totalCost': 'Total Cost', 'keyUsage.avgDuration': 'Avg Duration', 'keyUsage.querySuccess': 'Query successful', 'keyUsage.queryFailed': 'Query failed', 'keyUsage.queryFailedRetry': 'Query failed, please try again later', 'home.viewDocs': 'Docs', 'home.switchToLight': 'Light', 'home.switchToDark': 'Dark', 'home.footer.allRightsReserved': 'All rights reserved.', } vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: () => ({ t: (key: string) => messages[key] ?? key, locale: { value: 'en' }, }), } }) vi.mock('@/stores', () => ({ useAppStore: () => ({ cachedPublicSettings: null, siteName: 'Sub2API', siteLogo: '', docUrl: '', publicSettingsLoaded: true, fetchPublicSettings, showInfo, showSuccess, showError, }), })) describe('KeyUsageView daily detail', () => { beforeEach(() => { showInfo.mockReset() showSuccess.mockReset() showError.mockReset() fetchPublicSettings.mockReset() localStorage.clear() Object.defineProperty(window, 'matchMedia', { configurable: true, value: vi.fn().mockReturnValue({ matches: false }), }) vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => window.setTimeout(() => cb(0), 0)) vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => ({ mode: 'quota_limited', isValid: true, status: 'active', quota: { limit: 10, used: 1, remaining: 9, unit: 'USD', }, usage: { today: { requests: 1, input_tokens: 10, output_tokens: 20, cache_creation_tokens: 0, cache_read_tokens: 0, total_tokens: 30, actual_cost: 0.01, }, total: { requests: 12, input_tokens: 100, output_tokens: 200, cache_creation_tokens: 10, cache_read_tokens: 30, total_tokens: 340, actual_cost: 0.12, }, rpm: 0, tpm: 0, }, daily_usage: [ { date: '2026-05-19', requests: 12, input_tokens: 100, output_tokens: 200, cache_read_tokens: 30, cache_write_tokens: 10, total_tokens: 340, cost: 0.15, actual_cost: 0.12, }, ], }), })) }) afterEach(() => { vi.unstubAllGlobals() }) it('renders daily usage detail rows after a successful query', async () => { const wrapper = mount(KeyUsageView, { global: { stubs: { RouterLink: { template: '' }, LocaleSwitcher: true, Icon: true, }, }, }) await wrapper.find('input').setValue('sk-test-key') await wrapper.find('input').trigger('keydown.enter') await flushPromises() await nextTick() const fetchMock = vi.mocked(fetch) expect(fetchMock).toHaveBeenCalledWith( expect.stringContaining('/v1/usage?'), expect.objectContaining({ headers: { Authorization: 'Bearer sk-test-key' }, }) ) expect(String(fetchMock.mock.calls[0][0])).toContain('days=30') const text = wrapper.text() expect(text).toContain('Daily Detail') expect(text).toContain('Date') expect(text).toContain('Cache Read') expect(text).toContain('Cache Write') expect(text).toContain('2026-05-19') expect(text).toContain('12') expect(text).toContain('100') expect(text).toContain('200') expect(text).toContain('30') expect(text).toContain('10') expect(text).toContain('$0.12') wrapper.unmount() }) })