Skip to content

Commit

Permalink
Merge pull request #447 from RichoKD/ekubo_test
Browse files Browse the repository at this point in the history
[Dashboard] Add test cases for Ekubo
  • Loading branch information
djeck1432 authored Feb 25, 2025
2 parents 51cc82d + 757e788 commit 2cbec76
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 24 deletions.
57 changes: 33 additions & 24 deletions apps/dashboard_app/helpers/ekubo.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def apply_liquidity_to_dataframe(

return self.data

def fetch_liquidity(self, bids: bool = True) -> dict[str, str | list[float]]:
def fetch_liquidity(self, bids: bool = True) -> dict[str, str | list[float]] | None:
"""
Fetching liquidity from API endpoint and structuring data to comfortable format.
Returns dictionary with the following struct:
Expand All @@ -101,33 +101,42 @@ def fetch_liquidity(self, bids: bool = True) -> dict[str, str | list[float]]:
:param bids: bool = True
:return: dict[str, str | list[float]]
"""
max_retries = 5
retry_delay = 5 if not bids else 300

params = self.params_for_bids if bids else self.params_for_asks
attempt = 0

while attempt < max_retries:
response = requests.get(self.URL, params=params)

if response.ok:
liquidity = response.json()
data = {
"type": "bids" if bids else "asks",
}

if data["type"] in liquidity:
try:
data["prices"], data["quantities"] = zip(
*liquidity[data["type"]]
)
return {
"type": data["type"],
"prices": data["prices"],
"quantities": data["quantities"],
}
except ValueError:
logging.warning("Invalid response format, retrying...")

params = self.params_for_bids
if not bids:
params = self.params_for_asks
logging.warning(
"Using collateral token as base token and debt token as quote token."
f"API request failed (attempt {attempt + 1}/{max_retries}), retrying in {retry_delay} seconds..."
)
time.sleep(retry_delay)
attempt += 1

response = requests.get(self.URL, params=params)

if response.ok:
liquidity = response.json()
data = {
"type": "bids" if bids else "asks",
}
try:
data["prices"], data["quantities"] = zip(*liquidity[data["type"]])
except ValueError:
time.sleep(300 if bids else 5)
self.fetch_liquidity(bids=True)
else:
return data
else:
time.sleep(300 if bids else 5)
self.fetch_liquidity(
bids=False if bids else True,
)
logging.error("Max retries reached. Could not fetch liquidity.")
return None

def _get_available_liquidity(
self, data: pandas.DataFrame, price: float, price_diff: float, bids: bool
Expand Down
112 changes: 112 additions & 0 deletions apps/dashboard_app/tests/test_ekubo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from unittest.mock import MagicMock, patch

import pandas as pd
import pytest

from dashboard_app.helpers.ekubo import EkuboLiquidity


@pytest.fixture
def sample_data():
"""Fixture to initialize sample Dataframe."""
return pd.DataFrame(
{
"price": [1.0, 1.1, 1.2],
"debt_token_supply": [100, 200, 300],
"collateral_token_price": [1.0, 1.1, 1.2],
"Ekubo_debt_token_supply": [10, 20, 30],
}
)


@pytest.fixture
def liquidity_instance(sample_data):
"""Fixture for initializing EkuboLiquidity instance."""
return EkuboLiquidity(
data=sample_data,
collateral_token="0x0000000000000000000000000000000000000001",
debt_token="0x0000000000000000000000000000000000000002",
)


@patch("requests.get")
def test_fetch_liquidity_bids(mock_get, liquidity_instance):
"""Test fetch_liquidity() correctly parses API response for bids."""
mock_get.return_value.ok = True
mock_get.return_value.json.return_value = {
"bids": [[1.0, 50.0], [1.1, 40.0], [1.2, 30.0]]
}

result = liquidity_instance.fetch_liquidity(bids=True)

assert result["type"] == "bids"
assert result["prices"] == (1.0, 1.1, 1.2)
assert result["quantities"] == (50.0, 40.0, 30.0)


@patch("requests.get")
def test_fetch_liquidity_asks(mock_get, liquidity_instance):
"""Test fetch_liquidity() correctly parses API response for asks."""
mock_get.return_value.ok = True
mock_get.return_value.json.return_value = {
"asks": [[1.0, 20.0], [1.1, 15.0], [1.2, 10.0]]
}

result = liquidity_instance.fetch_liquidity(bids=False)

assert result["type"] == "asks"
assert result["prices"] == (1.0, 1.1, 1.2)
assert result["quantities"] == (20.0, 15.0, 10.0)


def test_apply_liquidity_to_dataframe(liquidity_instance):
"""Test apply_liquidity_to_dataframe updates DataFrame correctly."""
mock_bids_or_asks = {
"type": "bids",
"prices": [1.0, 1.1, 1.2],
"quantities": [50.0, 40.0, 30.0],
}

updated_df = liquidity_instance.apply_liquidity_to_dataframe(mock_bids_or_asks)

assert "Ekubo_debt_token_supply" in updated_df.columns
assert "debt_token_supply" in updated_df.columns
assert updated_df["debt_token_supply"].iloc[0] >= 100 # Supply should increase


def test_remove_leading_zeros():
"""Test _remove_leading_zeros correctly removes extra zeros."""
assert (
EkuboLiquidity._remove_leading_zeros(
"0x0000000000000000000000000000000000001234"
)
== "0x1234"
)
assert (
EkuboLiquidity._remove_leading_zeros(
"0x000000000000000000000000000000000000abcd"
)
== "0xabcd"
)


def test_get_available_liquidity(liquidity_instance):
"""Test _get_available_liquidity retrieves correct sum of liquidity."""
data = pd.DataFrame({"price": [1.0, 1.1, 1.2], "quantity": [50.0, 40.0, 30.0]})
result = liquidity_instance._get_available_liquidity(
data, price=1.05, price_diff=0.1, bids=True
)

assert result == 50.0 # Only one price in range [0.95, 1.05]


@patch("requests.get")
def test_fetch_liquidity_fails(mock_get, liquidity_instance):
"""Test fetch_liquidity handles API failures gracefully."""
mock_get.return_value.ok = False # Simulate API failure

with patch("time.sleep") as mock_sleep:
result = liquidity_instance.fetch_liquidity(bids=True)
mock_sleep.assert_called() # Ensure sleep is triggered on failure

assert result is None # Should return None on failure

0 comments on commit 2cbec76

Please sign in to comment.