Update calculator test assertions for new balance components

This commit is contained in:
Zach Gollwitzer
2025-07-20 08:37:55 -04:00
parent a2a0bd2e6c
commit 91d970c7fe
7 changed files with 503 additions and 115 deletions

View File

@@ -2,8 +2,16 @@ class Balance < ApplicationRecord
include Monetizable
belongs_to :account
validates :account, :date, :balance, presence: true
monetize :balance, :cash_balance
validates :flows_factor, inclusion: { in: [ -1, 1 ] }
monetize :balance, :cash_balance,
:start_cash_balance, :start_non_cash_balance, :start_balance,
:cash_inflows, :cash_outflows, :non_cash_inflows, :non_cash_outflows, :net_market_flows,
:cash_adjustments, :non_cash_adjustments,
:end_cash_balance, :end_non_cash_balance, :end_balance
scope :in_period, ->(period) { period.nil? ? all : where(date: period.date_range) }
scope :chronological, -> { order(:date) }
end

View File

@@ -57,12 +57,14 @@ class Balance::BaseCalculator
raise NotImplementedError, "Directional calculators must implement this method"
end
def build_balance(date:, cash_balance:, non_cash_balance:)
def build_balance(date:, cash_balance:, non_cash_balance:, start_cash_balance: nil, start_non_cash_balance: nil)
Balance.new(
account_id: account.id,
date: date,
balance: non_cash_balance + cash_balance,
cash_balance: cash_balance,
start_cash_balance: start_cash_balance || 0,
start_non_cash_balance: start_non_cash_balance || 0,
currency: account.currency
)
end

View File

@@ -24,7 +24,9 @@ class Balance::ForwardCalculator < Balance::BaseCalculator
output_balance = build_balance(
date: date,
cash_balance: end_cash_balance,
non_cash_balance: end_non_cash_balance
non_cash_balance: end_non_cash_balance,
start_cash_balance: start_cash_balance,
start_non_cash_balance: start_non_cash_balance
)
# Set values for the next iteration

View File

@@ -24,7 +24,9 @@ class Balance::ReverseCalculator < Balance::BaseCalculator
build_balance(
date: date,
cash_balance: end_cash_balance,
non_cash_balance: end_non_cash_balance
non_cash_balance: end_non_cash_balance,
start_cash_balance: start_cash_balance,
start_non_cash_balance: start_non_cash_balance
)
else
start_cash_balance = derive_start_cash_balance(end_cash_balance: end_cash_balance, date: date)
@@ -35,7 +37,9 @@ class Balance::ReverseCalculator < Balance::BaseCalculator
output_balance = build_balance(
date: date,
cash_balance: end_cash_balance,
non_cash_balance: end_non_cash_balance
non_cash_balance: end_non_cash_balance,
start_cash_balance: start_cash_balance,
start_non_cash_balance: start_non_cash_balance
)
end_cash_balance = start_cash_balance

View File

