2024-02-02 09:05:04 -06:00
|
|
|
require "test_helper"
|
|
|
|
|
|
|
|
class AccountTest < ActiveSupport::TestCase
|
2025-01-27 16:56:46 -05:00
|
|
|
include SyncableInterfaceTest, Account::EntriesTestHelper
|
2024-07-10 11:22:59 -04:00
|
|
|
|
|
|
|
setup do
|
2024-11-15 13:49:37 -05:00
|
|
|
@account = @syncable = accounts(:depository)
|
2024-03-21 13:39:10 -04:00
|
|
|
@family = families(:dylan_family)
|
2024-02-06 12:30:51 -05:00
|
|
|
end
|
|
|
|
|
2025-01-20 11:37:01 -05:00
|
|
|
test "can destroy" do
|
|
|
|
assert_difference "Account.count", -1 do
|
|
|
|
@account.destroy
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-03-21 13:39:10 -04:00
|
|
|
test "groups accounts by type" do
|
|
|
|
result = @family.accounts.by_group(period: Period.all)
|
|
|
|
assets = result[:assets]
|
|
|
|
liabilities = result[:liabilities]
|
|
|
|
|
|
|
|
assert_equal @family.assets, assets.sum
|
|
|
|
assert_equal @family.liabilities, liabilities.sum
|
|
|
|
|
2024-06-20 07:26:25 -04:00
|
|
|
depositories = assets.children.find { |group| group.name == "Depository" }
|
|
|
|
properties = assets.children.find { |group| group.name == "Property" }
|
|
|
|
vehicles = assets.children.find { |group| group.name == "Vehicle" }
|
|
|
|
investments = assets.children.find { |group| group.name == "Investment" }
|
|
|
|
other_assets = assets.children.find { |group| group.name == "OtherAsset" }
|
2024-03-21 13:39:10 -04:00
|
|
|
|
2024-06-20 07:26:25 -04:00
|
|
|
credits = liabilities.children.find { |group| group.name == "CreditCard" }
|
|
|
|
loans = liabilities.children.find { |group| group.name == "Loan" }
|
|
|
|
other_liabilities = liabilities.children.find { |group| group.name == "OtherLiability" }
|
2024-03-21 13:39:10 -04:00
|
|
|
|
2024-11-15 13:49:37 -05:00
|
|
|
assert_equal 2, depositories.children.count
|
2024-06-19 06:52:08 -04:00
|
|
|
assert_equal 1, properties.children.count
|
|
|
|
assert_equal 1, vehicles.children.count
|
|
|
|
assert_equal 1, investments.children.count
|
2024-03-21 13:39:10 -04:00
|
|
|
assert_equal 1, other_assets.children.count
|
|
|
|
|
|
|
|
assert_equal 1, credits.children.count
|
2024-06-19 06:52:08 -04:00
|
|
|
assert_equal 1, loans.children.count
|
|
|
|
assert_equal 1, other_liabilities.children.count
|
2024-03-21 13:39:10 -04:00
|
|
|
end
|
|
|
|
|
2024-07-10 11:22:59 -04:00
|
|
|
test "generates balance series" do
|
|
|
|
assert_equal 2, @account.series.values.count
|
2024-03-21 13:39:10 -04:00
|
|
|
end
|
|
|
|
|
2024-07-10 11:22:59 -04:00
|
|
|
test "generates balance series with single value if no balances" do
|
|
|
|
@account.balances.delete_all
|
|
|
|
assert_equal 1, @account.series.values.count
|
2024-03-21 13:39:10 -04:00
|
|
|
end
|
2024-03-29 12:53:08 -04:00
|
|
|
|
2024-07-10 11:22:59 -04:00
|
|
|
test "generates balance series in period" do
|
|
|
|
@account.balances.delete_all
|
|
|
|
@account.balances.create! date: 31.days.ago.to_date, balance: 5000, currency: "USD" # out of period range
|
|
|
|
@account.balances.create! date: 30.days.ago.to_date, balance: 5000, currency: "USD" # in range
|
2024-03-29 12:53:08 -04:00
|
|
|
|
2024-07-10 11:22:59 -04:00
|
|
|
assert_equal 1, @account.series(period: Period.last_30_days).values.count
|
2024-03-29 12:53:08 -04:00
|
|
|
end
|
|
|
|
|
2024-07-10 11:22:59 -04:00
|
|
|
test "generates empty series if no balances and no exchange rate" do
|
2024-08-01 19:43:23 -04:00
|
|
|
with_env_overrides SYNTH_API_KEY: nil do
|
|
|
|
assert_equal 0, @account.series(currency: "NZD").values.count
|
|
|
|
end
|
2024-03-29 12:53:08 -04:00
|
|
|
end
|
2025-01-27 16:56:46 -05:00
|
|
|
|
|
|
|
test "auto-matches transfers" do
|
|
|
|
outflow_entry = create_transaction(date: 1.day.ago.to_date, account: @account, amount: 500)
|
|
|
|
inflow_entry = create_transaction(date: Date.current, account: accounts(:credit_card), amount: -500)
|
|
|
|
|
|
|
|
assert_difference -> { Transfer.count } => 1 do
|
|
|
|
@account.auto_match_transfers!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# In this scenario, our matching logic should find 4 potential matches. These matches should be ranked based on
|
|
|
|
# days apart, then de-duplicated so that we aren't auto-matching the same transaction across multiple transfers.
|
|
|
|
test "when 2 options exist, only auto-match one at a time, ranked by days apart" do
|
|
|
|
yesterday_outflow = create_transaction(date: 1.day.ago.to_date, account: @account, amount: 500)
|
|
|
|
yesterday_inflow = create_transaction(date: 1.day.ago.to_date, account: accounts(:credit_card), amount: -500)
|
|
|
|
|
|
|
|
today_outflow = create_transaction(date: Date.current, account: @account, amount: 500)
|
|
|
|
today_inflow = create_transaction(date: Date.current, account: accounts(:credit_card), amount: -500)
|
|
|
|
|
|
|
|
assert_difference -> { Transfer.count } => 2 do
|
|
|
|
@account.auto_match_transfers!
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
test "does not auto-match any transfers that have been rejected by user already" do
|
|
|
|
outflow = create_transaction(date: Date.current, account: @account, amount: 500)
|
|
|
|
inflow = create_transaction(date: Date.current, account: accounts(:credit_card), amount: -500)
|
|
|
|
|
|
|
|
RejectedTransfer.create!(inflow_transaction_id: inflow.entryable_id, outflow_transaction_id: outflow.entryable_id)
|
|
|
|
|
|
|
|
assert_no_difference -> { Transfer.count } do
|
|
|
|
@account.auto_match_transfers!
|
|
|
|
end
|
|
|
|
end
|
2025-02-05 11:52:44 -06:00
|
|
|
|
|
|
|
test "transfer_match_candidates only matches between active accounts" do
|
|
|
|
active_account = accounts(:depository)
|
|
|
|
another_active_account = accounts(:credit_card)
|
|
|
|
inactive_account = accounts(:investment)
|
|
|
|
inactive_account.update!(is_active: false)
|
|
|
|
|
|
|
|
# Create matching transactions
|
|
|
|
active_inflow = active_account.entries.create!(
|
|
|
|
date: Date.current,
|
|
|
|
amount: -100,
|
|
|
|
currency: "USD",
|
|
|
|
name: "Test transfer",
|
|
|
|
entryable: Account::Transaction.new
|
|
|
|
)
|
|
|
|
|
|
|
|
active_outflow = another_active_account.entries.create!(
|
|
|
|
date: Date.current,
|
|
|
|
amount: 100,
|
|
|
|
currency: "USD",
|
|
|
|
name: "Test transfer",
|
|
|
|
entryable: Account::Transaction.new
|
|
|
|
)
|
|
|
|
|
|
|
|
inactive_outflow = inactive_account.entries.create!(
|
|
|
|
date: Date.current,
|
|
|
|
amount: 100,
|
|
|
|
currency: "USD",
|
|
|
|
name: "Test transfer",
|
|
|
|
entryable: Account::Transaction.new
|
|
|
|
)
|
|
|
|
|
|
|
|
# Should find matches between active accounts
|
|
|
|
candidates = active_account.transfer_match_candidates
|
|
|
|
assert_includes candidates.map(&:outflow_transaction_id), active_outflow.entryable_id
|
|
|
|
|
|
|
|
# Should not match with inactive account
|
|
|
|
assert_not_includes candidates.map(&:outflow_transaction_id), inactive_outflow.entryable_id
|
|
|
|
end
|
2024-02-02 09:05:04 -06:00
|
|
|
end
|