summaryrefslogtreecommitdiff
path: root/backend/tests/test_api.py
diff options
context:
space:
mode:
authorTyler Hoang <tyler@tylerhoang.xyz>2026-05-17 12:46:13 -0700
committerTyler Hoang <tyler@tylerhoang.xyz>2026-05-17 12:46:13 -0700
commit1482422f2f5b236cdcdff4429ae06bb55dca4083 (patch)
tree4653cb4986a8a138f84dbec934effb0d011751d3 /backend/tests/test_api.py
Add stack start and stop scripts
Diffstat (limited to 'backend/tests/test_api.py')
-rw-r--r--backend/tests/test_api.py181
1 files changed, 181 insertions, 0 deletions
diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py
new file mode 100644
index 0000000..70b67a7
--- /dev/null
+++ b/backend/tests/test_api.py
@@ -0,0 +1,181 @@
+import pandas as pd
+import pytest
+from fastapi import HTTPException
+
+from app import main
+from app.services import data_service
+
+
+def test_health() -> None:
+ assert main.health() == {"status": "ok"}
+
+
+def test_search_smoke(monkeypatch) -> None:
+ monkeypatch.setattr(main.data_service, "search_tickers", lambda q: [{"symbol": "AAPL", "name": "Apple Inc.", "exchange": "NASDAQ"}])
+ assert main.search("apple")[0]["symbol"] == "AAPL"
+
+
+def test_watchlist_smoke(tmp_path, monkeypatch) -> None:
+ monkeypatch.setattr(main, "DB_PATH", tmp_path / "prism.db")
+ monkeypatch.setattr(main.data_service, "get_company_info", lambda symbol: {"currentPrice": 100.0, "previousClose": 95.0})
+ res = main.add_watchlist_symbol("aapl")
+ assert res["items"][0]["symbol"] == "AAPL"
+
+
+def test_mocked_ticker_overview(monkeypatch) -> None:
+ monkeypatch.setattr(
+ main.data_service,
+ "get_ticker_overview",
+ lambda symbol: {
+ "profile": {"symbol": "AAPL", "name": "Apple Inc.", "sector": None, "industry": None, "exchange": "NASDAQ", "website": None, "summary": None},
+ "quote": {"price": 100.0, "prev_close": 98.0, "change": 2.0, "change_pct": 0.0204},
+ "signals": [],
+ "stats": {"market_cap": None, "trailing_pe": None, "trailing_eps": None, "volume": None, "average_volume": None, "beta": None},
+ "range_52w": {"low": None, "high": None, "price": 100.0},
+ "short_interest": {"short_percent_of_float": None, "short_ratio": None, "shares_short": None, "shares_short_prior_month": None, "shares_short_delta_pct": None},
+ "meta": {"status": "partial", "is_partial": True, "field_availability": {}, "sources": {}},
+ },
+ )
+ assert main.ticker_overview("AAPL")["profile"]["symbol"] == "AAPL"
+
+
+def test_service_overview_prefers_info_fields(monkeypatch) -> None:
+ monkeypatch.setattr(
+ data_service,
+ "get_company_info",
+ lambda symbol: {
+ "longName": "Apple Inc.",
+ "exchange": "NMS",
+ "currentPrice": 190.0,
+ "previousClose": 188.0,
+ "marketCap": 2_900_000_000_000,
+ "trailingPE": 31.2,
+ "trailingEps": 6.08,
+ "volume": 50_000_000,
+ "averageVolume": 60_000_000,
+ "beta": 1.18,
+ "fiftyTwoWeekHigh": 199.0,
+ "fiftyTwoWeekLow": 164.0,
+ },
+ )
+ monkeypatch.setattr(data_service, "get_fast_info", lambda symbol: {"lastPrice": 1.0, "exchange": "NYQ"})
+ monkeypatch.setattr(data_service, "get_price_history", lambda symbol, period="1m": [])
+ monkeypatch.setattr(data_service, "_pick_search_match", lambda symbol: {"symbol": "AAPL", "name": "Wrong", "exchange": "NYSE"})
+ monkeypatch.setattr(data_service, "get_profile_enrichment", lambda symbol: {})
+
+ overview = data_service.get_ticker_overview("AAPL")
+ assert overview is not None
+ assert overview["profile"]["name"] == "Apple Inc."
+ assert overview["profile"]["exchange"] == "NASDAQ"
+ assert overview["quote"]["price"] == 190.0
+ assert overview["stats"]["market_cap"] == 2_900_000_000_000
+ assert overview["meta"]["sources"]["profile.name"] == "info"
+ assert overview["meta"]["sources"]["quote.price"] == "info"
+
+
+def test_service_overview_falls_back_to_fast_info(monkeypatch) -> None:
+ monkeypatch.setattr(data_service, "get_company_info", lambda symbol: {})
+ monkeypatch.setattr(
+ data_service,
+ "get_fast_info",
+ lambda symbol: {
+ "lastPrice": 100.0,
+ "previousClose": 98.0,
+ "marketCap": 2_000_000_000,
+ "lastVolume": 1_500_000,
+ "threeMonthAverageVolume": 1_250_000,
+ "yearHigh": 130.0,
+ "yearLow": 90.0,
+ "exchange": "NMS",
+ },
+ )
+ monkeypatch.setattr(data_service, "get_price_history", lambda symbol, period="1m": [])
+ monkeypatch.setattr(data_service, "_pick_search_match", lambda symbol: {"symbol": "AAPL", "name": "Apple Inc.", "exchange": "NASDAQ"})
+ monkeypatch.setattr(data_service, "get_profile_enrichment", lambda symbol: {})
+
+ overview = data_service.get_ticker_overview("AAPL")
+ assert overview is not None
+ assert overview["quote"]["price"] == 100.0
+ assert overview["quote"]["prev_close"] == 98.0
+ assert overview["stats"]["average_volume"] == 1_250_000
+ assert overview["meta"]["sources"]["quote.price"] == "fast_info"
+ assert overview["meta"]["sources"]["range_52w.high"] == "fast_info"
+
+
+def test_service_overview_falls_back_to_search_and_history(monkeypatch) -> None:
+ month_history = [
+ {"date": "2026-01-01", "close": 98.0, "volume": 1000.0},
+ {"date": "2026-01-02", "close": 100.0, "volume": 1200.0},
+ ]
+ year_history = month_history + [{"date": "2026-04-02", "close": 120.0, "volume": 900.0}]
+ monkeypatch.setattr(data_service, "get_company_info", lambda symbol: {})
+ monkeypatch.setattr(data_service, "get_fast_info", lambda symbol: {})
+ monkeypatch.setattr(data_service, "_pick_search_match", lambda symbol: {"symbol": "AAPL", "name": "Apple Inc.", "exchange": "NMS"})
+ monkeypatch.setattr(data_service, "get_profile_enrichment", lambda symbol: {})
+ monkeypatch.setattr(data_service, "get_price_history", lambda symbol, period="1m": month_history if period == "1m" else year_history)
+
+ overview = data_service.get_ticker_overview("AAPL")
+ assert overview is not None
+ assert overview["profile"]["name"] == "Apple Inc."
+ assert overview["quote"]["price"] == 100.0
+ assert overview["range_52w"]["high"] == 120.0
+ assert overview["meta"]["sources"]["profile.name"] == "search"
+ assert overview["meta"]["sources"]["quote.price"] == "history_recent"
+
+
+def test_service_overview_invalid_symbol(monkeypatch) -> None:
+ monkeypatch.setattr(data_service, "get_company_info", lambda symbol: {})
+ monkeypatch.setattr(data_service, "get_fast_info", lambda symbol: {})
+ monkeypatch.setattr(data_service, "_pick_search_match", lambda symbol: {})
+ monkeypatch.setattr(data_service, "get_profile_enrichment", lambda symbol: {})
+ monkeypatch.setattr(data_service, "get_price_history", lambda symbol, period="1m": [])
+ assert data_service.get_ticker_overview("BAD") is None
+
+
+def test_ticker_overview_404(monkeypatch) -> None:
+ monkeypatch.setattr(main.data_service, "get_ticker_overview", lambda symbol: None)
+ with pytest.raises(HTTPException) as exc:
+ main.ticker_overview("INVALID")
+ assert exc.value.status_code == 404
+ assert exc.value.detail == "ticker data unavailable"
+
+
+def test_ticker_overview_partial_response(monkeypatch) -> None:
+ monkeypatch.setattr(
+ main.data_service,
+ "get_ticker_overview",
+ lambda symbol: {
+ "profile": {"symbol": "AAPL", "name": "Apple Inc.", "exchange": "NASDAQ", "sector": None, "industry": None, "website": None, "summary": None},
+ "quote": {"price": 100.0, "prev_close": 98.0, "change": 2.0, "change_pct": 0.0204},
+ "signals": [],
+ "stats": {"market_cap": None, "trailing_pe": None, "trailing_eps": None, "volume": 1000.0, "average_volume": None, "beta": None},
+ "range_52w": {"low": None, "high": None, "price": 100.0},
+ "short_interest": {"short_percent_of_float": None, "short_ratio": None, "shares_short": None, "shares_short_prior_month": None, "shares_short_delta_pct": None},
+ "meta": {"status": "partial", "is_partial": True, "field_availability": {"stats.market_cap": False}, "sources": {"profile.name": "search"}},
+ },
+ )
+ body = main.ticker_overview("AAPL")
+ assert body["meta"]["is_partial"] is True
+ assert body["profile"]["name"] == "Apple Inc."
+
+
+def test_ticker_history_period_mapping(monkeypatch) -> None:
+ data_service.HISTORY_CACHE.clear()
+ captured: list[str] = []
+
+ class DummyTicker:
+ def __init__(self, symbol: str) -> None:
+ self.symbol = symbol
+
+ def history(self, period: str):
+ captured.append(period)
+ return pd.DataFrame(
+ [{"Open": 1.0, "High": 1.0, "Low": 1.0, "Close": 1.0, "Volume": 1.0}],
+ index=[pd.Timestamp("2026-01-01")],
+ )
+
+ monkeypatch.setattr(data_service.yf, "Ticker", DummyTicker)
+ assert len(data_service.get_price_history("AAPL", period="1m")) == 1
+ assert len(data_service.get_price_history("AAPL", period="3m")) == 1
+ assert len(data_service.get_price_history("AAPL", period="6m")) == 1
+ assert captured == ["1mo", "3mo", "6mo"]