mirror of
https://github.com/we-promise/sure.git
synced 2026-04-13 09:07:25 +00:00
266 lines
7.1 KiB
Ruby
266 lines
7.1 KiB
Ruby
require "test_helper"
|
|
|
|
class LunchflowAccount::Investments::HoldingsProcessorTest < ActiveSupport::TestCase
|
|
setup do
|
|
@lunchflow_account = lunchflow_accounts(:investment_account)
|
|
@account = accounts(:investment)
|
|
|
|
# Create account_provider to link lunchflow_account to account
|
|
@account_provider = AccountProvider.create!(
|
|
account: @account,
|
|
provider: @lunchflow_account
|
|
)
|
|
|
|
# Reload to ensure associations are loaded
|
|
@lunchflow_account.reload
|
|
end
|
|
|
|
test "creates holding records from Lunchflow holdings snapshot" do
|
|
# Verify setup is correct
|
|
assert_not_nil @lunchflow_account.current_account, "Account should be linked"
|
|
assert_equal "Investment", @lunchflow_account.current_account.accountable_type
|
|
|
|
# Use unique dates to avoid conflicts with existing fixture holdings
|
|
test_holdings_payload = [
|
|
{
|
|
"security" => {
|
|
"name" => "iShares Inc MSCI Brazil",
|
|
"currency" => "USD",
|
|
"tickerSymbol" => "NEWTEST1",
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 5,
|
|
"price" => 42.15,
|
|
"value" => 210.75,
|
|
"costBasis" => 100.0,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_test_123"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"security" => {
|
|
"name" => "Test Security",
|
|
"currency" => "USD",
|
|
"tickerSymbol" => "NEWTEST2",
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 10,
|
|
"price" => 150.0,
|
|
"value" => 1500.0,
|
|
"costBasis" => 1200.0,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_test_456"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
@lunchflow_account.update!(raw_holdings_payload: test_holdings_payload)
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
assert_difference "Holding.count", 2 do
|
|
processor.process
|
|
end
|
|
|
|
holdings = Holding.where(account: @account).where.not(external_id: nil).order(:created_at)
|
|
|
|
assert_equal 2, holdings.count
|
|
assert_equal "USD", holdings.first.currency
|
|
assert_equal "lunchflow_hld_test_123", holdings.first.external_id
|
|
end
|
|
|
|
test "skips processing for non-investment accounts" do
|
|
# Create a depository account
|
|
depository_account = accounts(:depository)
|
|
depository_lunchflow_account = LunchflowAccount.create!(
|
|
lunchflow_item: lunchflow_items(:one),
|
|
account_id: "lf_depository",
|
|
name: "Depository",
|
|
currency: "USD"
|
|
)
|
|
|
|
AccountProvider.create!(
|
|
account: depository_account,
|
|
provider: depository_lunchflow_account
|
|
)
|
|
depository_lunchflow_account.reload
|
|
|
|
test_holdings_payload = [
|
|
{
|
|
"security" => { "name" => "Test", "tickerSymbol" => "TEST", "currency" => "USD" },
|
|
"quantity" => 10,
|
|
"price" => 100.0,
|
|
"value" => 1000.0,
|
|
"costBasis" => nil,
|
|
"currency" => "USD",
|
|
"raw" => { "quiltt" => { "id" => "hld_skip" } }
|
|
}
|
|
]
|
|
|
|
depository_lunchflow_account.update!(raw_holdings_payload: test_holdings_payload)
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(depository_lunchflow_account)
|
|
|
|
assert_no_difference "Holding.count" do
|
|
processor.process
|
|
end
|
|
end
|
|
|
|
test "creates synthetic ticker when tickerSymbol is missing" do
|
|
test_holdings_payload = [
|
|
{
|
|
"security" => {
|
|
"name" => "Custom 401k Fund",
|
|
"currency" => "USD",
|
|
"tickerSymbol" => nil,
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 100,
|
|
"price" => 50.0,
|
|
"value" => 5000.0,
|
|
"costBasis" => 4500.0,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_custom_123"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
@lunchflow_account.update!(raw_holdings_payload: test_holdings_payload)
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
assert_difference "Holding.count", 1 do
|
|
processor.process
|
|
end
|
|
|
|
holding = Holding.where(account: @account).where.not(external_id: nil).last
|
|
assert_equal "lunchflow_hld_custom_123", holding.external_id
|
|
assert_equal 100, holding.qty
|
|
assert_equal 5000.0, holding.amount
|
|
end
|
|
|
|
test "skips zero value holdings" do
|
|
test_holdings_payload = [
|
|
{
|
|
"security" => {
|
|
"name" => "Zero Position",
|
|
"currency" => "USD",
|
|
"tickerSymbol" => "ZERO",
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 0,
|
|
"price" => 0,
|
|
"value" => 0,
|
|
"costBasis" => nil,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_zero"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
@lunchflow_account.update!(raw_holdings_payload: test_holdings_payload)
|
|
|
|
Security::Resolver.any_instance.stubs(:resolve).returns(securities(:aapl))
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
assert_no_difference "Holding.count" do
|
|
processor.process
|
|
end
|
|
end
|
|
|
|
test "handles empty holdings payload gracefully" do
|
|
@lunchflow_account.update!(raw_holdings_payload: [])
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
assert_no_difference "Holding.count" do
|
|
processor.process
|
|
end
|
|
end
|
|
|
|
test "handles nil holdings payload gracefully" do
|
|
@lunchflow_account.update!(raw_holdings_payload: nil)
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
assert_no_difference "Holding.count" do
|
|
processor.process
|
|
end
|
|
end
|
|
|
|
test "continues processing other holdings when one fails" do
|
|
test_holdings_payload = [
|
|
{
|
|
"security" => {
|
|
"name" => "Good Holding",
|
|
"currency" => "USD",
|
|
"tickerSymbol" => "GOODTEST",
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 10,
|
|
"price" => 100.0,
|
|
"value" => 1000.0,
|
|
"costBasis" => nil,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_good"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"security" => {
|
|
"name" => nil, # This will cause it to skip (no name, no symbol)
|
|
"currency" => "USD",
|
|
"tickerSymbol" => nil,
|
|
"figi" => nil,
|
|
"cusp" => nil,
|
|
"isin" => nil
|
|
},
|
|
"quantity" => 5,
|
|
"price" => 50.0,
|
|
"value" => 250.0,
|
|
"costBasis" => nil,
|
|
"currency" => "USD",
|
|
"raw" => {
|
|
"quiltt" => {
|
|
"id" => "hld_bad"
|
|
}
|
|
}
|
|
}
|
|
]
|
|
|
|
@lunchflow_account.update!(raw_holdings_payload: test_holdings_payload)
|
|
|
|
processor = LunchflowAccount::Investments::HoldingsProcessor.new(@lunchflow_account)
|
|
|
|
# Should create 1 holding (the good one)
|
|
assert_difference "Holding.count", 1 do
|
|
processor.process
|
|
end
|
|
end
|
|
end
|