mirror of
https://github.com/we-promise/sure.git
synced 2026-04-09 07:14:47 +00:00
* Add tests and update logic for processing SimpleFIN investment transactions - Added `SimplefinAccount::Transactions::ProcessorInvestmentTest` to validate dividend transaction processing, transaction linking, and stale linkage repairs. - Enhanced `SimplefinItem#process_accounts` with stale linkage repair logic and detailed logging for unlinked accounts with transactions. - Updated `SimplefinAccount::Transactions::Processor` for improved logging and error handling during transaction processing. - Adjusted `SimplefinItem::Importer` to log detailed account and transaction information and use extended sync windows for investment accounts. * Refactor `SimplefinItem#process_accounts` to use direct queries for fresh data and streamline stale linkage repair logic; update tests for improved coverage and clarity. * Improve stale linkage repair logic in `SimplefinItem#repair_stale_linkages` - Updated to handle multiple linked accounts matching the same unlinked account by selecting the first match. - Added detailed logging to warn about multiple matches for easier debugging. * Include `:linked_account` in `SimplefinItem#process_accounts` queries for more comprehensive account data processing. * Expand `merge_transactions` logic with composite key fallback for deduplication; document edge cases. --------- Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
278 lines
9.8 KiB
Ruby
278 lines
9.8 KiB
Ruby
require "test_helper"
|
|
|
|
class SimplefinAccount::Transactions::ProcessorInvestmentTest < ActiveSupport::TestCase
|
|
setup do
|
|
@family = families(:dylan_family)
|
|
|
|
# Create SimpleFIN connection
|
|
@simplefin_item = SimplefinItem.create!(
|
|
family: @family,
|
|
name: "Test SimpleFIN",
|
|
access_url: "https://example.com/access"
|
|
)
|
|
|
|
# Create an Investment account
|
|
@account = Account.create!(
|
|
family: @family,
|
|
name: "Retirement - Roth IRA",
|
|
currency: "USD",
|
|
balance: 12199.06,
|
|
accountable: Investment.create!(subtype: :roth_ira)
|
|
)
|
|
|
|
# Create SimpleFIN account linked to the Investment account
|
|
@simplefin_account = SimplefinAccount.create!(
|
|
simplefin_item: @simplefin_item,
|
|
name: "Roth IRA",
|
|
account_id: "ACT-investment-123",
|
|
currency: "USD",
|
|
account_type: "investment",
|
|
current_balance: 12199.06,
|
|
raw_transactions_payload: [
|
|
{
|
|
"id" => "TRN-921a8cdb-f331-48ee-9de2-b0b9ff1d316a",
|
|
"posted" => 1766417520,
|
|
"amount" => "1.49",
|
|
"description" => "Dividend Reinvestment",
|
|
"payee" => "Dividend",
|
|
"memo" => "Dividend Reinvestment",
|
|
"transacted_at" => 1766417520
|
|
},
|
|
{
|
|
"id" => "TRN-881f2417-29e3-43f9-bd1b-013e60ba7a4b",
|
|
"posted" => 1766113200,
|
|
"amount" => "1.49",
|
|
"description" => "Sweep of dividend payouts",
|
|
"payee" => "Dividend",
|
|
"memo" => "Dividend Payment - IEMG",
|
|
"transacted_at" => 1766113200
|
|
},
|
|
{
|
|
"id" => "TRN-e52f1326-bbb6-42a7-8148-be48c8a81832",
|
|
"posted" => 1765985220,
|
|
"amount" => "0.05",
|
|
"description" => "Dividend Reinvestment",
|
|
"payee" => "Dividend",
|
|
"memo" => "Dividend Reinvestment",
|
|
"transacted_at" => 1765985220
|
|
}
|
|
]
|
|
)
|
|
|
|
# Link the account via legacy FK
|
|
@account.update!(simplefin_account_id: @simplefin_account.id)
|
|
end
|
|
|
|
test "processes dividend transactions for investment accounts" do
|
|
assert_equal 0, @account.entries.count, "Should start with no entries"
|
|
|
|
# Process transactions
|
|
processor = SimplefinAccount::Transactions::Processor.new(@simplefin_account)
|
|
processor.process
|
|
|
|
# Verify all 3 dividend transactions were created
|
|
assert_equal 3, @account.entries.count, "Should create 3 entries for dividend transactions"
|
|
|
|
# Verify entries are Transaction type (not Trade)
|
|
@account.entries.each do |entry|
|
|
assert_equal "Transaction", entry.entryable_type
|
|
end
|
|
|
|
# Verify external_ids are set correctly
|
|
external_ids = @account.entries.pluck(:external_id).sort
|
|
expected_ids = [
|
|
"simplefin_TRN-921a8cdb-f331-48ee-9de2-b0b9ff1d316a",
|
|
"simplefin_TRN-881f2417-29e3-43f9-bd1b-013e60ba7a4b",
|
|
"simplefin_TRN-e52f1326-bbb6-42a7-8148-be48c8a81832"
|
|
].sort
|
|
assert_equal expected_ids, external_ids
|
|
|
|
# Verify source is simplefin
|
|
@account.entries.each do |entry|
|
|
assert_equal "simplefin", entry.source
|
|
end
|
|
end
|
|
|
|
test "investment transactions processor is no-op to avoid duplicate processing" do
|
|
# First, process with regular processor
|
|
SimplefinAccount::Transactions::Processor.new(@simplefin_account).process
|
|
initial_count = @account.entries.count
|
|
assert_equal 3, initial_count
|
|
|
|
# Get the first entry's updated_at before running investment processor
|
|
first_entry = @account.entries.first
|
|
original_updated_at = first_entry.updated_at
|
|
|
|
# Run the investment transactions processor - should be a no-op
|
|
SimplefinAccount::Investments::TransactionsProcessor.new(@simplefin_account).process
|
|
|
|
# Entry count should be unchanged
|
|
assert_equal initial_count, @account.entries.reload.count
|
|
|
|
# Entries should not have been modified
|
|
first_entry.reload
|
|
assert_equal original_updated_at, first_entry.updated_at
|
|
end
|
|
|
|
test "processes transactions correctly via SimplefinAccount::Processor for investment accounts" do
|
|
# Verify the full processor flow works for investment accounts
|
|
processor = SimplefinAccount::Processor.new(@simplefin_account)
|
|
processor.process
|
|
|
|
# Should create transaction entries
|
|
assert_equal 3, @account.entries.where(entryable_type: "Transaction").count
|
|
|
|
# Verify amounts are correctly negated (SimpleFIN positive = income = negative in Sure)
|
|
entry = @account.entries.find_by(external_id: "simplefin_TRN-921a8cdb-f331-48ee-9de2-b0b9ff1d316a")
|
|
assert_not_nil entry
|
|
assert_equal BigDecimal("-1.49"), entry.amount
|
|
end
|
|
|
|
test "logs appropriate messages during processing" do
|
|
# Capture log output
|
|
log_output = StringIO.new
|
|
original_logger = Rails.logger
|
|
Rails.logger = Logger.new(log_output)
|
|
|
|
SimplefinAccount::Transactions::Processor.new(@simplefin_account).process
|
|
|
|
Rails.logger = original_logger
|
|
log_content = log_output.string
|
|
|
|
# Should log start message with transaction count
|
|
assert_match(/Processing 3 transactions/, log_content)
|
|
|
|
# Should log completion message
|
|
assert_match(/Completed.*3 processed, 0 errors/, log_content)
|
|
end
|
|
|
|
test "handles empty raw_transactions_payload gracefully" do
|
|
@simplefin_account.update!(raw_transactions_payload: [])
|
|
|
|
# Should not raise an error
|
|
processor = SimplefinAccount::Transactions::Processor.new(@simplefin_account)
|
|
processor.process
|
|
|
|
assert_equal 0, @account.entries.count
|
|
end
|
|
|
|
test "handles nil raw_transactions_payload gracefully" do
|
|
@simplefin_account.update!(raw_transactions_payload: nil)
|
|
|
|
# Should not raise an error
|
|
processor = SimplefinAccount::Transactions::Processor.new(@simplefin_account)
|
|
processor.process
|
|
|
|
assert_equal 0, @account.entries.count
|
|
end
|
|
|
|
test "repairs stale linkage when user re-adds institution in SimpleFIN" do
|
|
# Simulate user re-adding institution: old SimplefinAccount is linked but has no transactions,
|
|
# new SimplefinAccount is unlinked but has transactions
|
|
|
|
# Make the original account "stale" (no transactions)
|
|
@simplefin_account.update!(raw_transactions_payload: [])
|
|
|
|
# Create a "new" SimplefinAccount with the same name but different account_id
|
|
# This simulates what happens when SimpleFIN generates new IDs after re-adding
|
|
new_simplefin_account = SimplefinAccount.create!(
|
|
simplefin_item: @simplefin_item,
|
|
name: "Roth IRA", # Same name as original
|
|
account_id: "ACT-investment-456-NEW", # New ID
|
|
currency: "USD",
|
|
account_type: "investment",
|
|
current_balance: 12199.06,
|
|
raw_transactions_payload: [
|
|
{
|
|
"id" => "TRN-new-transaction-001",
|
|
"posted" => 1766417520,
|
|
"amount" => "5.00",
|
|
"description" => "New Dividend",
|
|
"payee" => "Dividend",
|
|
"memo" => "New Dividend Payment"
|
|
}
|
|
]
|
|
)
|
|
# New account is NOT linked (this is the problem we're fixing)
|
|
assert_nil new_simplefin_account.account
|
|
|
|
# Before repair: @simplefin_account is linked (but stale), new_simplefin_account is unlinked
|
|
assert_equal @simplefin_account.id, @account.reload.simplefin_account_id
|
|
|
|
# Process accounts - should repair the stale linkage
|
|
@simplefin_item.process_accounts
|
|
|
|
# After repair: new_simplefin_account should be linked
|
|
@account.reload
|
|
assert_equal new_simplefin_account.id, @account.simplefin_account_id, "Expected linkage to transfer to new_simplefin_account (#{new_simplefin_account.id}) but got #{@account.simplefin_account_id}"
|
|
|
|
# Old SimplefinAccount should still exist but be cleared of data
|
|
@simplefin_account.reload
|
|
assert_equal [], @simplefin_account.raw_transactions_payload
|
|
|
|
# Transaction from new SimplefinAccount should be created
|
|
assert_equal 1, @account.entries.count
|
|
entry = @account.entries.first
|
|
assert_equal "simplefin_TRN-new-transaction-001", entry.external_id
|
|
assert_equal BigDecimal("-5.00"), entry.amount
|
|
end
|
|
|
|
test "does not repair linkage when names dont match" do
|
|
# Make original stale
|
|
@simplefin_account.update!(raw_transactions_payload: [])
|
|
|
|
# Create new with DIFFERENT name
|
|
new_simplefin_account = SimplefinAccount.create!(
|
|
simplefin_item: @simplefin_item,
|
|
name: "Different Account Name", # Different name
|
|
account_id: "ACT-different-456",
|
|
currency: "USD",
|
|
account_type: "investment",
|
|
current_balance: 1000.00,
|
|
raw_transactions_payload: [
|
|
{ "id" => "TRN-different", "posted" => 1766417520, "amount" => "10.00", "description" => "Test" }
|
|
]
|
|
)
|
|
|
|
original_linkage = @account.simplefin_account_id
|
|
|
|
@simplefin_item.process_accounts
|
|
|
|
# Should NOT have transferred linkage because names don't match
|
|
@account.reload
|
|
assert_equal original_linkage, @account.simplefin_account_id
|
|
assert_equal 0, @account.entries.count
|
|
end
|
|
|
|
test "repairs linkage and merges transactions when both old and new have data" do
|
|
# Both accounts have transactions - repair should still happen and merge them
|
|
assert @simplefin_account.raw_transactions_payload.any?
|
|
|
|
# Create new with same name
|
|
new_simplefin_account = SimplefinAccount.create!(
|
|
simplefin_item: @simplefin_item,
|
|
name: "Roth IRA",
|
|
account_id: "ACT-investment-456-NEW",
|
|
currency: "USD",
|
|
account_type: "investment",
|
|
current_balance: 12199.06,
|
|
raw_transactions_payload: [
|
|
{ "id" => "TRN-new", "posted" => 1766417520, "amount" => "5.00", "description" => "New" }
|
|
]
|
|
)
|
|
|
|
@simplefin_item.process_accounts
|
|
|
|
# Should transfer linkage to new account (repair by name match)
|
|
@account.reload
|
|
assert_equal new_simplefin_account.id, @account.simplefin_account_id
|
|
|
|
# Transactions should be merged: 3 from old + 1 from new = 4 total
|
|
assert_equal 4, @account.entries.count
|
|
|
|
# Old account should be cleared
|
|
@simplefin_account.reload
|
|
assert_equal [], @simplefin_account.raw_transactions_payload
|
|
end
|
|
end
|