fix(usage): export missing reasoning effort cleanly

This commit is contained in:
wucm667
2026-09-21 12:41:01 +08:00
parent 7c700729c2
commit 386f321d3c
2 changed files with 35 additions and 1 deletions
+1
View File
@@ -626,6 +626,7 @@ const escapeCSVValue = (value: unknown): string => {
if (value == null) return ''
const str = String(value)
const escaped = str.replace(/"/g, '""')
if (str === '-') return str
if (/^[=+\-@\t\r]/.test(str)) return `"\'${escaped}"`
if (/[,"\n\r]/.test(str)) return `"${escaped}"`
return str
@@ -399,7 +399,7 @@ describe('user UsageView', () => {
expect(csvContent.startsWith('\uFEFF')).toBe(true)
expect(csvContent.slice(1)).toBe([
'Time,API Key Name,Model,Reasoning Effort,Inbound Endpoint,IP Address,Type,Billing Mode,Input Tokens,Output Tokens,Cache Read Tokens,Cache Creation Tokens,Rate Multiplier,Billed Cost,Original Cost,First Token (ms),Duration (ms)',
'2026-03-08T00:00:00Z,demo-key,gpt-5.4,"\'-",,203.0.113.10,Sync,Token,4057,101,278272,4,1,0.09288300,0.09288300,12,345',
'2026-03-08T00:00:00Z,demo-key,gpt-5.4,-,,203.0.113.10,Sync,Token,4057,101,278272,4,1,0.09288300,0.09288300,12,345',
].join('\n'))
expect(csvContent).toContain('IP Address')
expect(csvContent).toContain('203.0.113.10')
@@ -415,6 +415,39 @@ describe('user UsageView', () => {
clickSpy.mockRestore()
})
it('keeps formula-injection protection for dangerous exported values', async () => {
query.mockResolvedValue({
items: [{ ...usageLog, api_key: { name: '-1+1' } }],
total: 1,
pages: 1,
})
const wrapper = mountUsageView()
await flushPromises()
let csvContent = ''
const OriginalBlob = globalThis.Blob
vi.stubGlobal('Blob', vi.fn((parts: BlobPart[], options?: BlobPropertyBag) => {
csvContent = parts.map((part) => String(part)).join('')
return new OriginalBlob(parts, options)
}))
const originalCreateObjectURL = window.URL.createObjectURL
const originalRevokeObjectURL = window.URL.revokeObjectURL
window.URL.createObjectURL = vi.fn(() => 'blob:usage-export') as typeof window.URL.createObjectURL
window.URL.revokeObjectURL = vi.fn(() => {}) as typeof window.URL.revokeObjectURL
const clickSpy = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
await (wrapper.vm as any).exportToCSV()
expect(csvContent).toContain(',"\'-1+1",gpt-5.4,-,')
expect(showSuccess).toHaveBeenCalled()
window.URL.createObjectURL = originalCreateObjectURL
window.URL.revokeObjectURL = originalRevokeObjectURL
vi.unstubAllGlobals()
clickSpy.mockRestore()
wrapper.unmount()
})
it('keeps the initial filters, sort, and filename while exporting multiple pages', async () => {
const pageResponse = { items: [usageLog], total: 101, pages: 2 }
query.mockResolvedValue(pageResponse)