mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
2 changed files with 176 additions and 0 deletions
@ -0,0 +1,126 @@ |
|||||
|
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; |
||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; |
||||
|
|
||||
|
import { eachDayOfInterval } from 'date-fns'; |
||||
|
|
||||
|
import { ExchangeRateDataService } from './exchange-rate-data.service'; |
||||
|
|
||||
|
const startDate = new Date(Date.UTC(2022, 7, 9)); // 2022-08-09
|
||||
|
const endDate = new Date(Date.UTC(2022, 7, 18)); // 2022-08-18
|
||||
|
|
||||
|
// Build an ExchangeRateDataService whose market data has full USDEUR coverage and
|
||||
|
// the given USDBRL coverage, and no direct BRLEUR series (forcing the indirect
|
||||
|
// calculation that derives BRLEUR from USDBRL and USDEUR).
|
||||
|
function createService( |
||||
|
usdBrlMarketData: { date: Date; marketPrice: number }[] |
||||
|
) { |
||||
|
const marketDataService = { |
||||
|
getRange: jest.fn(async ({ assetProfileIdentifiers }) => { |
||||
|
const { symbol } = assetProfileIdentifiers[0]; |
||||
|
|
||||
|
if (symbol === 'USDBRL') { |
||||
|
return usdBrlMarketData; |
||||
|
} |
||||
|
|
||||
|
if (symbol === 'USDEUR') { |
||||
|
return eachDayOfInterval({ end: endDate, start: startDate }).map( |
||||
|
(date) => { |
||||
|
return { date, marketPrice: 0.98 }; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return []; |
||||
|
}) |
||||
|
} as unknown as MarketDataService; |
||||
|
|
||||
|
const dataProviderService = { |
||||
|
getDataSourceForExchangeRates: () => 'YAHOO' |
||||
|
} as unknown as DataProviderService; |
||||
|
|
||||
|
return new ExchangeRateDataService( |
||||
|
dataProviderService, |
||||
|
marketDataService, |
||||
|
null, |
||||
|
null |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
async function getBrlEurRates( |
||||
|
exchangeRateDataService: ExchangeRateDataService |
||||
|
) { |
||||
|
const result = await exchangeRateDataService.getExchangeRatesByCurrency({ |
||||
|
currencies: ['BRL'], |
||||
|
endDate, |
||||
|
startDate, |
||||
|
targetCurrency: 'EUR' |
||||
|
}); |
||||
|
|
||||
|
return result['BRLEUR']; |
||||
|
} |
||||
|
|
||||
|
describe('ExchangeRateDataService', () => { |
||||
|
describe('getExchangeRatesByCurrency (indirect calculation)', () => { |
||||
|
it('back-fills a leading gap (data gathered only from a later date)', async () => { |
||||
|
// USDBRL is gathered only from 2022-08-16, leaving 2022-08-09..2022-08-15
|
||||
|
// uncovered — the case that produced the reported error.
|
||||
|
const exchangeRateDataService = createService([ |
||||
|
{ date: new Date(Date.UTC(2022, 7, 16)), marketPrice: 5.0956 }, |
||||
|
{ date: new Date(Date.UTC(2022, 7, 17)), marketPrice: 5.1417 }, |
||||
|
{ date: new Date(Date.UTC(2022, 7, 18)), marketPrice: 5.165 } |
||||
|
]); |
||||
|
|
||||
|
const errorSpy = jest |
||||
|
.spyOn((exchangeRateDataService as any).logger, 'error') |
||||
|
.mockImplementation(() => undefined); |
||||
|
|
||||
|
const rates = await getBrlEurRates(exchangeRateDataService); |
||||
|
|
||||
|
expect(errorSpy).not.toHaveBeenCalled(); |
||||
|
expect(Object.values(rates).length).toBeGreaterThan(0); |
||||
|
expect(Object.values(rates).every((rate) => Number.isFinite(rate))).toBe( |
||||
|
true |
||||
|
); |
||||
|
|
||||
|
errorSpy.mockRestore(); |
||||
|
}); |
||||
|
|
||||
|
it('carries forward across a trailing gap (latest dates not yet gathered)', async () => { |
||||
|
// USDBRL stops at 2022-08-11, leaving 2022-08-12..2022-08-18 uncovered.
|
||||
|
const exchangeRateDataService = createService([ |
||||
|
{ date: new Date(Date.UTC(2022, 7, 9)), marketPrice: 5.12 }, |
||||
|
{ date: new Date(Date.UTC(2022, 7, 10)), marketPrice: 5.1 }, |
||||
|
{ date: new Date(Date.UTC(2022, 7, 11)), marketPrice: 5.15 } |
||||
|
]); |
||||
|
|
||||
|
const errorSpy = jest |
||||
|
.spyOn((exchangeRateDataService as any).logger, 'error') |
||||
|
.mockImplementation(() => undefined); |
||||
|
|
||||
|
const rates = await getBrlEurRates(exchangeRateDataService); |
||||
|
|
||||
|
expect(errorSpy).not.toHaveBeenCalled(); |
||||
|
expect(Object.values(rates).every((rate) => Number.isFinite(rate))).toBe( |
||||
|
true |
||||
|
); |
||||
|
|
||||
|
errorSpy.mockRestore(); |
||||
|
}); |
||||
|
|
||||
|
it('still reports an error when the pair has no data at all', async () => { |
||||
|
// Nothing to carry from — the genuine "please provide market data" case
|
||||
|
// must keep logging, so the fix stays surgical.
|
||||
|
const exchangeRateDataService = createService([]); |
||||
|
|
||||
|
const errorSpy = jest |
||||
|
.spyOn((exchangeRateDataService as any).logger, 'error') |
||||
|
.mockImplementation(() => undefined); |
||||
|
|
||||
|
await getBrlEurRates(exchangeRateDataService); |
||||
|
|
||||
|
expect(errorSpy).toHaveBeenCalled(); |
||||
|
|
||||
|
errorSpy.mockRestore(); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
Loading…
Reference in new issue