From 93e7e53ed0ee19a14965773a5fca5629498abaea Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Sat, 25 Jul 2026 16:15:30 -0400 Subject: [PATCH 1/2] fix(tests): keep the offline gate hermetic for model-fallback tests The embedder/reranker factory fallback tests constructed a real SentenceTransformer/CrossEncoder for an unresolvable model name, which resolves against the Hugging Face Hub. On a host with no route to the Hub that connect() blocks instead of erroring, so "python -m pytest tests/ -q" - the documented offline gate - hung indefinitely rather than failing. The fallback relied on the network failing fast, not on it being absent. Patch the loader in both tests so they exercise the fallback contract locally and instantly with no network, per AGENTS.md section 3 (local-first and offline-capable). --- tests/test_backends_factories.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test_backends_factories.py b/tests/test_backends_factories.py index baee1aa..91232dc 100644 --- a/tests/test_backends_factories.py +++ b/tests/test_backends_factories.py @@ -8,9 +8,30 @@ from engraphis.core.store import Store -def test_embedder_factory_falls_back_offline(): +def _force_unresolvable(monkeypatch, attr: str) -> None: + """Make the sentence-transformers loader fail immediately instead of hitting the network. + + The factories fall back when a model fails to load, but resolving an unknown model name + normally goes out to the Hugging Face Hub. On a host with no route to the Hub that + connect() blocks rather than erroring, so the offline gate would hang forever — the + fallback relies on the network failing *fast*, not on it being *absent* (AGENTS.md §3.8: + the core must run offline). Patching the loader keeps this hermetic and instant. + """ + try: + import sentence_transformers as st + except ImportError: + return # not installed — the factory already fails fast with ModuleNotFoundError + + def _raise(*args, **kwargs): + raise OSError("simulated unresolvable model (offline)") + + monkeypatch.setattr(st, attr, _raise) + + +def test_embedder_factory_falls_back_offline(monkeypatch): assert isinstance(get_embedder(None, 128), DeterministicEmbedder) # An unresolvable model name must not crash — it falls back. + _force_unresolvable(monkeypatch, "SentenceTransformer") assert isinstance(get_embedder("definitely-not-a-real-model-xyz", 128), DeterministicEmbedder) @@ -46,6 +67,7 @@ def __init__(self, *a, **k): s.close() -def test_reranker_factory_falls_back_offline(): +def test_reranker_factory_falls_back_offline(monkeypatch): assert isinstance(get_reranker(None), IdentityReranker) + _force_unresolvable(monkeypatch, "CrossEncoder") assert isinstance(get_reranker("definitely-not-a-real-model-xyz"), IdentityReranker) From 06f09b0b6099d6085355b27b32eff7ed266d422a Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Sat, 25 Jul 2026 16:44:31 -0400 Subject: [PATCH 2/2] fix(tests): patch the factory adapters, not sentence-transformers Addresses review feedback on PR #72. The first version imported sentence_transformers in the test helper and only guarded ImportError. If the package imports but raises something else - a RuntimeError from a mismatched torch/torchvision, say - the helper propagated it and the test failed, even though both production factories catch Exception and fall back correctly. That made the fallback tests depend on the health of the optional heavy stack. Patch the adapter each factory constructs instead (SentenceTransformerEmbedder, CrossEncoderReranker). The optional stack is now never imported by these tests, so every package-import failure stays inside the factory under test, where the existing except Exception handles it. Verified: sentence_transformers is absent from sys.modules after the module's tests run. --- tests/test_backends_factories.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tests/test_backends_factories.py b/tests/test_backends_factories.py index 91232dc..0a425b7 100644 --- a/tests/test_backends_factories.py +++ b/tests/test_backends_factories.py @@ -8,30 +8,33 @@ from engraphis.core.store import Store -def _force_unresolvable(monkeypatch, attr: str) -> None: - """Make the sentence-transformers loader fail immediately instead of hitting the network. +def _force_load_failure(monkeypatch, module, attr: str) -> None: + """Make the heavy model adapter fail at construction, without touching the network. The factories fall back when a model fails to load, but resolving an unknown model name normally goes out to the Hugging Face Hub. On a host with no route to the Hub that connect() blocks rather than erroring, so the offline gate would hang forever — the - fallback relies on the network failing *fast*, not on it being *absent* (AGENTS.md §3.8: - the core must run offline). Patching the loader keeps this hermetic and instant. + fallback relies on the network failing *fast*, not on it being *absent* (AGENTS.md §3: + the core must run offline). + + Patch the adapter the factory constructs, not sentence-transformers itself: the optional + heavy stack is then never imported here, so an install that raises something other than + ImportError (e.g. a RuntimeError from a mismatched torch) cannot fail this test. Every + such failure stays inside the factory, which catches Exception and falls back — which is + exactly the contract under test. """ - try: - import sentence_transformers as st - except ImportError: - return # not installed — the factory already fails fast with ModuleNotFoundError - def _raise(*args, **kwargs): raise OSError("simulated unresolvable model (offline)") - monkeypatch.setattr(st, attr, _raise) + monkeypatch.setattr(module, attr, _raise) def test_embedder_factory_falls_back_offline(monkeypatch): + import engraphis.backends.embedder_st as embedder_st + assert isinstance(get_embedder(None, 128), DeterministicEmbedder) # An unresolvable model name must not crash — it falls back. - _force_unresolvable(monkeypatch, "SentenceTransformer") + _force_load_failure(monkeypatch, embedder_st, "SentenceTransformerEmbedder") assert isinstance(get_embedder("definitely-not-a-real-model-xyz", 128), DeterministicEmbedder) @@ -68,6 +71,8 @@ def __init__(self, *a, **k): def test_reranker_factory_falls_back_offline(monkeypatch): + import engraphis.backends.reranker as reranker + assert isinstance(get_reranker(None), IdentityReranker) - _force_unresolvable(monkeypatch, "CrossEncoder") + _force_load_failure(monkeypatch, reranker, "CrossEncoderReranker") assert isinstance(get_reranker("definitely-not-a-real-model-xyz"), IdentityReranker)