"""The vendored converter is resolved explicitly, or not used at all. `pypandoc` searches `PATH` before its own bundled binary and takes the HIGHEST version it finds. Measured three times independently on this host: the wheel carries pandoc 3.9, the host carries 3.10.2, and `pypandoc.get_pandoc_version()` returns 3.10.2. So "we vendored the binary" buys nothing on its own -- the bundle would be built by a converter nobody chose, and nothing would say so. These tests pin the resolution, not the conversion. The seam that converts is tested separately; what is falsifiable here is which binary a conversion would have used, and that the answer is refused rather than guessed when it is wrong. """ from __future__ import annotations import os import sys from pathlib import Path import pytest from llm_ingestion_okf._pandoc import ( PANDOC_VERSION, converter_path, resolve_pandoc, ) from llm_ingestion_okf.errors import ExtractionError pytest.importorskip("pypandoc", reason="the [extract] extra is not installed") def test_the_resolved_binary_is_the_bundled_one_not_a_path_binary() -> None: """The whole point of the module, stated as an assertion. The bundled binary lives inside the installed package; a PATH binary does not. Comparing the resolved path against the package directory is what distinguishes them -- comparing versions would not, because a host could coincidentally carry the pinned version today and a different one tomorrow. """ import pypandoc resolved = resolve_pandoc() bundled = Path(pypandoc.__file__).parent / "files" / "pandoc" assert resolved == bundled assert resolved.is_file() def test_the_resolved_binary_reports_the_pinned_version() -> None: import subprocess # noqa: S404 - test-side only; `src/` stays free of it reported = subprocess.run( # noqa: S603 [str(resolve_pandoc()), "--version"], capture_output=True, text=True ).stdout.split("\n")[0] assert reported == f"pandoc {PANDOC_VERSION}" def test_resolution_is_not_poisoned_by_an_earlier_probe_in_the_process() -> None: """The regression this file did not catch on its first pass. `pypandoc.get_pandoc_version()` answers from a module global that `clean_pandocpath_cache()` does not reset. Using it meant the version check described whichever binary was probed FIRST in the process -- so the first implementation reported the host's 3.10.2 for the bundled 3.9 binary, and the suite stayed green because nothing in it probed the host binary first. A test that passes only in a fresh process is not a test of the resolver. This one poisons the caches the way real use does, then resolves. """ import pypandoc pypandoc.get_pandoc_version() # caches whatever the search finds assert resolve_pandoc().is_file() # must not raise extractor_binary_version def test_a_version_mismatch_is_refused_and_names_both_versions( monkeypatch: pytest.MonkeyPatch, ) -> None: """Refused, not used with a warning. Extraction is deterministic within a converter version and not across one, and the byte-pinned fixtures cannot tell "different converter" from "defect". A mismatch that proceeded would make every later measurement unattributable. """ monkeypatch.setattr("llm_ingestion_okf._pandoc.PANDOC_VERSION", "0.0.0") with pytest.raises(ExtractionError) as excinfo: resolve_pandoc() assert excinfo.value.code == "extractor_binary_version" message = str(excinfo.value) assert "0.0.0" in message, "the expected version is not named" assert PANDOC_VERSION in message, "the found version is not named" def test_an_absent_binary_raises_binary_missing( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: """Distinct from the extra being absent: the wheel can be there without it.""" import pypandoc monkeypatch.setattr(pypandoc, "__file__", str(tmp_path / "pypandoc" / "__init__.py")) with pytest.raises(ExtractionError) as excinfo: resolve_pandoc() assert excinfo.value.code == "extractor_binary_missing" def test_the_extra_being_absent_keeps_the_same_rejection( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setitem(sys.modules, "pypandoc", None) with pytest.raises(ExtractionError) as excinfo: resolve_pandoc() assert excinfo.value.code == "extractor_extra_missing" assert "extract" in str(excinfo.value) def test_the_scoped_path_leaves_os_environ_exactly_as_it_found_it() -> None: """A library must not set a process-global that outlives its own call. `pypandoc` offers no per-call path parameter -- the only override is the `PYPANDOC_PANDOC` environment variable plus a cached module global. Both are process-wide, so the scope is where the discipline has to live: enter, convert, restore, whether or not the body raised. """ before = dict(os.environ) with converter_path() as path: assert os.environ["PYPANDOC_PANDOC"] == str(path) assert dict(os.environ) == before with pytest.raises(RuntimeError): with converter_path(): raise RuntimeError("the body failed") assert dict(os.environ) == before, "an exception must not leak the override" def test_a_pre_existing_override_is_restored_not_dropped( monkeypatch: pytest.MonkeyPatch, ) -> None: """Restoring means putting back what was there, including a wrong value. Deleting the key on exit would look correct in an environment that had none, and would silently erase an operator's deliberate override in one that did. """ monkeypatch.setenv("PYPANDOC_PANDOC", "/somewhere/else/pandoc") with converter_path(): pass assert os.environ["PYPANDOC_PANDOC"] == "/somewhere/else/pandoc"