diff --git a/__tests__/shared/services/money.js b/__tests__/shared/services/money.js new file mode 100644 index 000000000..9883d9abd --- /dev/null +++ b/__tests__/shared/services/money.js @@ -0,0 +1,37 @@ +/* eslint-env jest */ + +jest.mock('isomorphic-fetch', () => jest.fn()); +jest.mock('money', () => ({ + convert: jest.fn(() => 42), +})); +jest.mock('topcoder-react-utils', () => ({ + config: { + CDN: { PUBLIC: 'https://cdn.example.com' }, + }, + isomorphy: { + isClientSide: () => false, + }, +})); + +const fetch = require('isomorphic-fetch'); +const fx = require('money'); + +fetch.mockResolvedValue({ + ok: false, + statusText: 'Internal Server Error', +}); + +const money = jest.requireActual('../../../src/shared/services/money'); + +describe('money service', () => { + test('keeps background refresh failures from becoming unhandled rejections', async () => { + expect(money.getRatesNow()).toEqual({ timestamp: 0 }); + expect(money.convertNow(10, 'EUR')).toBe(42); + + await Promise.resolve(); + await Promise.resolve(); + + expect(fetch).toHaveBeenCalledTimes(3); + expect(fx.convert).toHaveBeenCalledWith(10, { from: 'USD', to: 'EUR' }); + }); +}); diff --git a/src/shared/services/money.js b/src/shared/services/money.js index 996ec4634..c0d10ddca 100644 --- a/src/shared/services/money.js +++ b/src/shared/services/money.js @@ -36,12 +36,20 @@ async function updateCache() { fx.rates = cache.rates; } -try { - updateCache(); -} catch (error) { - // exchange-rates failed, reason: socket hang up +/** + * Starts a cache refresh without blocking synchronous callers. + * + * Used during module initialization and by the synchronous public methods. + * Refresh failures are consumed so existing cached rates remain available + * instead of creating an unhandled Promise rejection. + * @return {void} + */ +function refreshCacheInBackground() { + updateCache().catch(_.noop); } +refreshCacheInBackground(); + /** * Converts specified amount of money to another currency. * @param {Number} amount Amount of money to convert. @@ -61,21 +69,16 @@ export async function convert(amount, to, from = 'USD') { } /** - * Same as convert(..), but works syncroneously (using cached rates). - * This function still triggers refreshement of the cached rates if necessary, - * but it does not wait for the result, and just uses cached rates for the - * actual conversion. It is safe to use anyway - * @param {Number} amount - * @param {String} to - * @param {String} from - * @return {Number} + * Converts an amount synchronously using cached rates while triggering a + * non-blocking refresh when the cache is stale. Refresh failures are ignored + * and leave the existing cache in place. + * @param {Number} amount Amount of money to convert. + * @param {String} to Target currency (3-letter code such as USD or EUR). + * @param {String} from Original currency. Defaults to USD. + * @return {Number} Converted amount. */ export function convertNow(amount, to, from = 'USD') { - try { - updateCache(); - } catch (error) { - // exchange-rates failed, reason: socket hang up - } + refreshCacheInBackground(); return fx.convert(amount, { from, to }); } @@ -93,14 +96,11 @@ export async function getRates() { } /** - * Same as getRates(..) but works syncroneously, using the cached rates. - * @return {Promise} + * Returns cached exchange rates synchronously while triggering a non-blocking + * refresh when the cache is stale. Refresh failures leave the cache unchanged. + * @return {Object} A clone of the cached exchange-rate data. */ export function getRatesNow() { - try { - updateCache(); - } catch (error) { - // exchange-rates failed, reason: socket hang up - } + refreshCacheInBackground(); return _.cloneDeep(cache); }