DRRK/tests/unit/fakes.py

100 lines
3.3 KiB
Python

"""Фейковые клиенты MOEX/ЦБ для тестов сервисов (без сети)."""
from __future__ import annotations
from datetime import date
from app.integrations.cbr import FxRate
from app.integrations.gateway import RequestRecord
from app.integrations.moex import MarketQuote, SecurityInfo, SecuritySearchHit
def search_hit(secid: str, isin: str) -> SecuritySearchHit:
return SecuritySearchHit(
secid=secid,
isin=isin,
short_name=None,
name=None,
is_traded=True,
primary_board=None,
)
class FakeGateway:
"""Копит записи журнала, как GatewayClient: одна запись на вызов клиента."""
def __init__(self, source: str) -> None:
self.source = source
self.records: list[RequestRecord] = []
def record(self, endpoint: str, ok: bool = True, error: str | None = None) -> None:
self.records.append(
RequestRecord(
source=self.source,
endpoint=endpoint,
params={},
http_status=200 if ok else None,
duration_ms=1,
ok=ok,
error=error,
)
)
def drain_records(self) -> list[RequestRecord]:
records, self.records = self.records, []
return records
class FakeMoex:
"""search: isin -> hit; securities: secid -> info; history: secid -> quote | Exception."""
def __init__(
self,
*,
search: dict[str, SecuritySearchHit] | None = None,
securities: dict[str, SecurityInfo] | None = None,
history: dict[str, MarketQuote | Exception | None] | None = None,
) -> None:
self.gateway = FakeGateway("moex")
self._search = search or {}
self._securities = securities or {}
self._history = history or {}
self.calls: list[tuple] = []
def _maybe_raise(self, value): # noqa: ANN001
if isinstance(value, Exception):
raise value
return value
def find_by_isin(self, isin: str) -> SecuritySearchHit | None:
self.calls.append(("find_by_isin", isin))
self.gateway.record("/iss/securities.json")
return self._maybe_raise(self._search.get(isin))
def get_security(self, secid: str) -> SecurityInfo | None:
self.calls.append(("get_security", secid))
self.gateway.record(f"/iss/securities/{secid}.json")
return self._maybe_raise(self._securities.get(secid))
def get_history(self, secid: str, on_date: date) -> MarketQuote | None:
self.calls.append(("get_history", secid, on_date))
value = self._history.get(secid)
self.gateway.record(
f"/iss/history/.../{secid}.json", ok=not isinstance(value, Exception)
)
return self._maybe_raise(value)
class FakeCbr:
def __init__(self, rates: list[FxRate] | Exception) -> None:
self.gateway = FakeGateway("cbr")
self._rates = rates
self.calls: list[tuple] = []
def get_rates(self, date_from: date, date_to: date, code: str = "R01375"):
self.calls.append((date_from, date_to, code))
self.gateway.record("/scripts/XML_dynamic.asp")
if isinstance(self._rates, Exception):
raise self._rates
return [r for r in self._rates if date_from <= r.rate_date <= date_to]