SIP_GoldBees_Backend/backend/tests/test_broker_disconnect_handling.py

253 lines
7.7 KiB
Python

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"