diff --git a/src/fx.js b/src/fx.js index dae9d0c..b183ec0 100644 --- a/src/fx.js +++ b/src/fx.js @@ -1 +1,12 @@ -// Currency conversion. (Not implemented yet.) +// Currency conversion. +import { fetchRate } from './providers/fx-provider.js'; +import { cached } from './lib/ttl-cache.js'; +import { roundCents } from './lib/money.js'; + +const RATE_TTL_MS = 5 * 60 * 1000; +const getRate = cached(fetchRate, { ttlMs: RATE_TTL_MS }); + +export async function convert(amountCents, from, to) { + const rate = await getRate(from, to); + return roundCents(amountCents * rate); +} diff --git a/tests/fx.test.js b/tests/fx.test.js new file mode 100644 index 0000000..139efb9 --- /dev/null +++ b/tests/fx.test.js @@ -0,0 +1,22 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { convert } from '../src/fx.js'; +import { fetchCount } from '../src/providers/fx-provider.js'; + +test('converts and rounds to whole cents', async () => { + assert.equal(await convert(10000, 'USD', 'EUR'), 9200); + assert.equal(await convert(1, 'USD', 'EUR'), 1); // 0.92 -> 1 + assert.equal(await convert(500, 'USD', 'USD'), 500); +}); + +test('caches rates between conversions', async () => { + await convert(100, 'USD', 'GBP'); + const before = fetchCount(); + await convert(200, 'USD', 'GBP'); + await convert(300, 'USD', 'GBP'); + assert.equal(fetchCount(), before); +}); + +test('propagates provider errors', async () => { + await assert.rejects(convert(100, 'USD', 'XXX'), /no rate/); +});