diff --git a/app/apps/currencies/exchange_rates/providers.py b/app/apps/currencies/exchange_rates/providers.py index a1ef9211..77c86e13 100644 --- a/app/apps/currencies/exchange_rates/providers.py +++ b/app/apps/currencies/exchange_rates/providers.py @@ -739,10 +739,17 @@ class AllRatesTodayProvider(ExchangeRateProvider): """Reduce the table to one rate per pair, in both directions""" grouped = {} for row in rows: - if not isinstance(row, dict) or not row.get("value"): + if not isinstance(row, dict): + continue + try: + value = Decimal(str(row.get("value"))) + except ArithmeticError: + continue + # A bad cell skips its own row, not the whole table + if not value.is_finite() or value <= 0: continue pair = (row.get("base"), row.get("quote")) - grouped.setdefault(pair, {})[row.get("type")] = Decimal(str(row["value"])) + grouped.setdefault(pair, {})[row.get("type")] = value rates = {} for (base, quote), values in grouped.items(): @@ -756,11 +763,22 @@ class AllRatesTodayProvider(ExchangeRateProvider): ), None, ) - if rate is None or rate <= 0: + if rate is None: + logger.debug( + f"AllRatesToday {self.bank}: no usable rate type for " + f"{base}/{quote}, got {sorted(map(str, values))}" + ) continue rates[(base, quote)] = rate rates.setdefault((quote, base), self.invert_rate(rate)) + if grouped and not rates: + types = sorted({str(t) for values in grouped.values() for t in values}) + logger.warning( + f"AllRatesToday table for {self.bank} has no usable rate types, " + f"got {types}" + ) + return rates @staticmethod diff --git a/app/apps/currencies/tests/test_allratestoday_provider.py b/app/apps/currencies/tests/test_allratestoday_provider.py index 424dd9bb..151ef94b 100644 --- a/app/apps/currencies/tests/test_allratestoday_provider.py +++ b/app/apps/currencies/tests/test_allratestoday_provider.py @@ -155,13 +155,35 @@ class AllRatesTodayProviderTests(TestCase): self.assertEqual(self._get_rates(rows, self.brl, self.usd), []) + def test_logs_a_table_without_usable_types(self): + self.usd.exchange_currency = self.brl + payload = {"rates": [_row("USD", "BRL", 5, "monthly_average")]} + + with patch.object( + self.provider.session, "get", return_value=_response(payload) + ): + with self.assertLogs( + "apps.currencies.exchange_rates.providers", level="WARNING" + ) as logs: + self.provider.get_rates([self.usd], {self.brl}) + + self.assertIn("monthly_average", logs.output[0]) + def test_skips_pair_missing_from_the_table(self): rows = [_row("USD", "BRL", 5)] self.assertEqual(self._get_rates(rows, self.eur, self.usd), []) def test_ignores_zero_and_malformed_rows(self): - rows = ["nope", _row("USD", "BRL", 0), _row("EUR", "BRL", 6)] + rows = [ + "nope", + _row("USD", "BRL", 0), + _row("USD", "BRL", "5,4"), + _row("USD", "BRL", None), + _row("USD", "BRL", "Infinity"), + _row("USD", "BRL", "NaN"), + _row("EUR", "BRL", 6), + ] rates = self._get_rates(rows, self.brl, self.eur)