from __future__ import annotations from app.domain.portfolio_math import resolve_portfolio_spot_from_quote from app.models.portfolio import PortfolioConfig from app.services.alerts import build_portfolio_alert_context def test_overview_converts_gld_share_quote_to_ounce_equivalent_spot() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) spot_price, source, updated_at = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, ) portfolio = build_portfolio_alert_context( config, spot_price=spot_price, source=source, updated_at=updated_at, ) assert spot_price == 4041.9 assert source == "yfinance" assert updated_at == "2026-03-24T00:00:00+00:00" assert portfolio["gold_value"] == 889218.0 assert portfolio["net_equity"] == 744218.0 assert round(float(portfolio["margin_call_price"]), 2) == 878.79 def test_overview_fails_closed_to_configured_entry_price_for_unsupported_quote_symbol() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) spot_price, source, updated_at = resolve_portfolio_spot_from_quote( config, { "symbol": "SLV", "price": 28.50, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, ) assert spot_price == 4400.0 assert source == "configured_entry_price" assert updated_at == "" def test_overview_uses_fallback_symbol_when_quote_payload_omits_symbol() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) spot_price, source, updated_at = resolve_portfolio_spot_from_quote( config, {"price": 404.19, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) assert spot_price == 4041.9 assert source == "yfinance" assert updated_at == "2026-03-24T00:00:00+00:00" def test_overview_falls_back_when_quote_price_is_missing_or_invalid() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) missing_price = resolve_portfolio_spot_from_quote( config, {"symbol": "GLD", "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) zero_price = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": 0.0, "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, fallback_symbol="GLD", ) bad_string_price = resolve_portfolio_spot_from_quote( config, { "symbol": "GLD", "price": "not-a-number", "quote_unit": "share", "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00", }, fallback_symbol="GLD", ) assert missing_price == (4400.0, "configured_entry_price", "") assert zero_price == (4400.0, "configured_entry_price", "") assert bad_string_price == (4400.0, "configured_entry_price", "") def test_overview_falls_back_when_quote_unit_metadata_is_missing() -> None: config = PortfolioConfig(entry_price=4400.0, gold_ounces=220.0, entry_basis_mode="weight", loan_amount=145000.0) result = resolve_portfolio_spot_from_quote( config, {"symbol": "GLD", "price": 404.19, "source": "yfinance", "updated_at": "2026-03-24T00:00:00+00:00"}, fallback_symbol="GLD", ) assert result == (4400.0, "configured_entry_price", "")