mirror of
https://github.com/we-promise/sure.git
synced 2026-04-17 11:04:14 +00:00
211 lines
8.0 KiB
Ruby
211 lines
8.0 KiB
Ruby
require "test_helper"
|
||
|
||
class Security::ResolverTest < ActiveSupport::TestCase
|
||
test "resolves DB security" do
|
||
# Given an existing security in the DB that exactly matches the lookup params
|
||
db_security = Security.create!(ticker: "TSLA", exchange_operating_mic: "XNAS", country_code: "US")
|
||
|
||
# The resolver should return the DB record and never hit the provider
|
||
Security.expects(:search_provider).never
|
||
|
||
resolved = Security::Resolver.new("TSLA", exchange_operating_mic: "XNAS", country_code: "US").resolve
|
||
|
||
assert_equal db_security, resolved
|
||
end
|
||
|
||
test "resolves exact provider match" do
|
||
# Provider returns multiple results, one of which exactly matches symbol + exchange (and country)
|
||
exact_match = Security.new(ticker: "NVDA", exchange_operating_mic: "XNAS", country_code: "US")
|
||
near_miss = Security.new(ticker: "NVDA", exchange_operating_mic: "XNYS", country_code: "US")
|
||
|
||
Security.expects(:search_provider)
|
||
.with("NVDA", exchange_operating_mic: "XNAS", country_code: "US")
|
||
.returns([ near_miss, exact_match ])
|
||
|
||
assert_difference "Security.count", 1 do
|
||
resolved = Security::Resolver.new("NVDA", exchange_operating_mic: "XNAS", country_code: "US").resolve
|
||
|
||
assert resolved.persisted?
|
||
assert_equal "NVDA", resolved.ticker
|
||
assert_equal "XNAS", resolved.exchange_operating_mic
|
||
assert_equal "US", resolved.country_code
|
||
refute resolved.offline, "Exact provider matches should not be marked offline"
|
||
end
|
||
end
|
||
|
||
test "resolves close provider match" do
|
||
# No exact match – resolver should choose the most relevant close match based on exchange + country ranking
|
||
preferred = Security.new(ticker: "TEST1", exchange_operating_mic: "XNAS", country_code: "US")
|
||
other = Security.new(ticker: "TEST2", exchange_operating_mic: "XNYS", country_code: "GB")
|
||
|
||
# Return in reverse-priority order to prove the sorter works
|
||
Security.expects(:search_provider)
|
||
.with("TEST", exchange_operating_mic: "XNAS")
|
||
.returns([ other, preferred ])
|
||
|
||
assert_difference "Security.count", 1 do
|
||
resolved = Security::Resolver.new("TEST", exchange_operating_mic: "XNAS").resolve
|
||
|
||
assert resolved.persisted?
|
||
assert_equal "TEST1", resolved.ticker
|
||
assert_equal "XNAS", resolved.exchange_operating_mic
|
||
assert_equal "US", resolved.country_code
|
||
refute resolved.offline, "Provider matches should not be marked offline"
|
||
end
|
||
end
|
||
|
||
test "resolves offline security" do
|
||
Security.expects(:search_provider).returns([])
|
||
|
||
assert_difference "Security.count", 1 do
|
||
resolved = Security::Resolver.new("FOO").resolve
|
||
|
||
assert resolved.persisted?, "Offline security should be saved"
|
||
assert_equal "FOO", resolved.ticker
|
||
assert resolved.offline, "Offline securities should be flagged offline"
|
||
end
|
||
end
|
||
|
||
test "returns nil when symbol blank" do
|
||
assert_raises(ArgumentError) { Security::Resolver.new(nil).resolve }
|
||
assert_raises(ArgumentError) { Security::Resolver.new("").resolve }
|
||
end
|
||
|
||
test "persists explicit price_provider on DB match" do
|
||
db_security = Security.create!(ticker: "CSPX", exchange_operating_mic: "XLON", country_code: "GB")
|
||
|
||
Security.expects(:search_provider).never
|
||
Setting.stubs(:enabled_securities_providers).returns([ "tiingo" ])
|
||
|
||
resolved = Security::Resolver.new(
|
||
"CSPX",
|
||
exchange_operating_mic: "XLON",
|
||
country_code: "GB",
|
||
price_provider: "tiingo"
|
||
).resolve
|
||
|
||
assert_equal db_security, resolved
|
||
assert_equal "tiingo", resolved.reload.price_provider
|
||
end
|
||
|
||
test "persists price_provider on provider match" do
|
||
match = Security.new(ticker: "VWCE", exchange_operating_mic: "XETR", country_code: "DE", price_provider: "eodhd")
|
||
|
||
Security.expects(:search_provider)
|
||
.with("VWCE", exchange_operating_mic: "XETR")
|
||
.returns([ match ])
|
||
|
||
Setting.stubs(:enabled_securities_providers).returns([ "eodhd" ])
|
||
|
||
resolved = Security::Resolver.new(
|
||
"VWCE",
|
||
exchange_operating_mic: "XETR",
|
||
price_provider: "eodhd"
|
||
).resolve
|
||
|
||
assert resolved.persisted?
|
||
assert_equal "eodhd", resolved.price_provider
|
||
end
|
||
|
||
test "rejects unknown price_provider" do
|
||
db_security = Security.create!(ticker: "AAPL2", exchange_operating_mic: "XNAS", country_code: "US")
|
||
|
||
Security.expects(:search_provider).never
|
||
|
||
resolved = Security::Resolver.new(
|
||
"AAPL2",
|
||
exchange_operating_mic: "XNAS",
|
||
country_code: "US",
|
||
price_provider: "fake_provider"
|
||
).resolve
|
||
|
||
assert_equal db_security, resolved
|
||
assert_nil resolved.reload.price_provider, "Unknown providers should be rejected"
|
||
end
|
||
|
||
test "resolves Binance crypto match for a non-AE family" do
|
||
# Regression: BinancePublic search results carry country_code="AE" (the ISO
|
||
# 10383 MIC country), but the transactions controller passes the family's
|
||
# country (e.g. "US"). The resolver used to require an exact country match
|
||
# for both exact and close paths, so non-AE families would fall through to
|
||
# offline_security for every Binance pick — the user saw their BTCUSD
|
||
# holding resolve to an offline security that never fetched prices.
|
||
binance_match = Security.new(
|
||
ticker: "BTCUSD",
|
||
exchange_operating_mic: "BNCX",
|
||
country_code: nil,
|
||
price_provider: "binance_public"
|
||
)
|
||
|
||
Security.expects(:search_provider)
|
||
.with("BTCUSD", exchange_operating_mic: "BNCX", country_code: "US")
|
||
.returns([ binance_match ])
|
||
|
||
Setting.stubs(:enabled_securities_providers).returns([ "binance_public" ])
|
||
|
||
resolved = Security::Resolver.new(
|
||
"BTCUSD",
|
||
exchange_operating_mic: "BNCX",
|
||
country_code: "US",
|
||
price_provider: "binance_public"
|
||
).resolve
|
||
|
||
assert resolved.persisted?
|
||
refute resolved.offline, "Binance security must not fall through to offline_security"
|
||
assert_equal "BTCUSD", resolved.ticker
|
||
assert_equal "BNCX", resolved.exchange_operating_mic
|
||
assert_equal "binance_public", resolved.price_provider
|
||
end
|
||
|
||
test "resolved provider match is persisted with nil name/logo_url" do
|
||
# Documents that find_or_create_provider_match! intentionally copies only
|
||
# ticker, MIC, country_code, and price_provider from the match — not name
|
||
# or logo_url. This means Security#import_provider_details always has
|
||
# blank metadata on first resolution and runs fetch_security_info +
|
||
# probe_brandfetch_crypto_coverage! as expected. Regression guard: if
|
||
# someone adds name/logo copying to the resolver, the Brandfetch
|
||
# coverage probe would be bypassed on first sync.
|
||
match = Security.new(
|
||
ticker: "BTCUSD",
|
||
exchange_operating_mic: "BNCX",
|
||
country_code: nil,
|
||
name: "BTC",
|
||
logo_url: "https://cdn.brandfetch.io/crypto/BTC/icon/fallback/lettermark/w/120/h/120?c=test",
|
||
price_provider: "binance_public"
|
||
)
|
||
|
||
Security.expects(:search_provider)
|
||
.with("BTCUSD", exchange_operating_mic: "BNCX", country_code: "US")
|
||
.returns([ match ])
|
||
|
||
Setting.stubs(:enabled_securities_providers).returns([ "binance_public" ])
|
||
|
||
resolved = Security::Resolver.new(
|
||
"BTCUSD",
|
||
exchange_operating_mic: "BNCX",
|
||
country_code: "US",
|
||
price_provider: "binance_public"
|
||
).resolve
|
||
|
||
assert_nil resolved.reload.name, "Resolver must not copy name from the search match"
|
||
assert_nil resolved.logo_url, "Resolver must not copy logo_url from the search match"
|
||
end
|
||
|
||
test "rejects disabled price_provider" do
|
||
db_security = Security.create!(ticker: "GOOG2", exchange_operating_mic: "XNAS", country_code: "US")
|
||
|
||
Security.expects(:search_provider).never
|
||
Setting.stubs(:enabled_securities_providers).returns([ "twelve_data" ])
|
||
|
||
resolved = Security::Resolver.new(
|
||
"GOOG2",
|
||
exchange_operating_mic: "XNAS",
|
||
country_code: "US",
|
||
price_provider: "tiingo"
|
||
).resolve
|
||
|
||
assert_equal db_security, resolved
|
||
assert_nil resolved.reload.price_provider, "Disabled providers should be rejected"
|
||
end
|
||
end
|