"""Regression : run_ollama doit installer le client 'ollama' meme en mode --ollama-host distant. Avant le fix, un retour anticipe sautait cette installation et 'ollama pull' echouait ensuite avec FileNotFoundError('ollama') sur une machine sans le binaire local.""" from pathlib import Path from unittest.mock import patch from installer.core.events import Output from installer.core.osdetect import parse_os_release from installer.core.pipeline import StepContext from installer.core.state import InstallState from installer.core.steps import ollama FEDORA = parse_os_release('ID=fedora\nPRETTY_NAME="Fedora 43"\n') class _FakeBroker: def __init__(self, answer=True): self.answer = answer self.prompts = [] def confirm(self, prompt, default=True): self.prompts.append(prompt) return self.answer def _ctx(host, broker): events = [] state = InstallState(distro=FEDORA, lyra_dir=Path("/tmp/lyra-test"), ollama_host=host) ctx = StepContext(state=state, emit=events.append, broker=broker, mcps=(), step_id="ollama") return ctx, events def test_remote_host_installe_le_client_si_absent(): broker = _FakeBroker(answer=True) ctx, _events = _ctx("192.168.122.1", broker) with patch("installer.core.steps.ollama.shutil.which", return_value=None), \ patch("subprocess.run") as mock_run: mock_run.return_value.returncode = 0 mock_run.return_value.stdout = "" mock_run.return_value.stderr = "" ollama.run_ollama(ctx) assert mock_run.called, "le script d'installation du client aurait du etre lance" assert broker.prompts, "une confirmation d'installation aurait du etre demandee" def test_remote_host_skip_install_si_deja_present(): broker = _FakeBroker(answer=True) ctx, events = _ctx("192.168.122.1", broker) with patch("installer.core.steps.ollama.shutil.which", return_value="/usr/bin/ollama"), \ patch("subprocess.run") as mock_run: ollama.run_ollama(ctx) assert not mock_run.called assert not broker.prompts outputs = [e.line for e in events if isinstance(e, Output)] assert any("Ollama distant" in line for line in outputs) def test_local_sans_host_installe_et_active_service(): broker = _FakeBroker(answer=True) ctx, _events = _ctx("", broker) with patch("installer.core.steps.ollama.shutil.which", return_value=None), \ patch("subprocess.run") as mock_run, \ patch("installer.core.steps.ollama.run") as mock_systemctl: mock_run.return_value.returncode = 0 mock_run.return_value.stdout = "" mock_run.return_value.stderr = "" ollama.run_ollama(ctx) assert mock_run.called assert mock_systemctl.called class _Proc: def __init__(self, code, out=""): self.returncode, self.stdout, self.stderr = code, out, "" def test_regression_script_officiel_reessaye_apres_echec_ponctuel(): # 2026-09-26, campagne VM : le script d'ollama.com a teste le .tar.zst, raté # ponctuellement, s'est rabattu sur le .tgz qui n'existe plus -> 404 -> INSTALLATION # ECHOUEE, alors que le passage precedent avait reussi. ctx, events = _ctx("192.168.122.1", _FakeBroker()) runs = [_Proc(1, "curl: (22) The requested URL returned error: 404"), _Proc(0, "Install complete")] with patch("installer.core.steps.ollama.shutil.which", return_value=None), \ patch("subprocess.run", side_effect=runs) as mock_run, \ patch("installer.core.steps.ollama.time.sleep") as mock_sleep: ollama.run_ollama(ctx) assert mock_run.call_count == 2 mock_sleep.assert_called_once_with(ollama.INSTALL_RETRY_PAUSE_S) outputs = [e.line for e in events if isinstance(e, Output)] assert any("nouvel essai" in line for line in outputs) def test_script_officiel_abandonne_apres_trois_echecs(): ctx, _events = _ctx("192.168.122.1", _FakeBroker()) with patch("installer.core.steps.ollama.shutil.which", return_value=None), \ patch("subprocess.run", return_value=_Proc(1, "404")) as mock_run, \ patch("installer.core.steps.ollama.time.sleep"): try: ollama.run_ollama(ctx) except RuntimeError as e: assert "3 essais" in str(e) else: raise AssertionError("l'echec definitif aurait du lever RuntimeError") assert mock_run.call_count == ollama.INSTALL_ATTEMPTS def test_ollama_host_explicite_remplace_un_export_perime(tmp_path, monkeypatch): """Regression : --ollama-host doit l'emporter. Avant le fix, un `export OLLAMA_HOST=` deja present dans ~/.bashrc etait conserve, et comme OLLAMA_HOST prime sur config.yaml au runtime, l'ancienne valeur gagnait.""" monkeypatch.setenv("HOME", str(tmp_path)) bashrc = tmp_path / ".bashrc" bashrc.write_text("alias a=b\nexport OLLAMA_HOST=ancien:11434\nalias c=d\n", encoding="utf-8") ctx, _events = _ctx("192.168.122.1", _FakeBroker()) with patch("installer.core.steps.ollama.shutil.which", return_value="/usr/bin/ollama"): ollama.run_ollama(ctx) ollama.run_ollama(ctx) # idempotent text = bashrc.read_text(encoding="utf-8") assert "ancien" not in text assert text.count("OLLAMA_HOST=") == 1 assert "export OLLAMA_HOST=192.168.122.1:11434" in text assert "alias a=b" in text and "alias c=d" in text