import importlib from fastapi.testclient import TestClient def _build_app(monkeypatch): monkeypatch.setenv("APP_ENV", "test") monkeypatch.setenv("DISABLE_STARTUP_TASKS", "1") monkeypatch.setenv("DB_HOST", "localhost") monkeypatch.setenv("DB_NAME", "trading_db") monkeypatch.setenv("DB_USER", "trader") monkeypatch.setenv("DB_PASSWORD", "test-password") monkeypatch.setenv("CORS_ORIGINS", "http://localhost:3000") monkeypatch.setenv("BROKER_TOKEN_KEY", "test-broker-token-key") monkeypatch.setenv("RESET_OTP_SECRET", "test-reset-secret") import app.main as app_main importlib.reload(app_main) return app_main.create_app() def test_disconnect_route_stops_live_strategy_and_disconnects_broker(monkeypatch): app = _build_app(monkeypatch) client = TestClient(app) import app.routers.broker as broker_router calls = { "disconnect": [], "clear_session": [], } monkeypatch.setattr( broker_router, "_require_user", lambda _request: {"id": "user-1", "username": "user@example.com"}, ) monkeypatch.setattr( broker_router, "block_live_strategy_for_broker_disconnect", lambda user_id, reason="broker_disconnected": { "run_id": "run-1", "status": "STOPPED", "reason": reason, "user_id": user_id, }, ) monkeypatch.setattr( broker_router, "disconnect_user_broker", lambda user_id: calls["disconnect"].append(user_id), ) monkeypatch.setattr( broker_router, "clear_zerodha_session", lambda user_id: calls["clear_session"].append(user_id), ) monkeypatch.setattr(broker_router, "send_email_async", lambda *_args, **_kwargs: None) response = client.post("/api/broker/disconnect", cookies={"session_id": "session-1"}) assert response.status_code == 200 assert response.json() == { "connected": False, "brokerState": "DISCONNECTED", "strategy": { "run_id": "run-1", "status": "STOPPED", "reason": "broker_disconnected", "user_id": "user-1", }, } assert calls["disconnect"] == ["user-1"] assert calls["clear_session"] == ["user-1"] def test_block_live_strategy_for_broker_disconnect_auto_stops_active_live_run(monkeypatch): import app.services.strategy_service as strategy_service calls = [] monkeypatch.setattr(strategy_service, "_effective_running_run_id", lambda _user_id: "run-1") monkeypatch.setattr(strategy_service, "_load_config", lambda _user_id, _run_id: {"mode": "LIVE"}) monkeypatch.setattr( strategy_service, "stop_engine", lambda user_id, run_id, timeout=10.0: calls.append(("stop_engine", user_id, run_id, timeout)), ) monkeypatch.setattr( strategy_service, "deactivate_strategy_config", lambda user_id, run_id: calls.append(("deactivate", user_id, run_id)), ) monkeypatch.setattr( strategy_service, "stop_run", lambda user_id, run_id, reason="user_request": calls.append(("stop_run", user_id, run_id, reason)), ) monkeypatch.setattr( strategy_service, "_write_status", lambda user_id, run_id, status: calls.append(("write_status", user_id, run_id, status)), ) monkeypatch.setattr( strategy_service, "_set_run_status_or_raise", lambda user_id, run_id, status, meta=None: calls.append(("set_run_status", user_id, run_id, status, meta)), ) result = strategy_service.block_live_strategy_for_broker_disconnect( "user-1", reason="broker_disconnected", ) assert result == { "run_id": "run-1", "status": "STOPPED", "reason": "broker_disconnected", } assert ("stop_engine", "user-1", "run-1", 10.0) in calls assert ("deactivate", "user-1", "run-1") in calls assert ("stop_run", "user-1", "run-1", "broker_disconnected") in calls assert ("write_status", "user-1", "run-1", "STOPPED") in calls def test_strategy_status_marks_broker_disconnected_block(monkeypatch): import app.services.strategy_service as strategy_service monkeypatch.setattr(strategy_service, "_effective_running_run_id", lambda _user_id: None) monkeypatch.setattr(strategy_service, "get_active_run_id", lambda _user_id: "run-1") monkeypatch.setattr( strategy_service, "_load_config", lambda _user_id, _run_id: { "strategy": "Golden Nifty", "mode": "LIVE", "broker": "ZERODHA", "active": True, }, ) monkeypatch.setattr( strategy_service, "_get_run_row", lambda _user_id, _run_id: {"status": "STOPPED", "meta": {}, "started_at": None, "stopped_at": None}, ) monkeypatch.setattr( strategy_service, "_broker_block_state", lambda _user_id, _cfg: { "blocked": True, "reason": "broker_disconnected", "broker_state": "DISCONNECTED", "broker": "ZERODHA", }, ) class FakeCursor: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def execute(self, _sql, _params): return None def fetchone(self): return None class FakeConnection: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def cursor(self): return FakeCursor() monkeypatch.setattr(strategy_service, "db_connection", lambda: FakeConnection()) status = strategy_service.get_strategy_status("user-1") assert status["status"] == "STOPPED" assert status["strategy_blocked"] is True assert status["strategy_block_reason"] == "broker_disconnected" assert status["broker_state"] == "DISCONNECTED" assert status["broker"] == "ZERODHA" def test_strategy_status_clears_broker_block_after_reconnect(monkeypatch): import app.services.strategy_service as strategy_service monkeypatch.setattr(strategy_service, "_effective_running_run_id", lambda _user_id: None) monkeypatch.setattr(strategy_service, "get_active_run_id", lambda _user_id: "run-1") monkeypatch.setattr( strategy_service, "_load_config", lambda _user_id, _run_id: { "strategy": "Golden Nifty", "mode": "LIVE", "broker": "ZERODHA", "active": True, }, ) monkeypatch.setattr( strategy_service, "_get_run_row", lambda _user_id, _run_id: {"status": "STOPPED", "meta": {}, "started_at": None, "stopped_at": None}, ) monkeypatch.setattr( strategy_service, "_broker_block_state", lambda _user_id, _cfg: { "blocked": False, "reason": None, "broker_state": "VALID", "broker": "ZERODHA", }, ) class FakeCursor: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def execute(self, _sql, _params): return None def fetchone(self): return None class FakeConnection: def __enter__(self): return self def __exit__(self, exc_type, exc, tb): return False def cursor(self): return FakeCursor() monkeypatch.setattr(strategy_service, "db_connection", lambda: FakeConnection()) status = strategy_service.get_strategy_status("user-1") assert status["strategy_blocked"] is False assert status["strategy_block_reason"] is None assert status["broker_state"] == "VALID"