lorenzozane 3 days ago
committed by GitHub
parent
commit
4939064c65
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 82
      apps/api/src/services/exchange-rate-data/exchange-rate-data.regression.spec.ts
  2. 44
      apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

82
apps/api/src/services/exchange-rate-data/exchange-rate-data.regression.spec.ts

@ -0,0 +1,82 @@
import { ExchangeRateDataService } from './exchange-rate-data.service';
describe('ExchangeRateDataService regression #4299', () => {
let service: ExchangeRateDataService;
let mockDataProviderService: any;
let mockMarketDataService: any;
let mockPrismaService: any;
let mockPropertyService: any;
beforeEach(() => {
mockDataProviderService = {
getDataSourceForExchangeRates: jest.fn().mockReturnValue('YAHOO'),
getHistorical: jest.fn().mockResolvedValue({}),
getQuotes: jest.fn().mockResolvedValue({})
};
mockMarketDataService = {
get: jest.fn().mockResolvedValue(null),
getRange: jest.fn().mockResolvedValue([])
};
mockPrismaService = {
account: { findMany: jest.fn().mockResolvedValue([]) },
symbolProfile: { findMany: jest.fn().mockResolvedValue([]) }
};
mockPropertyService = {
getByKey: jest.fn().mockResolvedValue([])
};
service = new ExchangeRateDataService(
mockDataProviderService,
mockMarketDataService,
mockPrismaService,
mockPropertyService
);
// Ensure empty exchange rates to trigger missing-rate path
(service as any).exchangeRates = {};
(service as any).derivedCurrencyFactors = {};
});
it('toCurrency returns finite value when exchange rate is missing (CAD->USD)', () => {
const result = service.toCurrency(100, 'CAD', 'USD');
expect(Number.isFinite(result)).toBe(true);
expect(result).toBe(100);
expect(result).not.toBeNaN();
});
it('toCurrency handles missing indirect rates without NaN', () => {
(service as any).exchangeRates = {
'USDEUR': 0.9
// CADUSD missing, CAD->USD via USD should be missing
};
const result = service.toCurrency(100, 'CAD', 'USD');
expect(Number.isFinite(result)).toBe(true);
expect(result).toBe(100);
});
it('toCurrencyAtDate returns finite fallback when market data missing', async () => {
const pastDate = new Date('2025-01-15');
const result = await service.toCurrencyAtDate(100, 'CAD', 'USD', pastDate);
expect(Number.isFinite(result as number)).toBe(true);
expect(result).toBe(100);
expect(result).not.toBeUndefined();
});
it('toCurrencyAtDate returns finite for EUR->CAD missing rate', async () => {
const pastDate = new Date('2025-02-01');
mockMarketDataService.get.mockResolvedValue(null);
const result = await service.toCurrencyAtDate(250, 'EUR', 'CAD', pastDate);
expect(Number.isFinite(result as number)).toBe(true);
expect(result).toBe(250);
});
it('toCurrencyAtDate returns 0 for zero amount even with missing rate', async () => {
const result = await service.toCurrencyAtDate(0, 'CAD', 'USD', new Date('2025-01-01'));
expect(result).toBe(0);
});
it('toCurrency returns 0 for zero amount even with missing rate', () => {
const result = service.toCurrency(0, 'CAD', 'USD');
expect(result).toBe(0);
});
});

44
apps/api/src/services/exchange-rate-data/exchange-rate-data.service.ts

@ -234,17 +234,27 @@ export class ExchangeRateDataService {
if (!this.exchangeRates[symbol]) {
// Not found, calculate indirectly via base currency
this.exchangeRates[symbol] =
const price1 =
resultExtended[`${currency1}${DEFAULT_CURRENCY}`]?.[
dateStringOfYesterday
]?.marketPrice *
]?.marketPrice;
const price2 =
resultExtended[`${DEFAULT_CURRENCY}${currency2}`]?.[
dateStringOfYesterday
]?.marketPrice;
// Calculate the opposite direction
this.exchangeRates[`${currency2}${currency1}`] =
1 / this.exchangeRates[symbol];
if (isNumber(price1) && isNumber(price2)) {
this.exchangeRates[symbol] = price1 * price2;
if (
isNumber(this.exchangeRates[symbol]) &&
Number.isFinite(this.exchangeRates[symbol]) &&
this.exchangeRates[symbol] !== 0
) {
this.exchangeRates[`${currency2}${currency1}`] =
1 / this.exchangeRates[symbol];
}
}
}
}
}
@ -271,11 +281,13 @@ export class ExchangeRateDataService {
this.exchangeRates[`${aFromCurrency}${DEFAULT_CURRENCY}`];
const factor2 = this.exchangeRates[`${DEFAULT_CURRENCY}${aToCurrency}`];
factor = factor1 * factor2;
if (isNumber(factor1) && isNumber(factor2)) {
factor = factor1 * factor2;
}
}
}
if (isNumber(factor) && !isNaN(factor)) {
if (isNumber(factor) && Number.isFinite(factor) && !isNaN(factor)) {
return factor * aValue;
}
@ -357,13 +369,21 @@ export class ExchangeRateDataService {
} catch {}
// Calculate the opposite direction
factor =
(1 / marketPriceBaseCurrencyFromCurrency) *
marketPriceBaseCurrencyToCurrency;
if (
isNumber(marketPriceBaseCurrencyFromCurrency) &&
isNumber(marketPriceBaseCurrencyToCurrency) &&
Number.isFinite(marketPriceBaseCurrencyFromCurrency) &&
Number.isFinite(marketPriceBaseCurrencyToCurrency) &&
marketPriceBaseCurrencyFromCurrency !== 0
) {
factor =
(1 / marketPriceBaseCurrencyFromCurrency) *
marketPriceBaseCurrencyToCurrency;
}
}
}
if (isNumber(factor) && !isNaN(factor)) {
if (isNumber(factor) && Number.isFinite(factor) && !isNaN(factor)) {
return factor * aValue;
}
@ -374,7 +394,7 @@ export class ExchangeRateDataService {
)}`
);
return undefined;
return aValue;
}
private async getExchangeRates({

Loading…
Cancel
Save