""" Tests for backend/main.py — FastAPI endpoints. Covers: - Ticker input validation (valid + invalid) - DEMO_MODE staging response - Rate limiting logic - Quick signal caching - HTTP method restrictions """ import json import time import pytest from unittest.mock import patch, MagicMock, AsyncMock from fastapi.testclient import TestClient # ── Fixture helpers ──────────────────────────────────────────────────────────── def _make_client(demo_mode: bool = False): """Import app fresh with the desired DEMO_MODE.""" import importlib import sys # Patch env before importing app env_patch = { "DEMO_MODE": "true" if demo_mode else "false", "LLM_MODE": "cloud", } with patch.dict("os.environ", env_patch): # Force re-import so DEMO_MODE constant is re-evaluated for mod in list(sys.modules.keys()): if "backend.main" in mod or mod == "backend.main": del sys.modules[mod] import backend.main as app_module # noqa: PLC0415 importlib.reload(app_module) return TestClient(app_module.app), app_module # ── Ticker validation ───────────────────────────────────────────────────────── class TestTickerValidation: def setup_method(self): self.client, self.mod = _make_client() @pytest.mark.parametrize("ticker", [ "AAPL", "NVDA", "BRK.B", "SPY", "QQQ", "BP", "HSBA", ]) def test_valid_tickers_accepted_quick(self, ticker): with patch.object(self.mod, "fuse_signals", return_value={"recommendation": "BUY"}): r = self.client.get(f"/quick/{ticker}") assert r.status_code != 400, f"Valid ticker {ticker} was rejected" @pytest.mark.parametrize("bad", [ "../etc", # path traversal attempt "A" * 11, # too long "DROP;TABLE", # injection attempt "