Files
Danglewood 869cfed125 [Release] openbb-v5.0.0 (#7741)
* merge branch develop with main

* bump desktop version

* fix the merge

* sync Cargo.lock to desktop 2.0.0

* fix cli conftest.py

---------

Co-authored-by: deeleeramone <>
2026-09-29 10:58:44 -07:00

192 lines
6.3 KiB
Python

"""Tests for the Deribit response cache."""
import pytest
from openbb_deribit.utils import cache
class _Store:
"""A stand-in for the backend's response store."""
def __init__(self, keys):
self._keys = list(keys)
self.deleted: list = []
async def keys(self):
"""Yield every stored key."""
for key in self._keys:
yield key
async def size(self):
"""Return how many responses are stored."""
return len(self._keys)
async def bulk_delete(self, keys):
"""Forget the named responses."""
self.deleted.append(set(keys))
self._keys = [key for key in self._keys if key not in keys]
class _Backend:
"""A stand-in for the SQLite backend."""
def __init__(self, keys=()):
self.responses = _Store(keys)
self.expired = 0
async def delete_expired_responses(self):
"""Mark the expired responses for deletion."""
self.expired += 1
@pytest.fixture(autouse=True)
def _reset_sweep():
"""Let each test sweep once, as the first caller in a process would."""
cache._swept = False
yield
cache._swept = False
class TestConfiguration:
"""The cache is bounded by a TTL per endpoint and a size cap."""
def test_path_lives_under_the_user_cache(self):
"""The cache file is kept with the other OpenBB HTTP caches."""
assert cache.cache_path().endswith("/http/deribit")
def test_backend_carries_the_ttl_map(self, tmp_path, monkeypatch):
"""The backend is built with the per-endpoint expiries."""
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
backend = cache.get_cache_backend()
assert backend.expire_after == cache.CACHE_TTL_DEFAULT
assert backend.urls_expire_after
def test_live_endpoints_are_never_cached(self):
"""A quote or a book is read fresh every time."""
for pattern in (
"*/public/get_order_book*",
"*/public/ticker*",
"*/public/get_time*",
"*/public/status*",
):
assert cache.URL_CACHE_TTL[pattern] == 0
def test_reference_endpoints_are_held_longest(self):
"""A listing that barely changes is held for a day."""
assert cache.URL_CACHE_TTL["*/public/get_currencies*"] == cache.DAY
class TestSweep:
"""The sweep drops what expired, then trims what is still too big."""
@pytest.mark.asyncio
async def test_deletes_expired_and_vacuums(self, monkeypatch, tmp_path):
"""A file under the cap is swept once and left alone."""
stored = tmp_path / "deribit.sqlite"
stored.write_bytes(b"x")
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
vacuums: list = []
async def _vacuum(backend):
vacuums.append(backend)
monkeypatch.setattr(cache, "_vacuum", _vacuum)
backend = _Backend(["a", "b"])
await cache.sweep_cache(backend)
assert backend.expired == 1
assert len(vacuums) == 1
assert backend.responses.deleted == []
@pytest.mark.asyncio
async def test_trims_by_the_share_that_overshoots(self, monkeypatch, tmp_path):
"""A file over the cap loses the oldest share of its rows."""
stored = tmp_path / "deribit.sqlite"
stored.write_bytes(b"x" * 400)
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
monkeypatch.setattr(cache, "CACHE_MAX_BYTES", 100)
backend = _Backend([f"key-{index}" for index in range(8)])
async def _vacuum(_):
if backend.responses.deleted:
stored.write_bytes(b"x" * 50)
monkeypatch.setattr(cache, "_vacuum", _vacuum)
await cache.sweep_cache(backend)
assert backend.responses.deleted
assert len(backend.responses.deleted[0]) == 6
@pytest.mark.asyncio
async def test_stops_when_nothing_is_left_to_drop(self, monkeypatch, tmp_path):
"""A file still too big with nothing stored gives up."""
stored = tmp_path / "deribit.sqlite"
stored.write_bytes(b"x" * 400)
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
monkeypatch.setattr(cache, "CACHE_MAX_BYTES", 100)
async def _vacuum(backend):
return None
monkeypatch.setattr(cache, "_vacuum", _vacuum)
backend = _Backend([])
await cache.sweep_cache(backend)
assert backend.responses.deleted == []
@pytest.mark.asyncio
async def test_missing_file(self, monkeypatch, tmp_path):
"""A cache that was never written is swept without error."""
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
async def _vacuum(backend):
return None
monkeypatch.setattr(cache, "_vacuum", _vacuum)
backend = _Backend([])
await cache.sweep_cache(backend)
assert backend.expired == 1
@pytest.mark.asyncio
async def test_sweeps_once_per_process(self, monkeypatch, tmp_path):
"""A second caller does not sweep again."""
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
async def _vacuum(backend):
return None
monkeypatch.setattr(cache, "_vacuum", _vacuum)
backend = _Backend([])
await cache.sweep_cache(backend)
await cache.sweep_cache(backend)
assert backend.expired == 1
@pytest.mark.asyncio
async def test_oldest_stops_at_the_count(self):
"""The trim reads only as many keys as it means to drop."""
backend = _Backend([f"key-{index}" for index in range(10)])
assert len(await cache._oldest(backend, 3)) == 3
class TestVacuum:
"""Vacuuming gives back the pages the deleted responses held."""
@pytest.mark.asyncio
async def test_skips_a_foreign_backend(self):
"""A store that is not SQLite is left alone."""
assert await cache._vacuum(_Backend([])) is None
@pytest.mark.asyncio
async def test_runs_against_sqlite(self, tmp_path, monkeypatch):
"""A real SQLite store is vacuumed."""
monkeypatch.setattr(cache, "cache_path", lambda: str(tmp_path / "deribit"))
backend = cache.get_cache_backend()
await backend.responses.write("key", b"value")
await cache._vacuum(backend)
assert (tmp_path / "deribit.sqlite").exists()