mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:27:01 +01:00
Indexes the arch-wiki-docs dump that is already on disk and exposes it
to the knowledge-rag MCP server, so Arch questions can be asked by
symptom ("why doesn't my systemd user unit see DISPLAY under i3")
rather than by page title, with no network round-trip at query time.
The one Arch-specific MCP server in the wild fetches pages over the
network and bundles pacman operations that duplicate the local yay-mcp.
This does neither: it only searches, against a local corpus.
install.sh installs its own dependencies, wires the MCP config and
registers a pacman hook so the index follows package updates. The
generated index is machine-specific and regenerable, so it is
gitignored rather than committed.
_reindex_entry runs under the knowledge-rag interpreter, where
mcp_server is importable and this repo's packages are not. Resolving
that import with importlib.import_module states the deferral outright
instead of a function-level import statement that reads as an oversight,
and the backend failure path now re-raises as SystemExit, so the
exit-code translation is explicit rather than a swallowed exception.
Both replace lint suppressions; the package carries none.
pylint runs in its own venv, so beautifulsoup4 and markdownify are
declared there too — being in meta/requirements.txt does not make them
resolvable to the hook.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YQLwdquDCNmWtvgi3Lk72J
162 lines
5.4 KiB
Python
162 lines
5.4 KiB
Python
"""Tests for archwiki_rag.cli."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from python_pkg.archwiki_rag import cli
|
|
from python_pkg.archwiki_rag.sync import SyncResult
|
|
|
|
MOD = "python_pkg.archwiki_rag.cli"
|
|
|
|
|
|
class TestBuildParser:
|
|
def test_requires_a_subcommand(self) -> None:
|
|
with pytest.raises(SystemExit):
|
|
cli.build_parser().parse_args([])
|
|
|
|
def test_sync_defaults(self) -> None:
|
|
args = cli.build_parser().parse_args(["sync"])
|
|
assert args.command == "sync"
|
|
assert args.reindex is False
|
|
assert args.ignore_load is False
|
|
|
|
def test_sync_accepts_overrides(self) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", "/s", "--store", "/d", "--reindex", "--ignore-load"],
|
|
)
|
|
assert args.source == Path("/s")
|
|
assert args.store == Path("/d")
|
|
assert args.reindex is True
|
|
assert args.ignore_load is True
|
|
|
|
|
|
class TestRunSync:
|
|
def test_missing_source_reports_error(
|
|
self,
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path / "absent"), "--store", str(tmp_path)],
|
|
)
|
|
assert cli.run_sync(args) == 1
|
|
assert "arch-wiki-docs" in capsys.readouterr().out
|
|
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(3, 2, 1))
|
|
def test_reports_counts(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path), "--store", str(tmp_path)],
|
|
)
|
|
assert cli.run_sync(args) == 0
|
|
assert "converted 3, changed 2, skipped 1" in capsys.readouterr().out
|
|
|
|
@patch(f"{MOD}.reindex_mod.run_reindex")
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(3, 0, 0))
|
|
def test_skips_reindex_when_nothing_changed(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
mock_run: MagicMock,
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path), "--store", str(tmp_path), "--reindex"],
|
|
)
|
|
assert cli.run_sync(args) == 0
|
|
assert "nothing changed" in capsys.readouterr().out
|
|
mock_run.assert_not_called()
|
|
|
|
@patch(f"{MOD}.reindex_mod.run_reindex")
|
|
@patch(
|
|
f"{MOD}.reindex_mod.blocking_reason",
|
|
return_value="machine is busy (load 4.00/core)",
|
|
)
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(3, 3, 0))
|
|
def test_defers_when_blocked(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
mock_reason: MagicMock,
|
|
mock_run: MagicMock,
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path), "--store", str(tmp_path), "--reindex"],
|
|
)
|
|
assert cli.run_sync(args) == 0
|
|
assert "deferring reindex: machine is busy" in capsys.readouterr().out
|
|
mock_run.assert_not_called()
|
|
|
|
@patch(f"{MOD}.reindex_mod.run_reindex", return_value=0)
|
|
@patch(f"{MOD}.reindex_mod.blocking_reason", return_value=None)
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(3, 3, 0))
|
|
def test_runs_reindex_when_clear(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
mock_reason: MagicMock,
|
|
mock_run: MagicMock,
|
|
tmp_path: Path,
|
|
capsys: pytest.CaptureFixture[str],
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path), "--store", str(tmp_path), "--reindex"],
|
|
)
|
|
assert cli.run_sync(args) == 0
|
|
assert "reindexing..." in capsys.readouterr().out
|
|
mock_run.assert_called_once_with(Path(str(tmp_path)))
|
|
|
|
@patch(f"{MOD}.reindex_mod.run_reindex", return_value=127)
|
|
@patch(f"{MOD}.reindex_mod.blocking_reason", return_value=None)
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(3, 3, 0))
|
|
def test_propagates_reindex_failure(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
mock_reason: MagicMock,
|
|
mock_run: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
["sync", "--source", str(tmp_path), "--store", str(tmp_path), "--reindex"],
|
|
)
|
|
assert cli.run_sync(args) == 127
|
|
|
|
@patch(f"{MOD}.reindex_mod.blocking_reason", return_value=None)
|
|
@patch(f"{MOD}.reindex_mod.run_reindex", return_value=0)
|
|
@patch(f"{MOD}.sync_pages", return_value=SyncResult(1, 1, 0))
|
|
def test_ignore_load_is_forwarded(
|
|
self,
|
|
mock_sync: MagicMock,
|
|
mock_run: MagicMock,
|
|
mock_reason: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
args = cli.build_parser().parse_args(
|
|
[
|
|
"sync",
|
|
"--source",
|
|
str(tmp_path),
|
|
"--store",
|
|
str(tmp_path),
|
|
"--reindex",
|
|
"--ignore-load",
|
|
],
|
|
)
|
|
cli.run_sync(args)
|
|
assert mock_reason.call_args.kwargs["ignore_load"] is True
|
|
|
|
|
|
class TestMain:
|
|
@patch(f"{MOD}.run_sync", return_value=0)
|
|
def test_dispatches_to_run_sync(self, mock_run_sync: MagicMock) -> None:
|
|
assert cli.main(["sync"]) == 0
|
|
mock_run_sync.assert_called_once()
|