from __future__ import annotations import multiprocessing import subprocess from pathlib import Path import pytest import tools.rag.rebuild_index as rebuild def hold_lock(lock_path: str, ready, release) -> None: with rebuild.acquire_reindex_lock(Path(lock_path)): ready.set() release.wait(timeout=10) def test_only_one_process_can_reindex(tmp_path: Path) -> None: lock_file = tmp_path / "reindex.lock" context = multiprocessing.get_context("fork") ready = context.Event() release = context.Event() process = context.Process( target=hold_lock, args=(str(lock_file), ready, release), ) process.start() try: assert ready.wait(timeout=5) with pytest.raises( rebuild.ReindexInProgressError, match="už prebieha", ): with rebuild.acquire_reindex_lock(lock_file): pass finally: release.set() process.join(timeout=5) assert process.exitcode == 0 with rebuild.acquire_reindex_lock(lock_file): pass def test_rebuild_runs_pipeline_in_correct_order( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: calls: list[str] = [] monkeypatch.setattr( rebuild, "git_pull", lambda root: calls.append("git_pull"), ) import tools.sync_indexes as sync monkeypatch.setattr(sync, 'synchronize_indexes', lambda wiki, data: calls.append('sqlite_and_neo4j') or {'documents_scanned': 1, 'chunks_created': 1, 'counts': {'fts_chunks': 1}, 'index_version': {'version': 'both'}}) result = rebuild.rebuild_index( pull_git=True, lock_file=tmp_path / "lock", ) assert calls == ["git_pull", "sqlite_and_neo4j"] assert result["documents_scanned"] == 1 assert result["chunks_created"] == 1 assert result["counts"]["fts_chunks"] == 1 assert result['index_version'] == {'version': 'both'} def test_rebuild_pulls_and_locks_the_requested_snapshot_paths(tmp_path, monkeypatch): import tools.sync_indexes as sync wiki = tmp_path / 'alternate-wiki' data = tmp_path / 'alternate-data' seen = [] monkeypatch.setattr(rebuild, 'git_pull', lambda root: seen.append(('pull', root))) def synchronized(root, target): assert (data / '.reindex.lock').exists() seen.append(('sync', root, target)) return {'index_version': {'version': 'alternate'}} monkeypatch.setattr(sync, 'synchronize_indexes', synchronized) result = rebuild.rebuild_index(pull_git=True, wiki_root=wiki, data_dir=data) assert seen == [('pull', wiki), ('sync', wiki, data)] assert result['index_version']['version'] == 'alternate' def test_git_pull_uses_fast_forward_only( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: repo = tmp_path / "repo" (repo / ".git").mkdir(parents=True) captured = {} def fake_run(args, **kwargs): captured["args"] = args captured["kwargs"] = kwargs return subprocess.CompletedProcess(args, 0, stdout="OK", stderr="") monkeypatch.setattr(rebuild.subprocess, "run", fake_run) rebuild.git_pull(repo) assert captured["args"] == ["git", "pull", "--ff-only"] assert captured["kwargs"]["cwd"] == repo assert captured["kwargs"]["timeout"] == rebuild.GIT_PULL_TIMEOUT_SECONDS def test_git_pull_rejects_non_repository(tmp_path: Path) -> None: directory = tmp_path / "not-repo" directory.mkdir() with pytest.raises(RuntimeError, match="Nie je to git repozitár"): rebuild.git_pull(directory) def test_git_pull_reports_timeout( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: repo = tmp_path / "repo" (repo / ".git").mkdir(parents=True) def timeout(*args, **kwargs): raise subprocess.TimeoutExpired(cmd="git pull", timeout=120) monkeypatch.setattr(rebuild.subprocess, "run", timeout) with pytest.raises(RuntimeError, match="prekročil limit"): rebuild.git_pull(repo) def test_git_pull_reports_nonzero_exit( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: repo = tmp_path / "repo" (repo / ".git").mkdir(parents=True) monkeypatch.setattr( rebuild.subprocess, "run", lambda *args, **kwargs: subprocess.CompletedProcess( args[0], 1, stdout="", stderr="chyba", ), ) with pytest.raises(RuntimeError, match="návratovým kódom 1"): rebuild.git_pull(repo)