mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
Balance sheet totals and accountable type summaries used a SQL JOIN on exchange_rates matching only today's date, which returned NULL (defaulting to 1:1) when no rate existed for that exact date. This caused foreign currency accounts to show incorrect totals. Changes: - Refactor BalanceSheet::AccountTotals to batch-fetch exchange rates via ExchangeRate.rates_for, with provider fallback, instead of a SQL join - Refactor Accountable.balance_money to use the same batch approach - Add ExchangeRate.rates_for helper for deduplicated rate lookups - Fix net worth chart query to fall back to the nearest future rate when no historical rate exists for a given date - Add composite index on accounts (family_id, status, accountable_type) - Reuse nearest cached exchange rate within a 5-day lookback window before calling the provider, preventing redundant API calls on weekends and holidays when providers return prior-day rates https://claude.ai/code/session_01GyssBJxQqdWnuYofQRjUu8 Co-authored-by: Claude <noreply@anthropic.com>
99 lines
3.1 KiB
Ruby
99 lines
3.1 KiB
Ruby
require "test_helper"
|
|
require "ostruct"
|
|
|
|
class ExchangeRateTest < ActiveSupport::TestCase
|
|
include ProviderTestHelper
|
|
|
|
setup do
|
|
@provider = mock
|
|
|
|
ExchangeRate.stubs(:provider).returns(@provider)
|
|
end
|
|
|
|
test "finds rate in DB" do
|
|
existing_rate = exchange_rates(:one)
|
|
|
|
@provider.expects(:fetch_exchange_rate).never
|
|
|
|
assert_equal existing_rate, ExchangeRate.find_or_fetch_rate(
|
|
from: existing_rate.from_currency,
|
|
to: existing_rate.to_currency,
|
|
date: existing_rate.date
|
|
)
|
|
end
|
|
|
|
test "fetches rate from provider without cache" do
|
|
ExchangeRate.delete_all
|
|
|
|
provider_response = provider_success_response(
|
|
OpenStruct.new(
|
|
from: "USD",
|
|
to: "EUR",
|
|
date: Date.current,
|
|
rate: 1.2
|
|
)
|
|
)
|
|
|
|
@provider.expects(:fetch_exchange_rate).returns(provider_response)
|
|
|
|
assert_no_difference "ExchangeRate.count" do
|
|
assert_equal 1.2, ExchangeRate.find_or_fetch_rate(from: "USD", to: "EUR", date: Date.current, cache: false).rate
|
|
end
|
|
end
|
|
|
|
test "fetches rate from provider with cache" do
|
|
ExchangeRate.delete_all
|
|
|
|
provider_response = provider_success_response(
|
|
OpenStruct.new(
|
|
from: "USD",
|
|
to: "EUR",
|
|
date: Date.current,
|
|
rate: 1.2
|
|
)
|
|
)
|
|
|
|
@provider.expects(:fetch_exchange_rate).returns(provider_response)
|
|
|
|
assert_difference "ExchangeRate.count", 1 do
|
|
assert_equal 1.2, ExchangeRate.find_or_fetch_rate(from: "USD", to: "EUR", date: Date.current, cache: true).rate
|
|
end
|
|
end
|
|
|
|
test "returns nil on provider error" do
|
|
provider_response = provider_error_response(StandardError.new("Test error"))
|
|
|
|
@provider.expects(:fetch_exchange_rate).returns(provider_response)
|
|
|
|
assert_nil ExchangeRate.find_or_fetch_rate(from: "USD", to: "EUR", date: Date.current, cache: true)
|
|
end
|
|
|
|
test "reuses nearest cached rate within lookback window instead of calling provider" do
|
|
# Simulate a rate saved under Friday's date when Saturday is requested
|
|
friday = 1.day.ago.to_date
|
|
ExchangeRate.create!(from_currency: "USD", to_currency: "JPY", date: friday, rate: 150.5)
|
|
|
|
saturday = Date.current
|
|
|
|
@provider.expects(:fetch_exchange_rate).never
|
|
|
|
result = ExchangeRate.find_or_fetch_rate(from: "USD", to: "JPY", date: saturday)
|
|
assert_equal 150.5, result.rate
|
|
assert_equal friday, result.date
|
|
end
|
|
|
|
test "does not reuse cached rate outside lookback window" do
|
|
old_date = (ExchangeRate::NEAREST_RATE_LOOKBACK_DAYS + 1).days.ago.to_date
|
|
ExchangeRate.create!(from_currency: "USD", to_currency: "JPY", date: old_date, rate: 140.0)
|
|
|
|
provider_response = provider_success_response(
|
|
OpenStruct.new(from: "USD", to: "JPY", date: Date.current, rate: 155.0)
|
|
)
|
|
|
|
@provider.expects(:fetch_exchange_rate).returns(provider_response)
|
|
|
|
result = ExchangeRate.find_or_fetch_rate(from: "USD", to: "JPY", date: Date.current)
|
|
assert_equal 155.0, result.rate
|
|
end
|
|
end
|