@@ -21,8 +21,12 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 0, cash_balance: 0 } ]
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 0, cash_balance: 0 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 }
}
]
)
end
@@ -41,9 +45,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
# Since we start at 0, this transaction (inflow) simply increases balance from 0 -> 1000
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 0, cash_balance: 0 } ],
[ 2.days.ago.to_date, { balance: 1000, cash_balance: 1000 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 0, cash_balance: 0 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 0, end: 0 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 1000, cash_balance: 1000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 }
}
]
)
end
@@ -62,9 +74,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
# First valuation sets balance to 18000, then transaction increases balance to 19000
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 18000, cash_balance: 18000 } ],
[ 2.days.ago.to_date, { balance: 19000, cash_balance: 19000 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 18000, cash_balance: 18000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 19000, cash_balance: 19000 },
balances: { start: 18000, start_cash: 18000, start_non_cash: 0, end_cash: 19000, end_non_cash: 0, end: 19000 }
}
]
)
end
@@ -83,9 +103,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 2.days.ago.to_date, { balance: 18000, cash_balance: 18000 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 18000, cash_balance: 18000 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
}
]
)
end
@@ -105,9 +133,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 17000, cash_balance: 0.0 } ],
[ 2.days.ago.to_date, { balance: 18000, cash_balance: 0.0 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 0.0 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 17000, end: 17000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 18000, cash_balance: 0.0 },
balances: { start: 17000, start_cash: 0, start_non_cash: 17000, end_cash: 0, end_non_cash: 18000, end: 18000 }
}
]
)
end
@@ -127,9 +163,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 2.days.ago.to_date, { balance: 18000, cash_balance: 18000 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 18000, cash_balance: 18000 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 18000, end_non_cash: 0, end: 18000 }
}
]
)
end
@@ -152,11 +196,27 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 5.days.ago.to_date, { balance: 20000, cash_balance: 20000 } ],
[ 4.days.ago.to_date, { balance: 20500, cash_balance: 20500 } ],
[ 3.days.ago.to_date, { balance: 20500, cash_balance: 20500 } ],
[ 2.days.ago.to_date, { balance: 20400, cash_balance: 20400 } ]
expected_data: [
{
date: 5.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 4.days.ago.to_date,
legacy_balances: { balance: 20500, cash_balance: 20500 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 }
},
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 20500, cash_balance: 20500 },
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20500, end_non_cash: 0, end: 20500 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 20400, cash_balance: 20400 },
balances: { start: 20500, start_cash: 20500, start_non_cash: 0, end_cash: 20400, end_non_cash: 0, end: 20400 }
}
]
)
end
@@ -176,11 +236,27 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 5.days.ago.to_date, { balance: 1000, cash_balance: 1000 } ],
[ 4.days.ago.to_date, { balance: 500, cash_balance: 500 } ],
[ 3.days.ago.to_date, { balance: 500, cash_balance: 500 } ],
[ 2.days.ago.to_date, { balance: 600, cash_balance: 600 } ]
expected_data: [
{
date: 5.days.ago.to_date,
legacy_balances: { balance: 1000, cash_balance: 1000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 1000, end_non_cash: 0, end: 1000 }
},
{
date: 4.days.ago.to_date,
legacy_balances: { balance: 500, cash_balance: 500 },
balances: { start: 1000, start_cash: 1000, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
},
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 500, cash_balance: 500 },
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 600, cash_balance: 600 },
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 600, end_non_cash: 0, end: 600 }
}
]
)
end
@@ -203,17 +279,57 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 10.days.ago.to_date, { balance: 20000, cash_balance: 20000 } ],
[ 9.days.ago.to_date, { balance: 20000, cash_balance: 20000 } ],
[ 8.days.ago.to_date, { balance: 25000, cash_balance: 25000 } ],
[ 7.days.ago.to_date, { balance: 25000, cash_balance: 25000 } ],
[ 6.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 5.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 4.days.ago.to_date, { balance: 17500, cash_balance: 17500 } ],
[ 3.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 2.days.ago.to_date, { balance: 17000, cash_balance: 17000 } ],
[ 1.day.ago.to_date, { balance: 16900, cash_balance: 16900 } ]
expected_data: [
{
date: 10.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 9.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 8.days.ago.to_date,
legacy_balances: { balance: 25000, cash_balance: 25000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 25000, end_non_cash: 0, end: 25000 }
},
{
date: 7.days.ago.to_date,
legacy_balances: { balance: 25000, cash_balance: 25000 },
balances: { start: 25000, start_cash: 25000, start_non_cash: 0, end_cash: 25000, end_non_cash: 0, end: 25000 }
},
{
date: 6.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 25000, start_cash: 25000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 5.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 4.days.ago.to_date,
legacy_balances: { balance: 17500, cash_balance: 17500 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17500, end_non_cash: 0, end: 17500 }
},
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 17500, start_cash: 17500, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 17000, cash_balance: 17000 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 17000, end_non_cash: 0, end: 17000 }
},
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 16900, cash_balance: 16900 },
balances: { start: 17000, start_cash: 17000, start_non_cash: 0, end_cash: 16900, end_non_cash: 0, end: 16900 }
}
]
)
end
@@ -237,11 +353,27 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 4.days.ago.to_date, { balance: 100, cash_balance: 100 } ],
[ 3.days.ago.to_date, { balance: 200, cash_balance: 200 } ],
[ 2.days.ago.to_date, { balance: 500, cash_balance: 500 } ],
[ 1.day.ago.to_date, { balance: 1100, cash_balance: 1100 } ]
expected_data: [
{
date: 4.days.ago.to_date,
legacy_balances: { balance: 100, cash_balance: 100 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 100, end_non_cash: 0, end: 100 }
},
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 200, cash_balance: 200 },
balances: { start: 100, start_cash: 100, start_non_cash: 0, end_cash: 200, end_non_cash: 0, end: 200 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 500, cash_balance: 500 },
balances: { start: 200, start_cash: 200, start_non_cash: 0, end_cash: 500, end_non_cash: 0, end: 500 }
},
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 1100, cash_balance: 1100 },
balances: { start: 500, start_cash: 500, start_non_cash: 0, end_cash: 1100, end_non_cash: 0, end: 1100 }
}
]
)
end
@@ -263,9 +395,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 2.days.ago.to_date, { balance: 20000, cash_balance: 0 } ],
[ 1.day.ago.to_date, { balance: 18000, cash_balance: 0 } ]
expected_data: [
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 0 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 20000, end: 20000 }
},
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 18000, cash_balance: 0 },
balances: { start: 20000, start_cash: 0, start_non_cash: 20000, end_cash: 0, end_non_cash: 18000, end: 18000 }
}
]
)
end
@@ -286,9 +426,17 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 500000, cash_balance: 0 } ],
[ 2.days.ago.to_date, { balance: 500000, cash_balance: 0 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 500000, cash_balance: 0 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 0, end_non_cash: 500000, end: 500000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 500000, cash_balance: 0 },
balances: { start: 500000, start_cash: 0, start_non_cash: 500000, end_cash: 0, end_non_cash: 500000, end: 500000 }
}
]
)
end
@@ -324,11 +472,27 @@ class Balance::ForwardCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ 3.days.ago.to_date, { balance: 5000, cash_balance: 5000 } ],
[ 2.days.ago.to_date, { balance: 5000, cash_balance: 5000 } ],
[ 1.day.ago.to_date, { balance: 5000, cash_balance: 4000 } ],
[ Date.current, { balance: 5000, cash_balance: 4000 } ]
expected_data: [
{
date: 3.days.ago.to_date,
legacy_balances: { balance: 5000, cash_balance: 5000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 }
},
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 5000, cash_balance: 5000 },
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 5000, end_non_cash: 0, end: 5000 }
},
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 5000, cash_balance: 4000 },
balances: { start: 5000, start_cash: 5000, start_non_cash: 0, end_cash: 4000, end_non_cash: 1000, end: 5000 }
},
{
date: Date.current,
legacy_balances: { balance: 5000, cash_balance: 4000 },
balances: { start: 5000, start_cash: 4000, start_non_cash: 1000, end_cash: 4000, end_non_cash: 1000, end: 5000 }
}
]
)
end

