mirror of
https://github.com/we-promise/sure.git
synced 2026-04-18 19:44:09 +00:00
Breaks our Plaid sync process out into more manageable classes. Notably, this moves the sync process to a distinct, 2-step flow: 1. Import stage - we first make API calls and import Plaid data to "mirror" tables 2. Processing stage - read the raw data, apply business rules, build internal domain models and sync balances This provides several benefits: - Plaid syncs can now be "replayed" without fetching API data again - Mirror tables provide better audit and debugging capabilities - Eliminates the "all or nothing" sync behavior that is currently in place, which is brittle
116 lines
3.4 KiB
Ruby
116 lines
3.4 KiB
Ruby
require "test_helper"
|
|
|
|
class PlaidAccount::Investments::SecurityResolverTest < ActiveSupport::TestCase
|
|
setup do
|
|
@upstream_resolver = mock("Security::Resolver")
|
|
@plaid_account = plaid_accounts(:one)
|
|
@resolver = PlaidAccount::Investments::SecurityResolver.new(@plaid_account)
|
|
end
|
|
|
|
test "handles missing plaid security" do
|
|
missing_id = "missing_security_id"
|
|
|
|
# Ensure there are *no* securities that reference the missing ID
|
|
@plaid_account.update!(raw_investments_payload: {
|
|
securities: [
|
|
{
|
|
"security_id" => "some_other_id",
|
|
"ticker_symbol" => "FOO",
|
|
"type" => "equity",
|
|
"market_identifier_code" => "XNAS"
|
|
}
|
|
]
|
|
})
|
|
|
|
Security::Resolver.expects(:new).never
|
|
Sentry.stubs(:capture_exception)
|
|
|
|
response = @resolver.resolve(plaid_security_id: missing_id)
|
|
|
|
assert_nil response.security
|
|
refute response.cash_equivalent?
|
|
refute response.brokerage_cash?
|
|
end
|
|
|
|
test "identifies brokerage cash plaid securities" do
|
|
brokerage_cash_id = "brokerage_cash_security_id"
|
|
|
|
@plaid_account.update!(raw_investments_payload: {
|
|
securities: [
|
|
{
|
|
"security_id" => brokerage_cash_id,
|
|
"ticker_symbol" => "CUR:USD", # Plaid brokerage cash ticker
|
|
"type" => "cash",
|
|
"is_cash_equivalent" => true
|
|
}
|
|
]
|
|
})
|
|
|
|
Security::Resolver.expects(:new).never
|
|
|
|
response = @resolver.resolve(plaid_security_id: brokerage_cash_id)
|
|
|
|
assert_nil response.security
|
|
assert response.cash_equivalent?
|
|
assert response.brokerage_cash?
|
|
end
|
|
|
|
test "identifies cash equivalent plaid securities" do
|
|
mmf_security_id = "money_market_security_id"
|
|
|
|
@plaid_account.update!(raw_investments_payload: {
|
|
securities: [
|
|
{
|
|
"security_id" => mmf_security_id,
|
|
"ticker_symbol" => "VMFXX", # Vanguard Federal Money Market Fund
|
|
"type" => "mutual fund",
|
|
"is_cash_equivalent" => true,
|
|
"market_identifier_code" => "XNAS"
|
|
}
|
|
]
|
|
})
|
|
|
|
resolved_security = Security.create!(ticker: "VMFXX", exchange_operating_mic: "XNAS")
|
|
|
|
Security::Resolver.expects(:new)
|
|
.with("VMFXX", exchange_operating_mic: "XNAS")
|
|
.returns(@upstream_resolver)
|
|
@upstream_resolver.expects(:resolve).returns(resolved_security)
|
|
|
|
response = @resolver.resolve(plaid_security_id: mmf_security_id)
|
|
|
|
assert_equal resolved_security, response.security
|
|
assert response.cash_equivalent?
|
|
refute response.brokerage_cash?
|
|
end
|
|
|
|
test "resolves normal plaid securities" do
|
|
security_id = "regular_security_id"
|
|
|
|
@plaid_account.update!(raw_investments_payload: {
|
|
securities: [
|
|
{
|
|
"security_id" => security_id,
|
|
"ticker_symbol" => "IVV",
|
|
"type" => "etf",
|
|
"is_cash_equivalent" => false,
|
|
"market_identifier_code" => "XNAS"
|
|
}
|
|
]
|
|
})
|
|
|
|
resolved_security = Security.create!(ticker: "IVV", exchange_operating_mic: "XNAS")
|
|
|
|
Security::Resolver.expects(:new)
|
|
.with("IVV", exchange_operating_mic: "XNAS")
|
|
.returns(@upstream_resolver)
|
|
@upstream_resolver.expects(:resolve).returns(resolved_security)
|
|
|
|
response = @resolver.resolve(plaid_security_id: security_id)
|
|
|
|
assert_equal resolved_security, response.security
|
|
refute response.cash_equivalent? # Normal securities are not cash equivalent
|
|
refute response.brokerage_cash?
|
|
end
|
|
end
|