| 1 | """Tests for setup-wizard auto-install of the default-on Printing Press sources |
| 2 | (arxiv, techmeme, trustpilot) -- lib/setup_wizard.py.""" |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import pytest |
| 7 | |
| 8 | from lib import setup_wizard as sw |
| 9 | |
| 10 | |
| 11 | @pytest.fixture |
| 12 | def no_off_path(monkeypatch): |
| 13 | # Default: no binary present on disk in known dirs. |
| 14 | monkeypatch.setattr(sw, "_pp_off_path_binary", lambda bin_name: None) |
| 15 | |
| 16 | |
| 17 | def test_already_installed_when_on_path(monkeypatch): |
| 18 | monkeypatch.setattr(sw.shutil, "which", lambda name: f"/usr/bin/{name}") |
| 19 | installed, action, stderr, off = sw._install_pp_cli("arxiv", "arxiv-pp-cli") |
| 20 | assert installed is True |
| 21 | assert action == "already_installed" |
| 22 | |
| 23 | |
| 24 | def test_installed_off_path(monkeypatch, no_off_path): |
| 25 | monkeypatch.setattr(sw.shutil, "which", lambda name: None) |
| 26 | monkeypatch.setattr(sw, "_pp_off_path_binary", lambda bin_name: "/home/u/.local/bin/" + bin_name) |
| 27 | installed, action, stderr, off = sw._install_pp_cli("techmeme", "techmeme-pp-cli") |
| 28 | assert installed is False |
| 29 | assert action == "installed_off_path" |
| 30 | assert off.endswith("techmeme-pp-cli") |
| 31 | |
| 32 | |
| 33 | def test_no_npx(monkeypatch, no_off_path): |
| 34 | monkeypatch.setattr(sw.shutil, "which", lambda name: None) # neither bin nor npx |
| 35 | installed, action, stderr, off = sw._install_pp_cli("trustpilot", "trustpilot-pp-cli") |
| 36 | assert installed is False |
| 37 | assert action == "no_npx" |
| 38 | |
| 39 | |
| 40 | def test_install_success(monkeypatch, no_off_path): |
| 41 | # npx present; binary absent before, resolves after the install. |
| 42 | calls = {"n": 0} |
| 43 | |
| 44 | def fake_which(name): |
| 45 | if name == "npx": |
| 46 | return "/usr/bin/npx" |
| 47 | if name == "arxiv-pp-cli": |
| 48 | # absent on first check, present after install |
| 49 | calls["n"] += 1 |
| 50 | return None if calls["n"] == 1 else "/home/u/.local/bin/arxiv-pp-cli" |
| 51 | return None |
| 52 | |
| 53 | monkeypatch.setattr(sw.shutil, "which", fake_which) |
| 54 | monkeypatch.setattr(sw.subprocess, "run", |
| 55 | lambda *a, **k: type("P", (), {"returncode": 0, "stdout": "", "stderr": ""})()) |
| 56 | installed, action, stderr, off = sw._install_pp_cli("arxiv", "arxiv-pp-cli") |
| 57 | assert installed is True |
| 58 | assert action == "installed" |
| 59 | |
| 60 | |
| 61 | def test_install_uses_resolved_windows_npx_path(monkeypatch, no_off_path): |
| 62 | """Regression for #904: subprocess.run must receive the resolved npx |
| 63 | path (e.g. a Windows PATHEXT-resolved npx.CMD), not the bare "npx" |
| 64 | string -- bare "npx" fails with WinError 2 on Windows.""" |
| 65 | calls = {"n": 0} |
| 66 | windows_npx = r"C:\Program Files\nodejs\npx.CMD" |
| 67 | |
| 68 | def fake_which(name): |
| 69 | if name == "npx": |
| 70 | return windows_npx |
| 71 | if name == "techmeme-pp-cli": |
| 72 | calls["n"] += 1 |
| 73 | return None if calls["n"] == 1 else r"C:\Users\me\.local\bin\techmeme-pp-cli" |
| 74 | return None |
| 75 | |
| 76 | run_calls = [] |
| 77 | |
| 78 | def fake_run(cmd, **kwargs): |
| 79 | run_calls.append(cmd) |
| 80 | return type("P", (), {"returncode": 0, "stdout": "", "stderr": ""})() |
| 81 | |
| 82 | monkeypatch.setattr(sw.shutil, "which", fake_which) |
| 83 | monkeypatch.setattr(sw.subprocess, "run", fake_run) |
| 84 | installed, action, stderr, off = sw._install_pp_cli("techmeme", "techmeme-pp-cli") |
| 85 | assert installed is True |
| 86 | assert action == "installed" |
| 87 | assert run_calls[0][0] == windows_npx |
| 88 | |
| 89 | |
| 90 | def test_install_failed_nonzero_rc(monkeypatch, no_off_path): |
| 91 | def fake_which(name): |
| 92 | return "/usr/bin/npx" if name == "npx" else None |
| 93 | |
| 94 | monkeypatch.setattr(sw.shutil, "which", fake_which) |
| 95 | monkeypatch.setattr(sw.subprocess, "run", |
| 96 | lambda *a, **k: type("P", (), {"returncode": 1, "stdout": "", "stderr": "boom"})()) |
| 97 | installed, action, stderr, off = sw._install_pp_cli("techmeme", "techmeme-pp-cli") |
| 98 | assert installed is False |
| 99 | assert action == "install_failed" |
| 100 | assert "boom" in stderr |
| 101 | |
| 102 | |
| 103 | def test_install_default_pp_sources_covers_default_on_pair(monkeypatch): |
| 104 | # Only the zero-auth default-on sources are auto-installed. Trustpilot is |
| 105 | # opt-in (INCLUDE_SOURCES=trustpilot) and intentionally excluded here. |
| 106 | monkeypatch.setattr(sw.shutil, "which", lambda name: f"/usr/bin/{name}") |
| 107 | out = sw.install_default_pp_sources() |
| 108 | assert set(out.keys()) == {"arxiv", "techmeme"} |
| 109 | assert "trustpilot" not in out |
| 110 | for entry in out.values(): |
| 111 | assert entry["action"] == "already_installed" |
| 112 | assert entry["installed"] is True |
| 113 | |
| 114 | |
| 115 | def test_install_is_idempotent(monkeypatch): |
| 116 | monkeypatch.setattr(sw.shutil, "which", lambda name: f"/usr/bin/{name}") |
| 117 | run_calls = [] |
| 118 | monkeypatch.setattr(sw.subprocess, "run", lambda *a, **k: run_calls.append(a)) |
| 119 | sw.install_default_pp_sources() |
| 120 | sw.install_default_pp_sources() |
| 121 | # All already on PATH -> npx install never invoked. |
| 122 | assert run_calls == [] |
| 123 |