mirror of
https://github.com/we-promise/sure.git
synced 2026-04-20 20:44:08 +00:00
Add tests and refactor for Simplefin holdings import and processing
- Introduced tests for importer post-import logic and `SimplefinHoldingsApplyJob`. - Refactored `ProviderImportAdapter` to improve holding resolution strategy. - Added handling of investment and crypto holdings in importer with debounce logic for job enqueuing. - Updated rake task to use `SimplefinHoldingsApplyJob` for holding materialization.
This commit is contained in:
63
test/jobs/simplefin_holdings_apply_job_test.rb
Normal file
63
test/jobs/simplefin_holdings_apply_job_test.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
require "test_helper"
|
||||
|
||||
class SimplefinHoldingsApplyJobTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@family = families(:dylan_family)
|
||||
@item = SimplefinItem.create!(family: @family, name: "SF", access_url: "https://example.com/x")
|
||||
|
||||
@account = accounts(:investment)
|
||||
|
||||
# Link SFA to existing investment account via legacy association for simplicity
|
||||
@sfa = @item.simplefin_accounts.create!(
|
||||
name: "Invest",
|
||||
account_id: "sf_invest_1",
|
||||
currency: "USD",
|
||||
account_type: "investment",
|
||||
current_balance: 10_000
|
||||
)
|
||||
@account.update!(simplefin_account_id: @sfa.id)
|
||||
end
|
||||
|
||||
test "materializes holdings from raw_holdings_payload and is idempotent" do
|
||||
# Two holdings: one AAPL (existing security), one NEWCO (should be created)
|
||||
@sfa.update!(
|
||||
raw_holdings_payload: [
|
||||
{
|
||||
"id" => "h1",
|
||||
"symbol" => "AAPL",
|
||||
"quantity" => 10,
|
||||
"market_value" => 2000,
|
||||
"currency" => "USD",
|
||||
"as_of" => (Date.current - 2.days).to_s
|
||||
},
|
||||
{
|
||||
"id" => "h2",
|
||||
"symbol" => "NEWCO",
|
||||
"quantity" => 5,
|
||||
"market_value" => 500,
|
||||
"currency" => "USD",
|
||||
"as_of" => Date.current.to_s
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
assert_difference "Holding.where(account: @account).count", 2 do
|
||||
SimplefinHoldingsApplyJob.perform_now(@sfa.id)
|
||||
end
|
||||
|
||||
|
||||
# Running again should not create duplicates (external_id uniqueness)
|
||||
assert_no_difference "Holding.where(account: @account).count" do
|
||||
SimplefinHoldingsApplyJob.perform_now(@sfa.id)
|
||||
end
|
||||
|
||||
holdings = @account.holdings.order(:external_id)
|
||||
aapl = holdings.find { |h| h.security.ticker == "AAPL" }
|
||||
refute_nil aapl
|
||||
assert_equal 10, aapl.qty
|
||||
assert_equal Money.new(2000, "USD"), aapl.amount_money
|
||||
|
||||
newco_sec = Security.find_by(ticker: "NEWCO")
|
||||
refute_nil newco_sec, "should create NEWCO security via resolver when missing"
|
||||
end
|
||||
end
|
||||
74
test/models/simplefin_item/importer_post_import_test.rb
Normal file
74
test/models/simplefin_item/importer_post_import_test.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
require "test_helper"
|
||||
|
||||
class SimplefinItem::ImporterPostImportTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@family = families(:dylan_family)
|
||||
@item = SimplefinItem.create!(family: @family, name: "SF Conn", access_url: "https://example.com/access")
|
||||
@sync = Sync.create!(syncable: @item)
|
||||
end
|
||||
|
||||
test "credit account import updates available_credit when available-balance provided" do
|
||||
credit_acct = accounts(:credit_card)
|
||||
|
||||
sfa = @item.simplefin_accounts.create!(
|
||||
name: "CC",
|
||||
account_id: "sf_cc_1",
|
||||
currency: "USD",
|
||||
account_type: "credit",
|
||||
available_balance: 0
|
||||
)
|
||||
# Link via legacy association
|
||||
credit_acct.update!(simplefin_account_id: sfa.id)
|
||||
|
||||
importer = SimplefinItem::Importer.new(@item, simplefin_provider: mock(), sync: @sync)
|
||||
|
||||
account_data = {
|
||||
id: sfa.account_id,
|
||||
name: "CC",
|
||||
balance: -1200.0, # liabilities often negative from provider
|
||||
currency: "USD",
|
||||
"available-balance": 5000.0
|
||||
}
|
||||
|
||||
# Call private method for focused unit test
|
||||
importer.send(:import_account, account_data)
|
||||
|
||||
assert_equal 5000.0, credit_acct.reload.credit_card.available_credit
|
||||
end
|
||||
|
||||
test "investment import recalculates cash_balance when holdings payload changes" do
|
||||
invest_acct = accounts(:investment)
|
||||
|
||||
sfa = @item.simplefin_accounts.create!(
|
||||
name: "Invest",
|
||||
account_id: "sf_inv_1",
|
||||
currency: "USD",
|
||||
account_type: "investment",
|
||||
current_balance: 0
|
||||
)
|
||||
invest_acct.update!(simplefin_account_id: sfa.id)
|
||||
|
||||
importer = SimplefinItem::Importer.new(@item, simplefin_provider: mock(), sync: @sync)
|
||||
|
||||
holdings = [
|
||||
{ "id" => "h1", "symbol" => "AAPL", "quantity" => 10, "market_value" => 2000, "currency" => "USD", "as_of" => Date.current.to_s },
|
||||
{ "id" => "h2", "symbol" => "MSFT", "quantity" => 20, "market_value" => 4000, "currency" => "USD", "as_of" => Date.current.to_s }
|
||||
]
|
||||
|
||||
account_data = {
|
||||
id: sfa.account_id,
|
||||
name: "Invest",
|
||||
balance: 10000.0,
|
||||
currency: "USD",
|
||||
holdings: holdings
|
||||
}
|
||||
|
||||
# Prevent the job from running in this unit test; we only care about cash balance recompute
|
||||
SimplefinHoldingsApplyJob.expects(:perform_later).once
|
||||
|
||||
importer.send(:import_account, account_data)
|
||||
|
||||
# Cash balance should be total balance (10_000) minus market_value sum (6_000) = 4_000
|
||||
assert_equal 4000.0, invest_acct.reload.cash_balance
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user