From 05784cc8ec8bc6b4aabdcd29e369cca43996744f Mon Sep 17 00:00:00 2001 From: Davide Casale Date: Wed, 1 Feb 2023 17:05:25 +0100 Subject: [PATCH] Add tests subpackage. Add TestRestSerializersAndTypes and TestWebsocketSerializersAndTypes unit tests. Fix consistency bugs between serializers and types. --- bfxapi/labeler.py | 3 +++ bfxapi/rest/serializers.py | 24 +++++++++++++++++-- bfxapi/rest/types.py | 22 ++++++++++++++++- bfxapi/tests/__init__.py | 8 +++++++ .../tests/test_rest_serializers_and_types.py | 23 ++++++++++++++++++ .../test_websocket_serializers_and_types.py | 23 ++++++++++++++++++ bfxapi/websocket/serializers.py | 15 ++++++++++-- bfxapi/websocket/types.py | 12 ++++++++++ 8 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 bfxapi/tests/__init__.py create mode 100644 bfxapi/tests/test_rest_serializers_and_types.py create mode 100644 bfxapi/tests/test_websocket_serializers_and_types.py diff --git a/bfxapi/labeler.py b/bfxapi/labeler.py index 6201b82..1c2655d 100644 --- a/bfxapi/labeler.py +++ b/bfxapi/labeler.py @@ -28,6 +28,9 @@ class _Serializer(Generic[T]): def parse(self, *values: Any, skip: Optional[List[str]] = None) -> T: return cast(T, self.klass(**dict(self._serialize(*values, skip=skip)))) + def get_labels(self) -> List[str]: + return [ label for label in self.__labels if label not in self.__IGNORE ] + class _RecursiveSerializer(_Serializer, Generic[T]): def __init__(self, name: str, klass: Type[_Type], labels: List[str], serializers: Dict[str, _Serializer[Any]], IGNORE: List[str] = ["_PLACEHOLDER"]): super().__init__(name, klass, labels, IGNORE) diff --git a/bfxapi/rest/serializers.py b/bfxapi/rest/serializers.py index 938aef5..3ef765a 100644 --- a/bfxapi/rest/serializers.py +++ b/bfxapi/rest/serializers.py @@ -4,6 +4,26 @@ from .. labeler import generate_labeler_serializer, generate_recursive_serialize from .. notification import _Notification +__serializers__ = [ + "PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker", + "TickersHistory", "TradingPairTrade", "FundingCurrencyTrade", + "TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook", + "FundingCurrencyRawBook", "Statistic", "Candle", + "DerivativesStatus", "Liquidation", "Leaderboard", + "FundingStatistic", "PulseProfile", "PulseMessage", + "TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate", + + "Order", "Position", "Trade", + "FundingTrade", "OrderTrade", "Ledger", + "FundingOffer", "FundingCredit", "FundingLoan", + "FundingAutoRenew", "FundingInfo", "Wallet", + "Transfer", "Withdrawal", "DepositAddress", + "Invoice", "Movement", "SymbolMarginInfo", + "BaseMarginInfo", "Claim", "IncreaseInfo", + "Increase", "PositionHistory", "PositionSnapshot", + "PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits", +] + #region Serializers definition for Rest Public Endpoints PlatformStatus = generate_labeler_serializer("PlatformStatus", klass=types.PlatformStatus, labels=[ @@ -308,7 +328,7 @@ Position = generate_labeler_serializer("Position", klass=types.Position, labels= Trade = generate_labeler_serializer("Trade", klass=types.Trade, labels=[ "id", - "pair", + "symbol", "mts_create", "order_id", "exec_amount", @@ -333,7 +353,7 @@ FundingTrade = generate_labeler_serializer("FundingTrade", klass=types.FundingTr OrderTrade = generate_labeler_serializer("OrderTrade", klass=types.OrderTrade, labels=[ "id", - "pair", + "symbol", "mts_create", "order_id", "exec_amount", diff --git a/bfxapi/rest/types.py b/bfxapi/rest/types.py index 2330eeb..ad28321 100644 --- a/bfxapi/rest/types.py +++ b/bfxapi/rest/types.py @@ -6,12 +6,32 @@ from .. labeler import _Type from .. notification import Notification from .. utils.encoder import JSON +__types__ = [ + "PlatformStatus", "TradingPairTicker", "FundingCurrencyTicker", + "TickersHistory", "TradingPairTrade", "FundingCurrencyTrade", + "TradingPairBook", "FundingCurrencyBook", "TradingPairRawBook", + "FundingCurrencyRawBook", "Statistic", "Candle", + "DerivativesStatus", "Liquidation", "Leaderboard", + "FundingStatistic", "PulseProfile", "PulseMessage", + "TradingMarketAveragePrice", "FundingMarketAveragePrice", "FxRate", + + "Order", "Position", "Trade", + "FundingTrade", "OrderTrade", "Ledger", + "FundingOffer", "FundingCredit", "FundingLoan", + "FundingAutoRenew", "FundingInfo", "Wallet", + "Transfer", "Withdrawal", "DepositAddress", + "Invoice", "Movement", "SymbolMarginInfo", + "BaseMarginInfo", "Claim", "IncreaseInfo", + "Increase", "PositionHistory", "PositionSnapshot", + "PositionAudit", "DerivativePositionCollateral", "DerivativePositionCollateralLimits", +] + #region Type hinting for Rest Public Endpoints @dataclass class PlatformStatus(_Type): status: int - + @dataclass class TradingPairTicker(_Type): symbol: Optional[str] diff --git a/bfxapi/tests/__init__.py b/bfxapi/tests/__init__.py new file mode 100644 index 0000000..eee78b7 --- /dev/null +++ b/bfxapi/tests/__init__.py @@ -0,0 +1,8 @@ +import unittest +from .test_rest_serializers_and_types import TestRestSerializersAndTypes +from .test_websocket_serializers_and_types import TestWebsocketSerializersAndTypes + +NAME = "tests" + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/bfxapi/tests/test_rest_serializers_and_types.py b/bfxapi/tests/test_rest_serializers_and_types.py new file mode 100644 index 0000000..ff1b427 --- /dev/null +++ b/bfxapi/tests/test_rest_serializers_and_types.py @@ -0,0 +1,23 @@ +import unittest + +from ..rest import serializers, types + +class TestRestSerializersAndTypes(unittest.TestCase): + def test_consistency(self): + __types__ = list(map(types.__dict__.get, types.__types__)) + + for serializer in map(serializers.__dict__.get, serializers.__serializers__): + type = types.__dict__.get(serializer.name) + + __types__.remove(type) + self.assertIsNotNone(type, f"_Serializer <{serializer.name}>: no respective _Type found in bfxapi.rest.types.") + self.assertEqual(serializer.klass, type, f"_Serializer <{serializer.name}>.klass: field does not match with respective _Type in bfxapi.rest.types.") + + self.assertListEqual(serializer.get_labels(), list(type.__annotations__), + f"_Serializer <{serializer.name}> and _Type <{type.__name__}> must have matching labels and fields.") + + for type in __types__: + self.fail(f"_Type <{type.__name__}>: no respective _Serializer found in bfxapi.rest.serializers.") + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/bfxapi/tests/test_websocket_serializers_and_types.py b/bfxapi/tests/test_websocket_serializers_and_types.py new file mode 100644 index 0000000..2d34d61 --- /dev/null +++ b/bfxapi/tests/test_websocket_serializers_and_types.py @@ -0,0 +1,23 @@ +import unittest + +from ..websocket import serializers, types + +class TestWebsocketSerializersAndTypes(unittest.TestCase): + def test_consistency(self): + __types__ = list(map(types.__dict__.get, types.__types__)) + + for serializer in map(serializers.__dict__.get, serializers.__serializers__): + type = types.__dict__.get(serializer.name) + + __types__.remove(type) + self.assertIsNotNone(type, f"_Serializer <{serializer.name}>: no respective _Type found in bfxapi.websocket.types.") + self.assertEqual(serializer.klass, type, f"_Serializer <{serializer.name}>.klass: field does not match with respective _Type in bfxapi.websocket.types.") + + self.assertListEqual(serializer.get_labels(), list(type.__annotations__), + f"_Serializer <{serializer.name}> and _Type <{type.__name__}> must have matching labels and fields.") + + for type in __types__: + self.fail(f"_Type <{type.__name__}>: no respective _Serializer found in bfxapi.websocket.serializers.") + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/bfxapi/websocket/serializers.py b/bfxapi/websocket/serializers.py index 6207f33..29c28c7 100644 --- a/bfxapi/websocket/serializers.py +++ b/bfxapi/websocket/serializers.py @@ -4,6 +4,17 @@ from .. labeler import generate_labeler_serializer from .. notification import _Notification +__serializers__ = [ + "TradingPairTicker", "FundingCurrencyTicker", "TradingPairTrade", + "FundingCurrencyTrade", "TradingPairBook", "FundingCurrencyBook", + "TradingPairRawBook", "FundingCurrencyRawBook", "Candle", + "DerivativesStatus", + + "Order", "Position", "Trade", + "FundingOffer", "FundingCredit", "FundingLoan", + "Wallet", "Balance", +] + #region Serializers definition for Websocket Public Channels TradingPairTicker = generate_labeler_serializer("TradingPairTicker", klass=types.TradingPairTicker, labels=[ @@ -32,7 +43,7 @@ FundingCurrencyTicker = generate_labeler_serializer("FundingCurrencyTicker", kla "last_price", "volume", "high", - "low" + "low", "_PLACEHOLDER", "_PLACEHOLDER", "frr_amount_available" @@ -100,7 +111,7 @@ DerivativesStatus = generate_labeler_serializer("DerivativesStatus", klass=types "next_funding_accrued", "next_funding_step", "_PLACEHOLDER", - "current_funding" + "current_funding", "_PLACEHOLDER", "_PLACEHOLDER", "mark_price", diff --git a/bfxapi/websocket/types.py b/bfxapi/websocket/types.py index 659fd29..3e0991f 100644 --- a/bfxapi/websocket/types.py +++ b/bfxapi/websocket/types.py @@ -6,6 +6,17 @@ from ..labeler import _Type from ..notification import Notification from .. utils.encoder import JSON +__types__ = [ + "TradingPairTicker", "FundingCurrencyTicker", "TradingPairTrade", + "FundingCurrencyTrade", "TradingPairBook", "FundingCurrencyBook", + "TradingPairRawBook", "FundingCurrencyRawBook", "Candle", + "DerivativesStatus", + + "Order", "Position", "Trade", + "FundingOffer", "FundingCredit", "FundingLoan", + "Wallet", "Balance", +] + #region Type hinting for Websocket Public Channels @dataclass @@ -143,6 +154,7 @@ class Position(_Type): pl_perc: float price_liq: float leverage: float + flag: int position_id: int mts_create: int mts_update: int