| """tests/test_valuation.py β unit tests for analytics/valuation.py. |
| |
| Mocks yfinance so tests are hermetic and fast, following the same pattern |
| as tests/test_analyst.py. |
| """ |
| from __future__ import annotations |
|
|
| from unittest.mock import MagicMock, patch |
|
|
| import pytest |
|
|
| from analytics import valuation |
|
|
|
|
| @pytest.fixture(autouse=True) |
| def _no_cache(monkeypatch): |
| monkeypatch.setattr("storage.earnings_cache.get", lambda *a, **k: None) |
| monkeypatch.setattr("storage.earnings_cache.set", lambda *a, **k: None) |
|
|
|
|
| |
|
|
| def test_fetch_multiples_success(): |
| fake = MagicMock() |
| fake.info = { |
| "trailingPE": 45.2, "forwardPE": 32.1, |
| "enterpriseToRevenue": 18.5, "enterpriseToEbitda": 25.0, |
| "marketCap": 3_000_000_000_000, |
| } |
| with patch("yfinance.Ticker", return_value=fake): |
| data, err = valuation.fetch_multiples("NVDA") |
|
|
| assert err is None |
| assert data["trailing_pe"] == 45.2 |
| assert data["forward_pe"] == 32.1 |
| assert data["ev_to_sales"] == 18.5 |
| assert data["ev_to_ebitda"] == 25.0 |
|
|
|
|
| def test_fetch_multiples_fail_closed_when_nothing_available(): |
| fake = MagicMock() |
| fake.info = {} |
| with patch("yfinance.Ticker", return_value=fake): |
| data, err = valuation.fetch_multiples("XYZ") |
|
|
| assert data is None |
| assert err is not None |
|
|
|
|
| def test_fetch_multiples_never_raises_on_yfinance_error(): |
| with patch("yfinance.Ticker", side_effect=RuntimeError("rate limit")): |
| data, err = valuation.fetch_multiples("NVDA") |
| assert data is None |
| assert err is not None |
|
|
|
|
| def test_fetch_multiples_tolerates_nan_values(): |
| fake = MagicMock() |
| fake.info = {"trailingPE": float("nan"), "forwardPE": 20.0} |
| with patch("yfinance.Ticker", return_value=fake): |
| data, err = valuation.fetch_multiples("NVDA") |
| assert err is None |
| assert data["trailing_pe"] is None |
| assert data["forward_pe"] == 20.0 |
|
|
|
|
| |
|
|
| def test_fetch_peer_multiples_includes_primary_and_peers(): |
| fake = MagicMock() |
| fake.info = {"trailingPE": 40.0} |
| with patch("yfinance.Ticker", return_value=fake): |
| rows = valuation.fetch_peer_multiples("NVDA") |
|
|
| tickers = [r["ticker"] for r in rows] |
| assert "NVDA" in tickers |
| assert "AMD" in tickers |
| assert "AVGO" in tickers |
|
|
|
|
| def test_fetch_peer_multiples_unknown_ticker_has_no_peers(): |
| fake = MagicMock() |
| fake.info = {"trailingPE": 40.0} |
| with patch("yfinance.Ticker", return_value=fake): |
| rows = valuation.fetch_peer_multiples("SOMEOBSCURETICKER") |
| assert len(rows) == 1 |
| assert rows[0]["ticker"] == "SOMEOBSCURETICKER" |
|
|
|
|
| def test_fetch_peer_multiples_omits_peer_whose_fetch_fails(): |
| def _side_effect(ticker): |
| if ticker == "AMD": |
| raise RuntimeError("no data") |
| fake = MagicMock() |
| fake.info = {"trailingPE": 40.0} |
| return fake |
|
|
| with patch("yfinance.Ticker", side_effect=_side_effect): |
| rows = valuation.fetch_peer_multiples("NVDA") |
|
|
| tickers = [r["ticker"] for r in rows] |
| assert "AMD" not in tickers |
| assert "NVDA" in tickers |
|
|
|
|
| |
|
|
| def test_next_catalysts_parses_dict_calendar(): |
| fake = MagicMock() |
| fake.calendar = {"Earnings Date": ["2026-08-20"], "Ex-Dividend Date": "2026-07-01"} |
| with patch("yfinance.Ticker", return_value=fake): |
| result = valuation.next_catalysts("NVDA") |
| assert result["next_earnings_date"] == "2026-08-20" |
| assert result["next_ex_dividend_date"] == "2026-07-01" |
|
|
|
|
| def test_next_catalysts_never_raises_when_calendar_missing(): |
| fake = MagicMock() |
| fake.calendar = None |
| with patch("yfinance.Ticker", return_value=fake): |
| result = valuation.next_catalysts("NVDA") |
| assert result == {"next_earnings_date": None, "next_ex_dividend_date": None} |
|
|
|
|
| def test_next_catalysts_never_raises_on_yfinance_error(): |
| with patch("yfinance.Ticker", side_effect=RuntimeError("boom")): |
| result = valuation.next_catalysts("NVDA") |
| assert result == {"next_earnings_date": None, "next_ex_dividend_date": None} |
|
|