personal memory agent
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")