View File

@@ -16,8 +16,12 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 20000 } ]
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 0, start_cash: 0, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
}
]
)
end
@@ -47,12 +51,32 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
# a 100% full entries history.
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 20000 } ], # Current anchor
[ 1.day.ago, { balance: 20000, cash_balance: 20000 } ],
[ 2.days.ago, { balance: 20000, cash_balance: 20000 } ],
[ 3.days.ago, { balance: 20000, cash_balance: 20000 } ],
[ 4.days.ago, { balance: 15000, cash_balance: 15000 } ] # Opening anchor
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
}, # Current anchor
{
date: 1.day.ago,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 2.days.ago,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 3.days.ago,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 15000, start_cash: 15000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
},
{
date: 4.days.ago,
legacy_balances: { balance: 15000, cash_balance: 15000 },
balances: { start: 15000, start_cash: 15000, start_non_cash: 0, end_cash: 15000, end_non_cash: 0, end: 15000 }
} # Opening anchor
]
)
end
@@ -75,9 +99,17 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 10000 } ], # Since $10,000 of holdings, cash has to be $10,000 to reach $20,000 total value
[ 1.day.ago, { balance: 15000, cash_balance: 5000 } ] # Since $10,000 of holdings, cash has to be $5,000 to reach $15,000 total value
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 10000 },
balances: { start: 20000, start_cash: 10000, start_non_cash: 10000, end_cash: 10000, end_non_cash: 10000, end: 20000 }
}, # Since $10,000 of holdings, cash has to be $10,000 to reach $20,000 total value
{
date: 1.day.ago,
legacy_balances: { balance: 15000, cash_balance: 5000 },
balances: { start: 15000, start_cash: 5000, start_non_cash: 10000, end_cash: 5000, end_non_cash: 10000, end: 15000 }
} # Since $10,000 of holdings, cash has to be $5,000 to reach $15,000 total value
]
)
end
@@ -96,13 +128,37 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 20000 } ], # Current balance
[ 1.day.ago, { balance: 20000, cash_balance: 20000 } ], # No change
[ 2.days.ago, { balance: 20000, cash_balance: 20000 } ], # After expense (+100)
[ 3.days.ago, { balance: 20100, cash_balance: 20100 } ], # Before expense
[ 4.days.ago, { balance: 20100, cash_balance: 20100 } ], # After income (-500)
[ 5.days.ago, { balance: 19600, cash_balance: 19600 } ] # After income (-500)
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
}, # Current balance
{
date: 1.day.ago,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
}, # No change
{
date: 2.days.ago,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20100, start_cash: 20100, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
}, # After expense (+100)
{
date: 3.days.ago,
legacy_balances: { balance: 20100, cash_balance: 20100 },
balances: { start: 20100, start_cash: 20100, start_non_cash: 0, end_cash: 20100, end_non_cash: 0, end: 20100 }
}, # Before expense
{
date: 4.days.ago,
legacy_balances: { balance: 20100, cash_balance: 20100 },
balances: { start: 19600, start_cash: 19600, start_non_cash: 0, end_cash: 20100, end_non_cash: 0, end: 20100 }
}, # After income (-500)
{
date: 5.days.ago,
legacy_balances: { balance: 19600, cash_balance: 19600 },
balances: { start: 19600, start_cash: 19600, start_non_cash: 0, end_cash: 19600, end_non_cash: 0, end: 19600 }
} # After income (-500)
]
)
end
@@ -122,13 +178,37 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
# Reversed order: showing how we work backwards
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 2000, cash_balance: 2000 } ], # Current balance
[ 1.day.ago, { balance: 2000, cash_balance: 2000 } ], # No change
[ 2.days.ago, { balance: 2000, cash_balance: 2000 } ], # After expense (+100)
[ 3.days.ago, { balance: 1900, cash_balance: 1900 } ], # Before expense
[ 4.days.ago, { balance: 1900, cash_balance: 1900 } ], # After CC payment (-500)
[ 5.days.ago, { balance: 2400, cash_balance: 2400 } ]
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 2000, cash_balance: 2000 },
balances: { start: 2000, start_cash: 2000, start_non_cash: 0, end_cash: 2000, end_non_cash: 0, end: 2000 }
}, # Current balance
{
date: 1.day.ago,
legacy_balances: { balance: 2000, cash_balance: 2000 },
balances: { start: 2000, start_cash: 2000, start_non_cash: 0, end_cash: 2000, end_non_cash: 0, end: 2000 }
}, # No change
{
date: 2.days.ago,
legacy_balances: { balance: 2000, cash_balance: 2000 },
balances: { start: 1900, start_cash: 1900, start_non_cash: 0, end_cash: 2000, end_non_cash: 0, end: 2000 }
}, # After expense (+100)
{
date: 3.days.ago,
legacy_balances: { balance: 1900, cash_balance: 1900 },
balances: { start: 1900, start_cash: 1900, start_non_cash: 0, end_cash: 1900, end_non_cash: 0, end: 1900 }
}, # Before expense
{
date: 4.days.ago,
legacy_balances: { balance: 1900, cash_balance: 1900 },
balances: { start: 2400, start_cash: 2400, start_non_cash: 0, end_cash: 1900, end_non_cash: 0, end: 1900 }
}, # After CC payment (-500)
{
date: 5.days.ago,
legacy_balances: { balance: 2400, cash_balance: 2400 },
balances: { start: 2400, start_cash: 2400, start_non_cash: 0, end_cash: 2400, end_non_cash: 0, end: 2400 }
}
]
)
end
@@ -150,10 +230,22 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 198000, cash_balance: 0 } ],
[ 1.day.ago, { balance: 198000, cash_balance: 0 } ],
[ 2.days.ago, { balance: 200000, cash_balance: 0 } ]
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 198000, cash_balance: 0 },
balances: { start: 198000, start_cash: 0, start_non_cash: 198000, end_cash: 0, end_non_cash: 198000, end: 198000 }
},
{
date: 1.day.ago,
legacy_balances: { balance: 198000, cash_balance: 0 },
balances: { start: 200000, start_cash: 0, start_non_cash: 200000, end_cash: 0, end_non_cash: 198000, end: 198000 }
},
{
date: 2.days.ago,
legacy_balances: { balance: 200000, cash_balance: 0 },
balances: { start: 200000, start_cash: 0, start_non_cash: 200000, end_cash: 0, end_non_cash: 200000, end: 200000 }
}
]
)
end
@@ -174,10 +266,22 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 1000, cash_balance: 0 } ],
[ 1.day.ago, { balance: 1000, cash_balance: 0 } ],
[ 2.days.ago, { balance: 1000, cash_balance: 0 } ]
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 1000, cash_balance: 0 },
balances: { start: 1000, start_cash: 0, start_non_cash: 1000, end_cash: 0, end_non_cash: 1000, end: 1000 }
},
{
date: 1.day.ago,
legacy_balances: { balance: 1000, cash_balance: 0 },
balances: { start: 1000, start_cash: 0, start_non_cash: 1000, end_cash: 0, end_non_cash: 1000, end: 1000 }
},
{
date: 2.days.ago,
legacy_balances: { balance: 1000, cash_balance: 0 },
balances: { start: 1000, start_cash: 0, start_non_cash: 1000, end_cash: 0, end_non_cash: 1000, end: 1000 }
}
]
)
end
@@ -206,10 +310,22 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
# (the single trade doesn't affect balance; it just alters cash vs. holdings composition)
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 19000 } ], # Current: $19k cash + $1k holdings (anchor)
[ 1.day.ago.to_date, { balance: 20000, cash_balance: 19000 } ], # After trade: $19k cash + $1k holdings
[ 2.days.ago.to_date, { balance: 20000, cash_balance: 20000 } ] # At first, account is 100% cash, no holdings (no trades)
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 19000 },
balances: { start: 20000, start_cash: 19000, start_non_cash: 1000, end_cash: 19000, end_non_cash: 1000, end: 20000 }
}, # Current: $19k cash + $1k holdings (anchor)
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 19000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 19000, end_non_cash: 1000, end: 20000 }
}, # After trade: $19k cash + $1k holdings
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 20000 },
balances: { start: 20000, start_cash: 20000, start_non_cash: 0, end_cash: 20000, end_non_cash: 0, end: 20000 }
} # At first, account is 100% cash, no holdings (no trades)
]
)
end
@@ -240,10 +356,22 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
[ Date.current, { balance: 20000, cash_balance: 19000 } ], # Current: $19k cash + $1k holdings ($500 MSFT, $500 AAPL)
[ 1.day.ago.to_date, { balance: 20000, cash_balance: 19000 } ], # After AAPL trade: $19k cash + $1k holdings
[ 2.days.ago.to_date, { balance: 20000, cash_balance: 19500 } ] # Before AAPL trade: $19.5k cash + $500 MSFT
expected_data: [
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 19000 },
balances: { start: 20000, start_cash: 19000, start_non_cash: 1000, end_cash: 19000, end_non_cash: 1000, end: 20000 }
}, # Current: $19k cash + $1k holdings ($500 MSFT, $500 AAPL)
{
date: 1.day.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 19000 },
balances: { start: 20000, start_cash: 19500, start_non_cash: 500, end_cash: 19000, end_non_cash: 1000, end: 20000 }
}, # After AAPL trade: $19k cash + $1k holdings
{
date: 2.days.ago.to_date,
legacy_balances: { balance: 20000, cash_balance: 19500 },
balances: { start: 20000, start_cash: 19500, start_non_cash: 500, end_cash: 19500, end_non_cash: 500, end: 20000 }
} # Before AAPL trade: $19.5k cash + $500 MSFT
]
)
end
@@ -267,12 +395,24 @@ class Balance::ReverseCalculatorTest < ActiveSupport::TestCase
assert_calculated_ledger_balances(
calculated_data: calculated,
expected_balances: [
expected_data: [
# No matter what, we force current day equal to the "anchor" balance (what provider gave us), and let "cash" float based on holdings value
# This ensures the user sees the same top-line number reported by the provider (even if it creates a discrepancy in the cash balance)
[ Date.current, { balance: 20000, cash_balance: 18000 } ],
[ 1.day.ago, { balance: 20000, cash_balance: 18000 } ],
[ 2.days.ago, { balance: 15000, cash_balance: 15000 } ] # Opening anchor sets absolute balance
{
date: Date.current,
legacy_balances: { balance: 20000, cash_balance: 18000 },
balances: { start: 20000, start_cash: 18000, start_non_cash: 2000, end_cash: 18000, end_non_cash: 2000, end: 20000 }
},
{
date: 1.day.ago,
legacy_balances: { balance: 20000, cash_balance: 18000 },
balances: { start: 20000, start_cash: 18000, start_non_cash: 2000, end_cash: 18000, end_non_cash: 2000, end: 20000 }
},
{
date: 2.days.ago,
legacy_balances: { balance: 15000, cash_balance: 15000 },
balances: { start: 15000, start_cash: 15000, start_non_cash: 0, end_cash: 15000, end_non_cash: 0, end: 15000 }
} # Opening anchor sets absolute balance
]
)
end

View File

@@ -109,13 +109,20 @@ module LedgerTestingHelper
created_account
end
def assert_calculated_ledger_balances(calculated_data:, expected_balances:)
# Convert expected balances to a hash for easier lookup
expected_hash = expected_balances.to_h do |date, balance_data|
[ date.to_date, balance_data ]
def assert_calculated_ledger_balances(calculated_data:, expected_data:)
# Convert expected data to a hash for easier lookup
# Structure: [ { date:, legacy_balances: { balance:, cash_balance: }, balances: { start:, start_cash:, etc... }, flows: { ... }, adjustments: { ... } } ]
expected_hash = {}
expected_data.each do |data|
expected_hash[data[:date].to_date] = {
legacy_balances: data[:legacy_balances] || {},
balances: data[:balances] || {},
flows: data[:flows] || {},
adjustments: data[:adjustments] || {}
}
end
# Get all unique dates from both calculated and expected data
# Get all unique dates from all data sources
all_dates = (calculated_data.map(&:date) + expected_hash.keys).uniq.sort
# Check each date
@@ -126,15 +133,76 @@ module LedgerTestingHelper
if expected
assert calculated_balance, "Expected balance for #{date} but none was calculated"
if expected[:balance]
assert_equal expected[:balance], calculated_balance.balance.to_d,
"Balance mismatch for #{date}"
end
legacy_balances = expected[:legacy_balances]
balances = expected[:balances]
flows = expected[:flows]
adjustments = expected[:adjustments]
if expected[:cash_balance]
assert_equal expected[:cash_balance], calculated_balance.cash_balance.to_d,
# Legacy balance assertions
if legacy_balances.any?
assert_equal legacy_balances[:balance], calculated_balance.balance.to_d,
"Balance mismatch for #{date}"
assert_equal legacy_balances[:cash_balance], calculated_balance.cash_balance.to_d,
"Cash balance mismatch for #{date}"
end
# Balance assertions
if balances.any?
assert_equal balances[:start_cash], calculated_balance.start_cash_balance.to_d,
"Start cash balance mismatch for #{date}" if balances.key?(:start_cash)
assert_equal balances[:start_non_cash], calculated_balance.start_non_cash_balance.to_d,
"Start non-cash balance mismatch for #{date}" if balances.key?(:start_non_cash)
assert_equal balances[:end_cash], calculated_balance.end_cash_balance.to_d,
"End cash balance mismatch for #{date}" if balances.key?(:end_cash)
assert_equal balances[:end_non_cash], calculated_balance.end_non_cash_balance.to_d,
"End non-cash balance mismatch for #{date}" if balances.key?(:end_non_cash)
# Generated column assertions
assert_equal balances[:start], calculated_balance.start_balance.to_d,
"Start balance mismatch for #{date}" if balances.key?(:start)
assert_equal balances[:end], calculated_balance.end_balance.to_d,
"End balance mismatch for #{date}" if balances.key?(:end)
end
# Flow assertions
if flows.any?
assert_equal flows[:cash_inflows], calculated_balance.cash_inflows.to_d,
"Cash inflows mismatch for #{date}" if flows.key?(:cash_inflows)
assert_equal flows[:cash_outflows], calculated_balance.cash_outflows.to_d,
"Cash outflows mismatch for #{date}" if flows.key?(:cash_outflows)
assert_equal flows[:non_cash_inflows], calculated_balance.non_cash_inflows.to_d,
"Non-cash inflows mismatch for #{date}" if flows.key?(:non_cash_inflows)
assert_equal flows[:non_cash_outflows], calculated_balance.non_cash_outflows.to_d,
"Non-cash outflows mismatch for #{date}" if flows.key?(:non_cash_outflows)
assert_equal flows[:net_market_flows], calculated_balance.net_market_flows.to_d,
"Net market flows mismatch for #{date}" if flows.key?(:net_market_flows)
end
# Adjustment assertions
if adjustments.any?
assert_equal adjustments[:cash_adjustments], calculated_balance.cash_adjustments.to_d,
"Cash adjustments mismatch for #{date}" if adjustments.key?(:cash_adjustments)
assert_equal adjustments[:non_cash_adjustments], calculated_balance.non_cash_adjustments.to_d,
"Non-cash adjustments mismatch for #{date}" if adjustments.key?(:non_cash_adjustments)
end
# Temporary assertions during migration (remove after migration complete)
# TODO: Remove these assertions after migration is complete
assert_equal calculated_balance.cash_balance.to_d, calculated_balance.end_cash_balance.to_d,
"Temporary assertion failed: end_cash_balance should equal cash_balance for #{date}"
assert_equal calculated_balance.balance.to_d, calculated_balance.end_balance.to_d,
"Temporary assertion failed: end_balance should equal balance for #{date}"
else
assert_nil calculated_balance, "Unexpected balance calculated for #{date}"
end