diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/rates-test.js | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/tools/rates-test.js b/tools/rates-test.js new file mode 100644 index 0000000..88119f3 --- /dev/null +++ b/tools/rates-test.js @@ -0,0 +1,69 @@ +// What a payment was worth WHEN IT HAPPENED. The price is sampled as the app +// runs and kept as a series; a payment's fiat value is read off the sample +// nearest its timestamp, and a payment older than anything we sampled is +// priced at today's number and SAID to be — never quietly. +// +// Run: bun tools/rates-test.js +let fails = 0; +const check = (name, ok, detail = '') => { + console.log(` ${ok ? '✓' : '✗'} ${name}${detail ? ' — ' + String(detail).slice(0, 90) : ''}`); + if (!ok) fails++; +}; +globalThis.localStorage = { + _d: {}, getItem(k) { return this._d[k] ?? null; }, setItem(k, v) { this._d[k] = String(v); }, + removeItem(k) { delete this._d[k]; }, key(i) { return Object.keys(this._d)[i] ?? null; }, + get length() { return Object.keys(this._d).length; }, +}; +const HOUR = 3600; +const now = Math.floor(Date.now() / 1000); +// a week of hourly samples, climbing from 50k to 100k +const series = []; +for (let i = 7 * 24; i >= 0; i--) series.push([now - i * HOUR, 50_000 + (7 * 24 - i) * 300]); +localStorage.setItem('btc-wallet-rate-series', JSON.stringify(series)); +localStorage.setItem('btc-wallet-rates', JSON.stringify({ USD: series[series.length - 1][1], EUR: 1234 })); + +const { rateAt, rateNow, fmtFiat, getCurrency, setCurrency, currencies, RATE_TOLERANCE } = await import('../src/rates.js'); +const { parseAmount, SATS } = await import('../src/format.js'); + +check('the live price is the newest sample', rateNow() === series[series.length - 1][1], String(rateNow())); + +// a payment three days ago is priced at the price three days ago +const threeDays = now - 3 * 24 * HOUR; +const then = rateAt(threeDays); +const want = series.find(([ts]) => ts === threeDays)[1]; +check('a payment is priced at the moment it happened', then.rate === want && !then.stale, `${then.rate} vs ${want}`); +check("...which isn't today's price", then.rate !== rateNow(), `${then.rate} vs ${rateNow()}`); + +// a payment between samples takes the nearest one +const between = rateAt(threeDays + 900); +check('between samples, the nearest one wins', between.rate === want && !between.stale, String(between.rate)); + +// older than anything we have: today's price, flagged +const ancient = rateAt(now - 400 * 24 * HOUR); +check('an older payment than we have samples for is flagged', ancient.stale === true && ancient.rate === rateNow(), JSON.stringify(ancient)); +check('...and the tolerance is a day', RATE_TOLERANCE === 24 * HOUR, String(RATE_TOLERANCE)); + +// no timestamp at all (a balance, a form) is simply the live price +check('an amount with no time is the live price', rateAt(null).rate === rateNow() && !rateAt(null).stale); + +// typing money +check('typing 5 with the unit on fiat buys five dollars of bitcoin', + parseAmount('5', 'fiat', 100_000) === Math.round((5 / 100_000) * SATS), String(parseAmount('5', 'fiat', 100_000))); +check('...and without a price it refuses rather than guessing', parseAmount('5', 'fiat', null) === null); +check('sats and BTC are untouched', parseAmount('2100', 'sats') === 2100 && parseAmount('0.001', 'btc') === 100_000); + +// formatting +check('an amount formats as money', /\$1[,.]/.test(fmtFiat(SATS / 100, 100_000)), fmtFiat(SATS / 100, 100_000)); +check('a small amount keeps more decimals', fmtFiat(100, 100_000).includes('0.10'), fmtFiat(100, 100_000)); +check('a currency Intl has never heard of still renders', /XYZ/.test(fmtFiat(SATS, 10, 'XYZ')), fmtFiat(SATS, 10, 'XYZ')); + +// changing currency drops the series rather than mislabelling it +check('the currency list comes from the rates map', currencies().includes('EUR'), currencies().join(',')); +setCurrency('EUR'); +check('changing currency starts the history again', JSON.parse(localStorage.getItem('btc-wallet-rate-series')).length <= 1, + localStorage.getItem('btc-wallet-rate-series').slice(0, 40)); +check('...and the live price follows it', rateNow() === 1234, String(rateNow())); +check('the choice sticks', getCurrency() === 'EUR'); + +console.log(fails ? `\n❌ ${fails} failed` : '\n✅ prices are remembered, not invented'); +process.exit(fails ? 1 : 0); |
