1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-05 05:25:24 +02:00

Data provider simplification, tests, and documentation (#1997)

* Ignore env.test from source control

* Simplification of providers interface

* Synth tests

* Update money to use new find rates method

* Remove unused issues code

* Additional issue feature removals

* Update price data fetching and tests

* Update documentation for providers

* Security test fixes

* Fix self host test

* Update synth usage data access

* Remove AI pr schema changes
This commit is contained in:
Zach Gollwitzer 2025-03-17 11:54:53 -04:00 committed by GitHub
parent dd75cadebc
commit f65b93a352
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
95 changed files with 2014 additions and 1638 deletions

View file

@ -2,7 +2,7 @@ require "test_helper"
require "ostruct"
class Account::ConvertibleTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
include Account::EntriesTestHelper, ProviderTestHelper
setup do
@family = families(:empty)
@ -16,33 +16,28 @@ class Account::ConvertibleTest < ActiveSupport::TestCase
end
test "syncs required exchange rates for an account" do
create_valuation(account: @account, date: 5.days.ago.to_date, amount: 9500, currency: "EUR")
create_valuation(account: @account, date: 1.day.ago.to_date, amount: 9500, currency: "EUR")
# Since we had a valuation 5 days ago, this account starts 6 days ago and needs daily exchange rates looking forward
assert_equal 6.days.ago.to_date, @account.start_date
# Since we had a valuation 1 day ago, this account starts 2 days ago and needs daily exchange rates looking forward
assert_equal 2.days.ago.to_date, @account.start_date
ExchangeRate.delete_all
provider_response = provider_success_response(
ExchangeRate::Provideable::FetchRatesData.new(
rates: [
ExchangeRate.new(from_currency: "EUR", to_currency: "USD", date: 2.days.ago.to_date, rate: 1.1),
ExchangeRate.new(from_currency: "EUR", to_currency: "USD", date: 1.day.ago.to_date, rate: 1.2),
ExchangeRate.new(from_currency: "EUR", to_currency: "USD", date: Date.current, rate: 1.3)
]
)
)
@provider.expects(:fetch_exchange_rates)
.with(
from: "EUR",
to: "USD",
start_date: 6.days.ago.to_date,
end_date: Date.current
).returns(
OpenStruct.new(
success?: true,
rates: [
OpenStruct.new(date: 6.days.ago.to_date, rate: 1.1),
OpenStruct.new(date: 5.days.ago.to_date, rate: 1.2),
OpenStruct.new(date: 4.days.ago.to_date, rate: 1.3),
OpenStruct.new(date: 3.days.ago.to_date, rate: 1.4),
OpenStruct.new(date: 2.days.ago.to_date, rate: 1.5),
OpenStruct.new(date: 1.day.ago.to_date, rate: 1.6),
OpenStruct.new(date: Date.current, rate: 1.7)
]
)
)
.with(from: "EUR", to: "USD", start_date: 2.days.ago.to_date, end_date: Date.current)
.returns(provider_response)
assert_difference "ExchangeRate.count", 7 do
assert_difference "ExchangeRate.count", 3 do
@account.sync_required_exchange_rates
end
end

View file

@ -1,63 +1,93 @@
require "test_helper"
class Account::Holding::PortfolioCacheTest < ActiveSupport::TestCase
include Account::EntriesTestHelper
include Account::EntriesTestHelper, ProviderTestHelper
setup do
# Prices, highest to lowest priority
@db_price = 210
@provider_price = 220
@trade_price = 200
@holding_price = 250
@provider = mock
Security.stubs(:provider).returns(@provider)
@account = families(:empty).accounts.create!(name: "Test Brokerage", balance: 10000, currency: "USD", accountable: Investment.new)
@test_security = Security.create!(name: "Test Security", ticker: "TEST")
@account = families(:empty).accounts.create!(
name: "Test Brokerage",
balance: 10000,
currency: "USD",
accountable: Investment.new
)
@trade = create_trade(@test_security, account: @account, qty: 1, date: Date.current, price: @trade_price)
@holding = Account::Holding.create!(security: @test_security, account: @account, date: Date.current, qty: 1, price: @holding_price, amount: @holding_price, currency: "USD")
Security::Price.create!(security: @test_security, date: Date.current, price: @db_price)
@security = Security.create!(name: "Test Security", ticker: "TEST", exchange_operating_mic: "TEST")
@trade = create_trade(@security, account: @account, qty: 1, date: 2.days.ago.to_date, price: 210.23).account_trade
end
test "gets price from DB if available" do
cache = Account::Holding::PortfolioCache.new(@account)
db_price = 210
assert_equal @db_price, cache.get_price(@test_security.id, Date.current).price
Security::Price.create!(
security: @security,
date: Date.current,
price: db_price
)
expect_provider_prices([], start_date: @account.start_date)
cache = Account::Holding::PortfolioCache.new(@account)
assert_equal db_price, cache.get_price(@security.id, Date.current).price
end
test "if no price in DB, try fetching from provider" do
Security::Price.destroy_all
Security::Price.expects(:find_prices)
.with(security: @test_security, start_date: @account.start_date, end_date: Date.current)
.returns([
Security::Price.new(security: @test_security, date: Date.current, price: @provider_price, currency: "USD")
])
Security::Price.delete_all
provider_price = Security::Price.new(
security: @security,
date: Date.current,
price: 220,
currency: "USD"
)
expect_provider_prices([ provider_price ], start_date: @account.start_date)
cache = Account::Holding::PortfolioCache.new(@account)
assert_equal @provider_price, cache.get_price(@test_security.id, Date.current).price
assert_equal provider_price.price, cache.get_price(@security.id, Date.current).price
end
test "if no price from db or provider, try getting the price from trades" do
Security::Price.destroy_all # No DB prices
Security::Price.expects(:find_prices)
.with(security: @test_security, start_date: @account.start_date, end_date: Date.current)
.returns([]) # No provider prices
Security::Price.destroy_all
expect_provider_prices([], start_date: @account.start_date)
cache = Account::Holding::PortfolioCache.new(@account)
assert_equal @trade_price, cache.get_price(@test_security.id, Date.current).price
assert_equal @trade.price, cache.get_price(@security.id, @trade.entry.date).price
end
test "if no price from db, provider, or trades, search holdings" do
Security::Price.destroy_all # No DB prices
Security::Price.expects(:find_prices)
.with(security: @test_security, start_date: @account.start_date, end_date: Date.current)
.returns([]) # No provider prices
Security::Price.delete_all
Account::Entry.delete_all
@account.entries.destroy_all # No prices from trades
holding = Account::Holding.create!(
security: @security,
account: @account,
date: Date.current,
qty: 1,
price: 250,
amount: 250 * 1,
currency: "USD"
)
expect_provider_prices([], start_date: @account.start_date)
cache = Account::Holding::PortfolioCache.new(@account, use_holdings: true)
assert_equal @holding_price, cache.get_price(@test_security.id, Date.current).price
assert_equal holding.price, cache.get_price(@security.id, holding.date).price
end
private
def expect_provider_prices(prices, start_date:, end_date: Date.current)
@provider.expects(:fetch_security_prices)
.with(@security, start_date: start_date, end_date: end_date)
.returns(
provider_success_response(
Security::Provideable::PricesData.new(
prices: prices
)
)
)
end
end