from __future__ import annotations import multiprocessing import subprocess from pathlib import Path import pytest import tools.rag.rebuild_index as rebuild import tools.wiki_git as wiki_git 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_explicit_branch_and_fast_forward_only(tmp_path, monkeypatch): repo = tmp_path / 'repo' (repo / '.git').mkdir(parents=True) captured = [] def fake_run(args, **kwargs): captured.append((args, kwargs)) output = {'remote': 'git@git.example:KEMT/zpwiki.git', 'branch': 'master', 'status': ''}.get(args[1], '') return subprocess.CompletedProcess(args, 0, stdout=output, stderr='') monkeypatch.setattr(wiki_git.subprocess, 'run', fake_run) monkeypatch.setenv('EXPECTED_GITEA_BRANCH', 'master') rebuild.git_pull(repo) assert captured[-2][0] == ['git', 'fetch', '--no-tags', '--no-recurse-submodules', 'origin', 'refs/heads/master:refs/remotes/origin/master'] assert captured[-1][0] == ['git', 'merge', '--ff-only', 'refs/remotes/origin/master'] assert all(kw['cwd'] == repo and kw['timeout'] == 120 and kw['env']['GIT_TERMINAL_PROMPT'] == '0' for _, kw in captured) 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(wiki_git.subprocess, "run", timeout) with pytest.raises(RuntimeError, match="prekročila 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( wiki_git.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)