Files
sure/test/models/exchange_rate/importer_test.rb
soky srm be42988adf Add throttling and cross-rate for twelve data (#1396)
* Add throttling and cross-rate for twelve data

* FIX yahoo precision also

* FIXES

* Update importer.rb

* Fixes

* Revert job

* Fixes
2026-04-07 20:46:05 +02:00

199 lines
6.7 KiB
Ruby

require "test_helper"
require "ostruct"
class ExchangeRate::ImporterTest < ActiveSupport::TestCase
include ProviderTestHelper
setup do
@provider = mock
end
test "syncs missing rates from provider" do
ExchangeRate.delete_all
provider_response = provider_success_response([
OpenStruct.new(from: "USD", to: "EUR", date: 2.days.ago.to_date, rate: 1.3),
OpenStruct.new(from: "USD", to: "EUR", date: 1.day.ago.to_date, rate: 1.4),
OpenStruct.new(from: "USD", to: "EUR", date: Date.current, rate: 1.5)
])
@provider.expects(:fetch_exchange_rates)
.with(from: "USD", to: "EUR", start_date: get_provider_fetch_start_date(2.days.ago.to_date), end_date: Date.current)
.returns(provider_response)
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: 2.days.ago.to_date,
end_date: Date.current
).import_provider_rates
db_rates = ExchangeRate.where(from_currency: "USD", to_currency: "EUR", date: 2.days.ago.to_date..Date.current)
.order(:date)
assert_equal 3, db_rates.count
assert_equal 1.3, db_rates[0].rate
assert_equal 1.4, db_rates[1].rate
assert_equal 1.5, db_rates[2].rate
end
test "syncs diff when some rates already exist" do
ExchangeRate.delete_all
# Pre-populate DB with the first two days
ExchangeRate.create!(from_currency: "USD", to_currency: "EUR", date: 3.days.ago.to_date, rate: 1.2)
ExchangeRate.create!(from_currency: "USD", to_currency: "EUR", date: 2.days.ago.to_date, rate: 1.25)
provider_response = provider_success_response([
OpenStruct.new(from: "USD", to: "EUR", date: 1.day.ago.to_date, rate: 1.3)
])
@provider.expects(:fetch_exchange_rates)
.with(from: "USD", to: "EUR", start_date: get_provider_fetch_start_date(1.day.ago.to_date), end_date: Date.current)
.returns(provider_response)
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: 3.days.ago.to_date,
end_date: Date.current
).import_provider_rates
forward_rates = ExchangeRate.where(from_currency: "USD", to_currency: "EUR").order(:date)
assert_equal 4, forward_rates.count
assert_equal [ 1.2, 1.25, 1.3, 1.3 ], forward_rates.map(&:rate)
end
test "no provider calls when all rates exist" do
ExchangeRate.delete_all
(3.days.ago.to_date..Date.current).each_with_index do |date, idx|
ExchangeRate.create!(from_currency: "USD", to_currency: "EUR", date:, rate: 1.2 + idx * 0.01)
end
@provider.expects(:fetch_exchange_rates).never
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: 3.days.ago.to_date,
end_date: Date.current
).import_provider_rates
end
# A helpful "reset" option for when we need to refresh provider data
test "full upsert if clear_cache is true" do
ExchangeRate.delete_all
# Seed DB with stale data
(2.days.ago.to_date..Date.current).each do |date|
ExchangeRate.create!(from_currency: "USD", to_currency: "EUR", date:, rate: 1.0)
end
provider_response = provider_success_response([
OpenStruct.new(from: "USD", to: "EUR", date: 2.days.ago.to_date, rate: 1.3),
OpenStruct.new(from: "USD", to: "EUR", date: 1.day.ago.to_date, rate: 1.4),
OpenStruct.new(from: "USD", to: "EUR", date: Date.current, rate: 1.5)
])
@provider.expects(:fetch_exchange_rates)
.with(from: "USD", to: "EUR", start_date: get_provider_fetch_start_date(2.days.ago.to_date), end_date: Date.current)
.returns(provider_response)
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: 2.days.ago.to_date,
end_date: Date.current,
clear_cache: true
).import_provider_rates
db_rates = ExchangeRate.where(from_currency: "USD", to_currency: "EUR").order(:date)
assert_equal [ 1.3, 1.4, 1.5 ], db_rates.map(&:rate)
end
test "clamps end_date to today when future date is provided" do
ExchangeRate.delete_all
future_date = Date.current + 3.days
provider_response = provider_success_response([
OpenStruct.new(from: "USD", to: "EUR", date: Date.current, rate: 1.6)
])
@provider.expects(:fetch_exchange_rates)
.with(from: "USD", to: "EUR", start_date: get_provider_fetch_start_date(Date.current), end_date: Date.current)
.returns(provider_response)
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: Date.current,
end_date: future_date
).import_provider_rates
# 1 forward rate + 1 inverse rate
assert_equal 2, ExchangeRate.count
end
test "upserts inverse rates alongside forward rates" do
ExchangeRate.delete_all
provider_response = provider_success_response([
OpenStruct.new(from: "USD", to: "EUR", date: Date.current, rate: 0.85)
])
@provider.expects(:fetch_exchange_rates)
.with(from: "USD", to: "EUR", start_date: get_provider_fetch_start_date(Date.current), end_date: Date.current)
.returns(provider_response)
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: Date.current,
end_date: Date.current
).import_provider_rates
forward = ExchangeRate.find_by(from_currency: "USD", to_currency: "EUR", date: Date.current)
inverse = ExchangeRate.find_by(from_currency: "EUR", to_currency: "USD", date: Date.current)
assert_not_nil forward, "Forward rate should be stored"
assert_not_nil inverse, "Inverse rate should be computed and stored"
assert_in_delta 0.85, forward.rate.to_f, 0.0001
assert_in_delta (1.0 / 0.85), inverse.rate.to_f, 0.0001
end
test "handles rate limit error gracefully" do
ExchangeRate.delete_all
rate_limit_error = Provider::TwelveData::RateLimitError.new("Rate limit exceeded")
@provider.expects(:fetch_exchange_rates).once.returns(
provider_error_response(rate_limit_error)
)
# Should not raise — logs warning and returns without importing
ExchangeRate::Importer.new(
exchange_rate_provider: @provider,
from: "USD",
to: "EUR",
start_date: Date.current,
end_date: Date.current
).import_provider_rates
assert_equal 0, ExchangeRate.count, "No rates should be imported on rate limit error"
end
private
def get_provider_fetch_start_date(start_date)
# We fetch with a 5 day buffer to account for weekends and holidays
start_date - 5.days
end
end