EverOS/tests/unit/test_component/test_parser_available_memoi...

112 lines
4.5 KiB
Python

"""``parser_available`` memoisation contract.
Round-3 review finding #14: ``/health`` used to trigger a heavy
``import everalgo.parser`` on every hit — pypdf / python-docx /
tesseract pulled in on the event loop, hundreds of ms of blocking
work per request. The fix wraps :func:`parser_available` in
``functools.lru_cache(maxsize=1)`` so only the first call in the
process pays the import cost; a lifespan warms that first call at
boot so ``/health`` itself never triggers it.
These tests pin the cache: the second call must reuse the cached
answer without re-attempting the import. ``cache_clear()`` is invoked
explicitly around each mutation of ``sys.modules`` to avoid
cross-test contamination — that discipline is contractual for any
test that toggles the module's importability.
"""
from __future__ import annotations
import sys
from unittest.mock import MagicMock, patch
import pytest
from everos.component.parser import parser_available
@pytest.fixture(autouse=True)
def _clear_cache_between_tests() -> None:
"""Drop the lru_cache before and after each test.
Without this, ``sys.modules`` swaps inside one test leak into the
next — the second test would see the frozen decision from the
first even after restoring ``sys.modules``.
"""
parser_available.cache_clear()
yield
parser_available.cache_clear()
def test_parser_available_second_call_hits_cache() -> None:
"""Second invocation must NOT re-run the ``import everalgo.parser``.
Uses :meth:`functools.lru_cache.cache_info` to confirm the second
call is a cache hit — the underlying ``import`` statement never
ran a second time, so ``/health`` can no longer trigger a fresh
import per request.
We deliberately avoid spying on ``builtins.__import__``: even a
cached ``import`` still goes through ``__import__`` for the
``sys.modules`` lookup, so a call-count assertion at that layer
can't distinguish "cached" from "re-imported". The cache-info
counters are the only reliable signal here.
"""
first = parser_available()
second = parser_available()
info = parser_available.cache_info()
assert first is second, "cached identity"
assert info.hits >= 1, f"expected at least one cache hit; got {info}"
assert info.misses == 1, f"expected exactly one miss (the warming call); got {info}"
def test_parser_available_caches_missing_extra_result() -> None:
"""When the extra isn't installed, the cache must remember ``False``
just as strongly as it would remember ``True``. Otherwise ``/health``
on a base install would re-attempt the (failing) import every hit."""
# Simulate the missing-extra case by making the module import raise
# ImportError. Patch `sys.modules` to insert a sentinel that Python's
# import machinery treats as "already imported → don't re-run"; use
# `builtins.__import__` wrapper to raise instead.
original_import = __import__
def _fake_import(name, *args, **kwargs): # type: ignore[no-untyped-def]
if name == "everalgo.parser":
raise ImportError("simulated missing extra")
return original_import(name, *args, **kwargs)
with patch("builtins.__import__", side_effect=_fake_import):
first = parser_available()
second = parser_available()
# Both calls returned False — but critically only the first ran the
# failing import; the second was a hot cache read (proved by the fact
# that we can `parser_available.cache_info().hits >= 1`).
assert first is False
assert second is False
assert parser_available.cache_info().hits >= 1
def test_parser_available_cache_clear_re_evaluates() -> None:
"""``cache_clear()`` is contractually the escape hatch for tests —
prove it works, so future tests toggling ``sys.modules`` have a
reliable reset primitive."""
# Force a False decision, then clear + swap to a True decision.
fake_module = MagicMock()
def _import_missing(name, *args, **kwargs): # type: ignore[no-untyped-def]
if name == "everalgo.parser":
raise ImportError("first pass: missing")
return __import__(name, *args, **kwargs)
with patch("builtins.__import__", side_effect=_import_missing):
assert parser_available() is False
parser_available.cache_clear()
# Now pretend the module is importable by pre-seeding sys.modules —
# `import everalgo.parser` will find it there and skip the loader.
with patch.dict(sys.modules, {"everalgo.parser": fake_module}):
assert parser_available() is True