personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3from __future__ import annotations
4
5import hashlib
6import subprocess
7import sys
8from pathlib import Path
9
10import pytest
11
12import scripts.check_native_sol_python_manifest as manifest
13
14
15def _git(repo: Path, *args: str) -> str:
16 result = subprocess.run(
17 ["git", *args],
18 cwd=repo,
19 check=True,
20 capture_output=True,
21 text=True,
22 )
23 return result.stdout.strip()
24
25
26def _fixture_tree(repo: Path, files: dict[str, str]) -> tuple[str, dict[str, str]]:
27 repo.mkdir()
28 _git(repo, "init")
29 blobs: dict[str, str] = {}
30 for rel_path, text in files.items():
31 path = repo / rel_path
32 path.parent.mkdir(parents=True, exist_ok=True)
33 path.write_text(text, encoding="utf-8")
34 blobs[rel_path] = _git(repo, "hash-object", str(path))
35 _git(repo, "add", *sorted(files))
36 tree = _git(repo, "write-tree")
37 return tree, blobs
38
39
40def _digest(blobs: dict[str, str]) -> str:
41 return hashlib.sha256(
42 "".join(f"{path}\t{blob}\n" for path, blob in sorted(blobs.items())).encode(
43 "utf-8"
44 )
45 ).hexdigest()
46
47
48def _patch_manifest(
49 monkeypatch: pytest.MonkeyPatch,
50 *,
51 repo: Path,
52 tree: str,
53 expected_blobs: dict[str, str],
54) -> None:
55 monkeypatch.setattr(manifest, "REPO_ROOT", repo)
56 monkeypatch.setattr(manifest, "PRE_CUTOVER_COMMIT", tree)
57 monkeypatch.setattr(manifest, "EXPECTED_BLOBS", dict(expected_blobs))
58 monkeypatch.setattr(manifest, "EXPECTED_SHA256", _digest(expected_blobs))
59
60
61def _run_manifest_main() -> int:
62 try:
63 return manifest.main()
64 except Exception as error:
65 print(error, file=sys.stderr)
66 return 1
67
68
69def test_manifest_main_fails_visibly_when_baseline_path_is_missing(
70 tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
71) -> None:
72 repo = tmp_path / "repo"
73 tree, blobs = _fixture_tree(repo, {"present.py": "print('present')\n"})
74 expected_blobs = {**blobs, "missing.py": "0" * 40}
75 _patch_manifest(
76 monkeypatch,
77 repo=repo,
78 tree=tree,
79 expected_blobs=expected_blobs,
80 )
81
82 exit_code = _run_manifest_main()
83
84 captured = capsys.readouterr()
85 assert exit_code == 1
86 assert "git ls-tree returned 1 entries; missing: missing.py" in captured.err
87
88
89def test_manifest_main_fails_visibly_when_expected_blob_drifts(
90 tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
91) -> None:
92 repo = tmp_path / "repo"
93 tree, blobs = _fixture_tree(repo, {"present.py": "print('present')\n"})
94 expected_blobs = dict(blobs)
95 expected_blobs["present.py"] = "0" * 40
96 _patch_manifest(
97 monkeypatch,
98 repo=repo,
99 tree=tree,
100 expected_blobs=expected_blobs,
101 )
102
103 exit_code = _run_manifest_main()
104
105 captured = capsys.readouterr()
106 assert exit_code == 1
107 assert "native sol Python manifest blob drifted" in captured.err
108 assert (
109 "present.py: expected 0000000000000000000000000000000000000000" in captured.err
110 )