Files
minibits_wallet/__tests__/mintInfoStale.test.ts
minibits-cash be6dc69224 Fix stale mint capabilities hiding the onchain topup option
Reported from device: with an 11k sat amount on a mint that advertises onchain
and NUT-20, the "Bitcoin address" button never appeared.

Two causes, both mine.

1. A missing `time` was being read as FRESH. The stamp only started being
   written when the capability model landed (d008d45), so every mint info
   cached before that has `time: undefined`. `now - undefined` is NaN, and
   every comparison against NaN is false — so `now - time > TTL` reported
   those records as fresh and never refetched them. A mint that had since
   gained onchain support kept looking like one that never had it, forever.

   isMintInfoStale() now treats a missing or non-finite timestamp as stale, and
   lives in its own module (no stores, no services) so the NaN behaviour is
   pinned by tests rather than rediscovered on a device.

2. Nothing on TopupScreen ever refreshed capabilities. The screen gates on the
   mint's cached NUT-06 info but otherwise never talks to the mint, so even
   with (1) fixed there was no trigger. Selecting a mint now kicks getMint(),
   which only hits the network when the info is genuinely stale; the screen is
   an observer, so the option appears by itself once fresher capabilities land.

Also logs the gate inputs (supportsOnchain / nut20 / mintMin / floor / amount),
since "the button is missing" is otherwise indistinguishable from a dozen
different causes.

260/260 tests pass; typecheck introduces no new errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 21:56:37 +02:00

51 lines
1.8 KiB
TypeScript

/**
* Mint info staleness (WalletStore.isMintInfoStale).
*
* The reason this is its own test: a MISSING `time` must count as STALE.
*
* The stamp was only added when the capability model landed, so every mint info
* cached before that has `time: undefined`. `now - undefined` is NaN, and every
* comparison against NaN is false — so the obvious `now - time > TTL` check reports
* those records as FRESH, forever. A mint that has since gained onchain support then
* keeps looking like a mint that never had it, and the wallet quietly refuses to
* offer a feature the mint supports.
*
* That is not hypothetical: it is exactly what happened on device.
*
* @jest-environment node
*/
import {isMintInfoStale, MINT_INFO_TTL_SECONDS} from '../src/models/helpers/mintInfoStale'
const NOW = 1_800_000_000
const infoAt = (time?: number) => ({time} as any)
describe('isMintInfoStale', () => {
it('treats absent info as stale', () => {
expect(isMintInfoStale(undefined, NOW)).toBe(true)
})
it('treats info with NO timestamp as stale (the legacy-cache trap)', () => {
// `now - undefined` is NaN; `NaN > TTL` is false. A naive check calls this
// fresh and never refetches.
expect(isMintInfoStale(infoAt(undefined), NOW)).toBe(true)
})
it('treats a non-finite timestamp as stale', () => {
expect(isMintInfoStale(infoAt(NaN), NOW)).toBe(true)
})
it('is fresh within the TTL', () => {
expect(isMintInfoStale(infoAt(NOW - 60), NOW)).toBe(false)
expect(isMintInfoStale(infoAt(NOW), NOW)).toBe(false)
})
it('is stale past the TTL', () => {
expect(isMintInfoStale(infoAt(NOW - MINT_INFO_TTL_SECONDS - 1), NOW)).toBe(true)
})
it('is fresh exactly at the TTL boundary', () => {
expect(isMintInfoStale(infoAt(NOW - MINT_INFO_TTL_SECONDS), NOW)).toBe(false)
})
})