personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_backup_rclone_acquire.py
5.2 kB 162 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6import hashlib 7import io 8import json 9import os 10import zipfile 11from pathlib import Path 12 13import pytest 14 15from solstone.think.backup import rclone_install 16 17 18def _asset(payload: bytes, filename: str) -> bytes: 19 buffer = io.BytesIO() 20 with zipfile.ZipFile(buffer, "w") as archive: 21 archive.writestr(f"{filename.removesuffix('.zip')}/rclone", payload) 22 return buffer.getvalue() 23 24 25def _sha256(data: bytes) -> str: 26 return hashlib.sha256(data).hexdigest() 27 28 29def _fake_binary() -> bytes: 30 return b"#!/bin/sh\necho 'rclone v1.74.4'\n" 31 32 33def test_ensure_rclone_installs_verified_asset( 34 monkeypatch: pytest.MonkeyPatch, 35 tmp_path: Path, 36): 37 filename = "rclone-v1.74.4-linux-amd64.zip" 38 archive = _asset(_fake_binary(), filename) 39 monkeypatch.setattr(rclone_install, "_platform_info", lambda: ("linux", "amd64")) 40 monkeypatch.setitem(rclone_install.RCLONE_ZIP_SHA256, filename, _sha256(archive)) 41 monkeypatch.setattr( 42 rclone_install, 43 "_download_with_retries", 44 lambda url, *, attempts, timeout: archive, 45 ) 46 47 binary_path = rclone_install.ensure_rclone(tool_dir=tmp_path) 48 49 assert binary_path == tmp_path / "rclone" 50 assert binary_path.read_bytes() == _fake_binary() 51 assert os.access(binary_path, os.X_OK) 52 sentinel = json.loads((tmp_path / ".install-complete").read_text()) 53 assert sentinel["tool"] == "rclone" 54 assert sentinel["version"] == "1.74.4" 55 assert sentinel["sha256"] == _sha256(_fake_binary()) 56 assert (tmp_path / "rclone.LICENSE").read_text() == ( 57 rclone_install.RCLONE_LICENSE_TEXT 58 ) 59 60 61def test_ensure_rclone_fails_closed_on_sha_mismatch( 62 monkeypatch: pytest.MonkeyPatch, 63 tmp_path: Path, 64): 65 filename = "rclone-v1.74.4-linux-amd64.zip" 66 archive = _asset(_fake_binary(), filename) 67 monkeypatch.setattr(rclone_install, "_platform_info", lambda: ("linux", "amd64")) 68 monkeypatch.setitem(rclone_install.RCLONE_ZIP_SHA256, filename, "0" * 64) 69 monkeypatch.setattr( 70 rclone_install, 71 "_download_with_retries", 72 lambda url, *, attempts, timeout: archive, 73 ) 74 75 with pytest.raises(RuntimeError, match="rclone asset SHA mismatch"): 76 rclone_install.ensure_rclone(tool_dir=tmp_path) 77 78 79def test_ensure_rclone_reuses_verified_install( 80 monkeypatch: pytest.MonkeyPatch, 81 tmp_path: Path, 82): 83 filename = "rclone-v1.74.4-linux-amd64.zip" 84 archive = _asset(_fake_binary(), filename) 85 monkeypatch.setattr(rclone_install, "_platform_info", lambda: ("linux", "amd64")) 86 monkeypatch.setitem(rclone_install.RCLONE_ZIP_SHA256, filename, _sha256(archive)) 87 calls = 0 88 89 def download(url: str, *, attempts: int, timeout: float) -> bytes: 90 nonlocal calls 91 calls += 1 92 return archive 93 94 monkeypatch.setattr(rclone_install, "_download_with_retries", download) 95 96 installed = rclone_install.ensure_rclone(tool_dir=tmp_path) 97 reused = rclone_install.ensure_rclone(tool_dir=tmp_path) 98 99 assert reused == installed 100 assert calls == 1 101 102 103def test_ensure_rclone_reinstalls_when_ready_binary_cannot_be_hashed( 104 monkeypatch: pytest.MonkeyPatch, 105 tmp_path: Path, 106): 107 filename = "rclone-v1.74.4-linux-amd64.zip" 108 archive = _asset(_fake_binary(), filename) 109 monkeypatch.setattr(rclone_install, "_platform_info", lambda: ("linux", "amd64")) 110 monkeypatch.setitem(rclone_install.RCLONE_ZIP_SHA256, filename, _sha256(archive)) 111 monkeypatch.setattr( 112 rclone_install, 113 "_download_with_retries", 114 lambda url, *, attempts, timeout: archive, 115 ) 116 installed = rclone_install.ensure_rclone(tool_dir=tmp_path) 117 real_file_sha256 = rclone_install._file_sha256 118 calls = 0 119 120 def flaky_file_sha256(path: Path) -> str: 121 nonlocal calls 122 calls += 1 123 if calls == 1: 124 raise OSError("transient read failure") 125 return real_file_sha256(path) 126 127 monkeypatch.setattr(rclone_install, "_file_sha256", flaky_file_sha256) 128 129 reinstalled = rclone_install.ensure_rclone(tool_dir=tmp_path) 130 131 assert reinstalled == installed 132 assert calls == 1 133 134 135@pytest.mark.parametrize( 136 ("os_name", "arch", "asset_os"), 137 [ 138 ("darwin", "amd64", "osx"), 139 ("darwin", "arm64", "osx"), 140 ("linux", "amd64", "linux"), 141 ("linux", "arm64", "linux"), 142 ], 143) 144def test_select_rclone_asset_platforms( 145 os_name: str, 146 arch: str, 147 asset_os: str, 148): 149 filename, url, sha256 = rclone_install.select_rclone_asset(os_name, arch) 150 151 assert filename == f"rclone-v1.74.4-{asset_os}-{arch}.zip" 152 assert url == ( 153 f"https://downloads.rclone.org/v1.74.4/rclone-v1.74.4-{asset_os}-{arch}.zip" 154 ) 155 assert sha256 == rclone_install.RCLONE_ZIP_SHA256[filename] 156 157 158def test_select_rclone_asset_rejects_unsupported_platform(): 159 with pytest.raises(RuntimeError, match="unsupported platform"): 160 rclone_install.select_rclone_asset("windows", "amd64") 161 with pytest.raises(RuntimeError, match="unsupported platform"): 162 rclone_install.select_rclone_asset("linux", "riscv64")