1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-07 14:35:23 +02:00

Account::Sync model and test fixture simplifications (#968)

* Add sync model

* Fresh fixtures for sync tests

* Sync tests overhaul

* Fix entry tests

* Complete remaining model test updates

* Update system tests

* Update demo data task

* Add system tests back to PR checks

* More simplifications, add empty family to fixtures for easier testing
This commit is contained in:
Zach Gollwitzer 2024-07-10 11:22:59 -04:00 committed by GitHub
parent de5a2e55b3
commit c6bdf49f10
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 929 additions and 1353 deletions

View file

@ -1,101 +0,0 @@
require "test_helper"
require "csv"
class Account::Balance::CalculatorTest < ActiveSupport::TestCase
include FamilySnapshotTestHelper
test "syncs other asset balances" do
expected_balances = get_expected_balances_for(:collectable)
assert_account_balances calculated_balances_for(:collectable), expected_balances
end
test "syncs other liability balances" do
expected_balances = get_expected_balances_for(:iou)
assert_account_balances calculated_balances_for(:iou), expected_balances
end
test "syncs credit balances" do
expected_balances = get_expected_balances_for :credit_card
assert_account_balances calculated_balances_for(:credit_card), expected_balances
end
test "syncs checking account balances" do
expected_balances = get_expected_balances_for(:checking)
assert_account_balances calculated_balances_for(:checking), expected_balances
end
test "syncs foreign checking account balances" do
required_exchange_rates_for_sync = [
1.0834, 1.0845, 1.0819, 1.0872, 1.0788, 1.0743, 1.0755, 1.0774,
1.0778, 1.0783, 1.0773, 1.0709, 1.0729, 1.0773, 1.0778, 1.078,
1.0809, 1.0818, 1.0824, 1.0822, 1.0854, 1.0845, 1.0839, 1.0807,
1.084, 1.0856, 1.0858, 1.0898, 1.095, 1.094, 1.0926, 1.0986
]
required_exchange_rates_for_sync.each_with_index do |exchange_rate, idx|
ExchangeRate.create! date: idx.days.ago.to_date, from_currency: "EUR", to_currency: "USD", rate: exchange_rate
end
# Foreign accounts will generate balances for all currencies
expected_usd_balances = get_expected_balances_for(:eur_checking_usd)
expected_eur_balances = get_expected_balances_for(:eur_checking_eur)
calculated_balances = calculated_balances_for(:eur_checking)
calculated_usd_balances = calculated_balances.select { |b| b[:currency] == "USD" }
calculated_eur_balances = calculated_balances.select { |b| b[:currency] == "EUR" }
assert_account_balances calculated_usd_balances, expected_usd_balances
assert_account_balances calculated_eur_balances, expected_eur_balances
end
test "syncs multi-currency checking account balances" do
required_exchange_rates_for_sync = [
{ from_currency: "EUR", to_currency: "USD", date: 4.days.ago.to_date, rate: 1.0788 },
{ from_currency: "EUR", to_currency: "USD", date: 19.days.ago.to_date, rate: 1.0822 }
]
ExchangeRate.insert_all(required_exchange_rates_for_sync)
expected_balances = get_expected_balances_for(:multi_currency)
assert_account_balances calculated_balances_for(:multi_currency), expected_balances
end
test "syncs savings accounts balances" do
expected_balances = get_expected_balances_for(:savings)
assert_account_balances calculated_balances_for(:savings), expected_balances
end
test "syncs investment account balances" do
expected_balances = get_expected_balances_for(:brokerage)
assert_account_balances calculated_balances_for(:brokerage), expected_balances
end
test "syncs loan account balances" do
expected_balances = get_expected_balances_for(:mortgage_loan)
assert_account_balances calculated_balances_for(:mortgage_loan), expected_balances
end
test "syncs property account balances" do
expected_balances = get_expected_balances_for(:house)
assert_account_balances calculated_balances_for(:house), expected_balances
end
test "syncs vehicle account balances" do
expected_balances = get_expected_balances_for(:car)
assert_account_balances calculated_balances_for(:car), expected_balances
end
private
def assert_account_balances(actual_balances, expected_balances)
assert_equal expected_balances.count, actual_balances.count
actual_balances.each do |ab|
expected_balance = expected_balances.find { |eb| eb[:date] == ab[:date] }
assert_in_delta expected_balance[:balance], ab[:balance], 0.01, "Balance incorrect on date: #{ab[:date]}"
end
end
def calculated_balances_for(account_key)
Account::Balance::Calculator.new(accounts(account_key)).daily_balances
end
end

View file

@ -0,0 +1,133 @@
require "test_helper"
class Account::Balance::SyncerTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
setup do
@account = families(:empty).accounts.create!(name: "Test", balance: 20000, currency: "USD", accountable: Depository.new)
end
test "syncs account with no entries" do
assert_equal 0, @account.balances.count
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ @account.balance ], @account.balances.chronological.map(&:balance)
end
test "syncs account with valuations only" do
create_valuation(account: @account, date: 2.days.ago.to_date, amount: 22000)
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ 22000, 22000, @account.balance ], @account.balances.chronological.map(&:balance)
end
test "syncs account with transactions only" do
create_transaction(account: @account, date: 4.days.ago.to_date, amount: 100)
create_transaction(account: @account, date: 2.days.ago.to_date, amount: -500)
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ 19600, 19500, 19500, 20000, 20000, @account.balance ], @account.balances.chronological.map(&:balance)
end
test "syncs account with valuations and transactions" do
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 20000)
create_transaction(account: @account, date: 3.days.ago.to_date, amount: -500)
create_transaction(account: @account, date: 2.days.ago.to_date, amount: 100)
create_valuation(account: @account, date: 1.day.ago.to_date, amount: 25000)
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ 20000, 20000, 20500, 20400, 25000, @account.balance ], @account.balances.chronological.map(&:balance)
end
test "syncs account with transactions in multiple currencies" do
ExchangeRate.create! date: 1.day.ago.to_date, from_currency: "EUR", to_currency: "USD", rate: 1.2
create_transaction(account: @account, date: 3.days.ago.to_date, amount: 100, currency: "USD")
create_transaction(account: @account, date: 2.days.ago.to_date, amount: 300, currency: "USD")
create_transaction(account: @account, date: 1.day.ago.to_date, amount: 500, currency: "EUR") # €500 * 1.2 = $600
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ 21000, 20900, 20600, 20000, @account.balance ], @account.balances.chronological.map(&:balance)
end
test "converts foreign account balances to family currency" do
@account.update! currency: "EUR"
create_transaction(date: 1.day.ago.to_date, amount: 1000, account: @account, currency: "EUR")
create_exchange_rate(2.days.ago.to_date, from: "EUR", to: "USD", rate: 2)
create_exchange_rate(1.day.ago.to_date, from: "EUR", to: "USD", rate: 2)
create_exchange_rate(Date.current, from: "EUR", to: "USD", rate: 2)
syncer = Account::Balance::Syncer.new(@account)
syncer.run
usd_balances = @account.balances.where(currency: "USD").chronological.map(&:balance)
eur_balances = @account.balances.where(currency: "EUR").chronological.map(&:balance)
assert_equal [ 21000, 20000, @account.balance ], eur_balances # native account balances
assert_equal [ 42000, 40000, @account.balance * 2 ], usd_balances # converted balances at rate of 2:1
end
test "fails with error if exchange rate not available for any entry" do
create_transaction(account: @account, currency: "EUR")
syncer = Account::Balance::Syncer.new(@account)
assert_raises Money::ConversionError do
syncer.run
end
end
# Account is able to calculate balances in its own currency (i.e. can still show a historical graph), but
# doesn't have exchange rates available to convert those calculated balances to the family currency
test "completes with warning if exchange rates not available to convert to family currency" do
@account.update! currency: "EUR"
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal 1, syncer.warnings.count
end
test "overwrites existing balances and purges stale balances" do
assert_equal 0, @account.balances.size
@account.balances.create! date: Date.current, currency: "USD", balance: 30000 # incorrect balance, will be updated
@account.balances.create! date: 10.years.ago.to_date, currency: "USD", balance: 35000 # Out of range balance, will be deleted
assert_equal 2, @account.balances.size
syncer = Account::Balance::Syncer.new(@account)
syncer.run
assert_equal [ @account.balance ], @account.balances.chronological.map(&:balance)
end
test "partial sync does not affect balances prior to sync start date" do
existing_balance = @account.balances.create! date: 2.days.ago.to_date, currency: "USD", balance: 30000
transaction = create_transaction(account: @account, date: 1.day.ago.to_date, amount: 100, currency: "USD")
syncer = Account::Balance::Syncer.new(@account, start_date: 1.day.ago.to_date)
syncer.run
assert_equal [ existing_balance.balance, existing_balance.balance - transaction.amount, @account.balance ], @account.balances.chronological.map(&:balance)
end
private
def create_exchange_rate(date, from:, to:, rate:)
ExchangeRate.create! date: date, from_currency: from, to_currency: to, rate: rate
end
end