mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
* fix cboe's cache lock so that concurrent callers in separate processes don't hang * fix test coverage and ty check * more test failure * more test failure * more Windows Python 3.10 test failure * Move lint path filter into a job so required checks always report lint-openbb-platform.yml used `on.pull_request.paths` to skip PRs that only touch api/extensions/providers/obbject_extensions. A workflow skipped by a trigger filter never reports its checks, so the required "General Code Linting" and "Markdown Linting" checks stayed pending forever on those PRs. Detect relevant changes in a `changes` job instead and gate the lint jobs with a job-level `if:`. Skipped jobs report success, so required checks resolve. The gate fails closed: if detection fails, the lint jobs still run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: dlee-bq <325002271+dlee-bq@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
3.8 KiB
Python
109 lines
3.8 KiB
Python
"""Tests for openbb_cboe.utils.shared_cache."""
|
|
|
|
import asyncio
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from openbb_cboe.utils.shared_cache import SharedCache, SharedStore
|
|
|
|
|
|
async def _gather(iterable):
|
|
"""Collect everything an async iterable yields."""
|
|
return [item async for item in iterable]
|
|
|
|
|
|
class TestSharedStore:
|
|
"""A store hands every operation to ``call``, whichever loop it is used on."""
|
|
|
|
@pytest.mark.parametrize(
|
|
("method", "args", "returned"),
|
|
[
|
|
("contains", ("key",), True),
|
|
("read", ("key",), "item"),
|
|
("size", (), 7),
|
|
("write", ("key", "item"), None),
|
|
("delete", ("key",), None),
|
|
("bulk_delete", ({"key"},), None),
|
|
("clear", (), None),
|
|
],
|
|
)
|
|
def test_an_operation_is_run_where_the_connection_is(self, method, args, returned):
|
|
"""The table, the method and its arguments go over, and the result comes back."""
|
|
call = AsyncMock(return_value=returned)
|
|
store = SharedStore("responses", call)
|
|
|
|
result = asyncio.run(getattr(store, method)(*args))
|
|
|
|
assert result == returned
|
|
call.assert_awaited_once_with("responses", method, *args)
|
|
|
|
@pytest.mark.parametrize("method", ["keys", "values"])
|
|
def test_what_is_yielded_is_yielded_here(self, method):
|
|
"""Keys and values arrive gathered, and are yielded on the caller's loop."""
|
|
call = AsyncMock(return_value=["a", "b"])
|
|
store = SharedStore("redirects", call)
|
|
|
|
assert asyncio.run(_gather(getattr(store, method)())) == ["a", "b"]
|
|
call.assert_awaited_once_with("redirects", method)
|
|
|
|
def test_closing_leaves_the_connection_to_the_cache(self):
|
|
"""Other requests are still using the connection, so a session may not close it."""
|
|
call = AsyncMock()
|
|
|
|
asyncio.run(SharedStore("responses", call).close())
|
|
|
|
call.assert_not_awaited()
|
|
|
|
def test_pop_reads_then_deletes(self):
|
|
"""The base class's ``pop`` works through the same two operations."""
|
|
call = AsyncMock(side_effect=lambda table, method, *args: "item")
|
|
|
|
assert asyncio.run(SharedStore("responses", call).pop("key")) == "item"
|
|
assert [step.args[1] for step in call.await_args_list] == ["read", "delete"]
|
|
|
|
def test_an_error_where_the_connection_is_reaches_the_caller(self):
|
|
"""A failed read is the failure of the request that made it."""
|
|
call = AsyncMock(side_effect=OSError("disk"))
|
|
|
|
with pytest.raises(OSError):
|
|
asyncio.run(SharedStore("responses", call).read("key"))
|
|
|
|
|
|
class TestSharedCache:
|
|
"""The backend decides expiry and keys itself, and stores through ``call``."""
|
|
|
|
def test_its_tables_are_the_responses_and_the_redirects(self):
|
|
"""Each table of the backend is a store of the same name."""
|
|
call = AsyncMock()
|
|
cache = SharedCache(call)
|
|
|
|
asyncio.run(cache.responses.write("key", "item"))
|
|
asyncio.run(cache.redirects.write("alias", "key"))
|
|
|
|
assert [step.args[:2] for step in call.await_args_list] == [
|
|
("responses", "write"),
|
|
("redirects", "write"),
|
|
]
|
|
|
|
def test_it_keeps_the_settings_it_is_given(self):
|
|
"""Expiry is applied on the caller's loop, from the backend's own settings."""
|
|
cache = SharedCache(
|
|
AsyncMock(),
|
|
expire_after=60,
|
|
urls_expire_after={"*/book/*": 5},
|
|
autoclose=False,
|
|
)
|
|
|
|
assert cache.expire_after == 60
|
|
assert cache.urls_expire_after == {"*/book/*": 5}
|
|
assert cache.autoclose is False
|
|
|
|
def test_closing_it_does_not_close_the_shared_connection(self):
|
|
"""Closing the backend is not closing the connection other requests use."""
|
|
call = AsyncMock()
|
|
|
|
asyncio.run(SharedCache(call).close())
|
|
|
|
call.assert_not_awaited()
|