import copy import json import os import subprocess import sys import tempfile import unittest from pathlib import Path from harness.tests._support.paths import ROOT from harness.tests._support.spec import MODPATH, mod, publication_fixture, result, verify_fixture class CacheTests(unittest.TestCase): def test_cache_validation_and_rejects_symlink(self): with tempfile.TemporaryDirectory(dir="/opt/data/cache/scratch") as td: root = Path(td); ops = root / "harness" / "operations"; ops.mkdir(parents=True) contract = ops / "repository-cache.md"; contract.write_text("cache", encoding="utf-8") rs = root / "repositories"; owner = rs / "acme"; repo = owner / "repo" (repo / "locks").mkdir(parents=True) (repo / "repository.git").mkdir() for p in (rs, owner, repo, repo / "locks"): os.chmod(p, 0o700) info = mod.validate_cache_paths(contract, "acme", "repo", 3) self.assertTrue(info["repository_git"].endswith("repository.git")) (repo / "locks" / "issue-3.lock").symlink_to(contract) with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(contract, "acme", "repo", 3) def test_cache_enforces_root_boundary_uid_and_dangling_locks(self): with tempfile.TemporaryDirectory(dir="/opt/data/cache/scratch") as td: root = Path(td); ops = root / "harness" / "operations"; ops.mkdir(parents=True) contract = ops / "repository-cache.md"; contract.write_text("cache", encoding="utf-8") rs = root / "repositories"; owner = rs / "acme"; repo = owner / "repo" (repo / "locks").mkdir(parents=True); (repo / "repository.git").mkdir() for path in (rs, owner, repo, repo / "locks"): path.chmod(0o700) os.chmod(root, 0o777) with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(contract, "acme", "repo", 3) os.chmod(root, 0o700) with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(contract, "acme", "repo", 3, uid=os.getuid() + 10000) (repo / "locks" / "issue-3.lock").symlink_to(root / "missing") with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(contract, "acme", "repo", 3) (repo / "locks" / "issue-3.lock").unlink() alias = root / "alias"; alias.symlink_to(ops.parent, target_is_directory=True) with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(alias / "operations" / "repository-cache.md", "acme", "repo", 3) def test_cache_group_membership_includes_other_primary_accounts(self): from types import SimpleNamespace from unittest.mock import patch role = SimpleNamespace(pw_name="role", pw_uid=os.getuid(), pw_gid=111) other = SimpleNamespace(pw_name="other", pw_uid=os.getuid() + 1, pw_gid=111) group = SimpleNamespace(gr_gid=111, gr_mem=["role"]) with tempfile.TemporaryDirectory(dir="/opt/data/cache/scratch") as td: root = Path(td); ops = root / "harness" / "operations"; ops.mkdir(parents=True) contract = ops / "repository-cache.md"; contract.write_text("cache") rs = root / "repositories"; repo = rs / "acme" / "repo" (repo / "locks").mkdir(parents=True); (repo / "repository.git").mkdir() for path in (rs / "acme", repo, repo / "locks"): path.chmod(0o700) rs.chmod(0o770) role.pw_gid = other.pw_gid = group.gr_gid = rs.stat().st_gid with patch.object(mod.grp, "getgrgid", return_value=group), \ patch.object(mod.pwd, "getpwuid", return_value=role), \ patch.object(mod.pwd, "getpwall", return_value=[role, other]): with self.assertRaises(mod.ValidationError): mod.validate_cache_paths(contract, "acme", "repo", 3) if __name__ == "__main__": unittest.main()