Archived
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
import json
|
|
import os
|
|
import subprocess
|
|
from dataclasses import asdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import cast
|
|
|
|
import pytest
|
|
|
|
from recoll_modern.engine.protocol import Engine, EngineHealth, EngineHit, EnginePage, SearchRequest
|
|
from recoll_modern.engine.recoll import RecollEngine
|
|
|
|
from .engine_contract import EngineContract
|
|
|
|
_CONTAINER_SCRIPT = """
|
|
import json, sys
|
|
from dataclasses import asdict
|
|
from pathlib import Path
|
|
from recoll_modern.engine.protocol import SearchRequest
|
|
from recoll_modern.engine.recoll import RecollEngine
|
|
engine = RecollEngine(Path('/var/lib/recoll-modern/recoll-config'))
|
|
method, payload = sys.argv[1], json.loads(sys.argv[2])
|
|
if method == 'search':
|
|
result = engine.search(SearchRequest(**payload))
|
|
elif method == 'health':
|
|
result = engine.health()
|
|
elif method == 'extracted_text':
|
|
result = engine.extracted_text(payload['path'])
|
|
else:
|
|
raise ValueError(method)
|
|
print(json.dumps(asdict(result) if method != 'extracted_text' else result, default=str))
|
|
"""
|
|
|
|
|
|
class ContainerRecollEngine:
|
|
def _call(self, method: str, payload: dict[str, object]) -> object:
|
|
result = subprocess.run(
|
|
["docker", "compose", "exec", "-T", "app", "python", "-c", _CONTAINER_SCRIPT,
|
|
method, json.dumps(payload)],
|
|
capture_output=True, text=True, check=False, timeout=30,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
return json.loads(result.stdout)
|
|
|
|
def search(self, request: SearchRequest) -> EnginePage:
|
|
value = cast(dict[str, object], self._call("search", asdict(request)))
|
|
hits = []
|
|
for raw in cast(list[dict[str, object]], value["hits"]):
|
|
date = raw["date"]
|
|
raw["date"] = datetime.fromisoformat(date) if isinstance(date, str) else None
|
|
raw["collection_ids"] = tuple(cast(list[str], raw["collection_ids"]))
|
|
raw["snippets"] = tuple(cast(list[str], raw["snippets"]))
|
|
hits.append(EngineHit(**raw)) # type: ignore[arg-type]
|
|
return EnginePage(
|
|
tuple(hits), int(cast(int, value["total"])), bool(value["total_is_approximate"])
|
|
)
|
|
|
|
def health(self) -> EngineHealth:
|
|
value = cast(dict[str, object], self._call("health", {}))
|
|
return EngineHealth(bool(value["available"]), cast(str | None, value["message"]))
|
|
|
|
def extracted_text(self, path: str) -> str:
|
|
return cast(str, self._call("extracted_text", {"path": path}))
|
|
|
|
def force_reindex(self, paths: tuple[str, ...]) -> None:
|
|
raise NotImplementedError
|
|
|
|
|
|
class TestRecollEngine(EngineContract):
|
|
@pytest.fixture(autouse=True)
|
|
def available_engine(self) -> None:
|
|
config = os.environ.get("RECOLL_MODERN_TEST_RECOLL_CONFIG_DIR")
|
|
if config is None:
|
|
if os.environ.get("RECOLL_MODERN_REQUIRE_DOCKER") != "1":
|
|
pytest.skip("Recoll test container is not running")
|
|
bootstrap = subprocess.run(
|
|
["docker", "compose", "exec", "-T", "app", "recollindex", "-c",
|
|
"/var/lib/recoll-modern/recoll-config", "-i", "-Z",
|
|
"/documents"],
|
|
capture_output=True, text=True, check=False, timeout=30,
|
|
)
|
|
assert bootstrap.returncode == 0, bootstrap.stderr
|
|
self.engine = cast(Engine, ContainerRecollEngine())
|
|
assert self.engine.health().available
|
|
return
|
|
engine = RecollEngine(Path(config))
|
|
if not engine.health().available:
|
|
pytest.skip("Recoll test container is not running")
|
|
self.engine = engine
|