| import pytest |
| from unittest.mock import AsyncMock, MagicMock |
| from fastapi.testclient import TestClient |
| import pandas as pd |
| import app.main as main |
|
|
| |
| client = TestClient(main.app, raise_server_exceptions=False) |
|
|
| @pytest.fixture(autouse=True) |
| def setup_mocks(monkeypatch): |
| mock_db = AsyncMock() |
| mock_db.connected = True |
| |
| mock_timesfm = AsyncMock() |
| mock_timesfm.model = MagicMock() |
| |
| mock_data = AsyncMock() |
| mock_chart = MagicMock() |
| |
| monkeypatch.setattr(main, "db_service", mock_db) |
| monkeypatch.setattr(main, "timesfm_service", mock_timesfm) |
| monkeypatch.setattr(main, "data_service", mock_data) |
| monkeypatch.setattr(main, "chart_service", mock_chart) |
| |
| |
| monkeypatch.setattr("app.middleware.RateLimitMiddleware._check", lambda self, ip, path: (False, 0)) |
| |
| return mock_db, mock_timesfm, mock_data, mock_chart |
|
|
| def test_health_check_healthy(setup_mocks): |
| mock_db, _, _, _ = setup_mocks |
| mock_db._execute.return_value = [{"1": 1}] |
| |
| response = client.get("/health") |
| assert response.status_code == 200 |
| data = response.json() |
| assert data["status"] == "healthy" |
| assert data["database_connected"] is True |
| assert data["model_loaded"] is True |
|
|
| def test_health_check_db_disconnected(setup_mocks): |
| mock_db, _, _, _ = setup_mocks |
| mock_db.connected = False |
| |
| response = client.get("/health") |
| assert response.status_code == 200 |
| assert response.json()["database_connected"] is False |
|
|
| def test_get_forecast_cache_hit(setup_mocks): |
| mock_db, _, _, _ = setup_mocks |
| mock_db.get_cached_forecast.return_value = { |
| "symbol": "AAPL", |
| "name": "Apple Inc.", |
| "exchange": "NASDAQ", |
| "currency": "USD", |
| "current_price": 150.0, |
| "last_updated": "2026-06-19T00:00:00", |
| "horizon_days": 20, |
| "point_forecast": 155.0, |
| "percentage_change": 3.33, |
| "quantiles": {"p10": [148.0], "p50": [155.0], "p90": [162.0]}, |
| "chart_svg": "<svg></svg>", |
| "methodology_version": "timesfm-2.5-200m-v1.0" |
| } |
| |
| response = client.get("/api/v1/forecast/AAPL?horizon=20") |
| assert response.status_code == 200 |
| assert response.json()["symbol"] == "AAPL" |
| mock_db.get_cached_forecast.assert_called_once_with("AAPL", 20) |
|
|
| def test_get_forecast_cache_miss_and_generate(setup_mocks): |
| mock_db, mock_timesfm, mock_data, mock_chart = setup_mocks |
| mock_db.get_cached_forecast.return_value = None |
| |
| mock_df = pd.DataFrame( |
| {"Close": [140.0 + i for i in range(100)]}, |
| index=pd.date_range("2026-01-01", periods=100) |
| ) |
| mock_df.attrs["name"] = "Apple Inc." |
| mock_df.attrs["exchange"] = "NASDAQ" |
| mock_df.attrs["currency"] = "USD" |
| mock_df.attrs["pe_ratio"] = 28.4 |
| mock_df.attrs["dividend_yield"] = 0.0055 |
| mock_df.attrs["fifty_two_week_low"] = 130.0 |
| mock_df.attrs["fifty_two_week_high"] = 200.0 |
| mock_df.attrs["market_cap"] = 2500000000000.0 |
| mock_data.get_stock_data.return_value = mock_df |
| |
| def mock_predict(historical_prices, horizon): |
| return { |
| "point_forecast": [245.0] * horizon, |
| "quantiles": { |
| "p10": [240.0] * horizon, |
| "p50": [245.0] * horizon, |
| "p90": [250.0] * horizon |
| } |
| } |
| mock_timesfm.predict.side_effect = mock_predict |
| mock_chart.generate_forecast_chart.return_value = "<svg>chart</svg>" |
| |
| response = client.get("/api/v1/forecast/AAPL?horizon=20") |
| assert response.status_code == 200 |
| data = response.json() |
| assert data["symbol"] == "AAPL" |
| assert data["current_price"] == 239.0 |
| |
| |
| assert "historical_prices" in data |
| assert isinstance(data["historical_prices"], list) |
| assert len(data["historical_prices"]) > 0 |
| assert "time" in data["historical_prices"][0] |
| assert "value" in data["historical_prices"][0] |
| |
| |
| assert "backtest_accuracy" in data |
| assert data["backtest_accuracy"] is not None |
| assert "accuracy_5d" in data["backtest_accuracy"] |
| assert "accuracy_20d" in data["backtest_accuracy"] |
| assert "predictions_5d" in data["backtest_accuracy"] |
| assert "predictions_20d" in data["backtest_accuracy"] |
| |
| |
| assert "financial_metrics" in data |
| assert data["financial_metrics"] is not None |
| assert data["financial_metrics"]["pe_ratio"] == 28.4 |
| assert data["financial_metrics"]["dividend_yield"] == 0.0055 |
| assert data["financial_metrics"]["fifty_two_week_low"] == 130.0 |
| assert data["financial_metrics"]["fifty_two_week_high"] == 200.0 |
| assert data["financial_metrics"]["market_cap"] == 2500000000000.0 |
| |
| mock_db.cache_forecast.assert_called_once() |
|
|