mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-10-27 09:11:45 +00:00
When the Frankfurter fetch failed (or under CODEBURN_FX_NO_FETCH) with no fresh cache, the rate fell back to 1 but the foreign code and symbol stayed, so USD amounts showed as EUR/JPY. Now the last cached rate of any age is used, and with none the display falls back to USD code and symbol.
67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
import { mkdtemp, mkdir, rm, writeFile } from 'fs/promises'
|
|
import { tmpdir } from 'os'
|
|
import { join } from 'path'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { formatCost, getCurrency, loadCurrency, switchCurrency } from '../src/currency.js'
|
|
|
|
const DAY_MS = 24 * 60 * 60 * 1000
|
|
let dir: string
|
|
|
|
async function seedRate(code: string, rate: number, ageMs: number): Promise<void> {
|
|
await writeFile(join(dir, 'cache', 'exchange-rate.json'), JSON.stringify({ timestamp: Date.now() - ageMs, code, rate }))
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
dir = await mkdtemp(join(tmpdir(), 'codeburn-fx-'))
|
|
await mkdir(join(dir, 'cache'), { recursive: true })
|
|
process.env['CODEBURN_CACHE_DIR'] = join(dir, 'cache')
|
|
process.env['HOME'] = dir
|
|
})
|
|
|
|
afterEach(async () => {
|
|
vi.unstubAllGlobals()
|
|
await switchCurrency('USD')
|
|
await rm(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('FX fallback with no fresh rate', () => {
|
|
it('shows USD code and symbol when fetching is off and nothing is cached', async () => {
|
|
await switchCurrency('EUR')
|
|
expect(getCurrency()).toEqual({ code: 'USD', rate: 1, symbol: '$' })
|
|
expect(formatCost(2)).toBe('$2.00')
|
|
})
|
|
|
|
it('uses a stale cached rate when fetching is off', async () => {
|
|
await seedRate('EUR', 0.9, 30 * DAY_MS)
|
|
await switchCurrency('EUR')
|
|
expect(getCurrency()).toMatchObject({ code: 'EUR', rate: 0.9 })
|
|
})
|
|
|
|
it('ignores a cached rate for another currency', async () => {
|
|
await seedRate('GBP', 0.8, 30 * DAY_MS)
|
|
await switchCurrency('EUR')
|
|
expect(getCurrency().code).toBe('USD')
|
|
})
|
|
|
|
describe('when the fetch fails', () => {
|
|
beforeEach(() => {
|
|
delete process.env['CODEBURN_FX_NO_FETCH']
|
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('offline')))
|
|
})
|
|
|
|
it('uses the last cached rate whatever its age', async () => {
|
|
await seedRate('JPY', 150, 400 * DAY_MS)
|
|
await switchCurrency('JPY')
|
|
expect(getCurrency()).toMatchObject({ code: 'JPY', rate: 150, symbol: '¥' })
|
|
})
|
|
|
|
it('falls back to USD from config when nothing is cached', async () => {
|
|
await mkdir(join(dir, '.config', 'codeburn'), { recursive: true })
|
|
await writeFile(join(dir, '.config', 'codeburn', 'config.json'), JSON.stringify({ currency: { code: 'EUR', symbol: 'E' } }))
|
|
await loadCurrency()
|
|
expect(getCurrency()).toEqual({ code: 'USD', rate: 1, symbol: '$' })
|
|
expect(formatCost(2)).toBe('$2.00')
|
|
})
|
|
})
|
|
})
|