"""缓存上传残块清理只触碰已确认结束的过期自有文件。""" import base64 import os from pathlib import Path import tempfile import unittest from gitea_cache_upload_cleanup import cleanup_upload_chunks, decode_owner class UploadCleanupTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.root = Path(self.temp.name).resolve() def file(self, name, run=12, age=10): directory = self.root / "tmp-upload" / f"run-{run}-v4" directory.mkdir(parents=True, exist_ok=True) path = directory / name path.write_bytes(b"block data") os.utime(path, (age, age)) return path def block(self, artifact=34, run=12, age=10, marker="genarrative-rust-cache-v1:backend-tests:1:00000000"): encoded = base64.urlsafe_b64encode(base64.b64encode(marker.encode())).decode() return self.file(f"block-{run}-{artifact}-10-{encoded}", run, age) def test_old_owned_blocks_and_associated_blocklist_are_deleted(self): block = self.block() blocklist = self.file("12-34-blocklist") self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 2) self.assertFalse(block.exists()) self.assertFalse(blocklist.exists()) self.assertTrue(block.parent.is_dir()) def test_other_runs_artifacts_and_unproven_blocklists_remain(self): own = self.block() other_run = self.block(run=13) other_artifact = self.block(artifact=35, marker="unrelated-upload") unproven = self.file("12-36-blocklist") self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 1) self.assertFalse(own.exists()) self.assertTrue(all(path.exists() for path in (other_run, other_artifact, unproven))) def test_young_or_unknown_member_protects_entire_artifact_group(self): own = self.block() young = self.file("12-34-blocklist", age=20) other = self.block(artifact=35) unknown = self.file("block-12-35-invalid-size-unknown") self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 0) self.assertTrue(all(path.exists() for path in (own, young, other, unknown))) def test_completely_unknown_file_protects_entire_run(self): block = self.block() self.file("unknown") self.assertEqual(cleanup_upload_chunks(self.root, {12}, 20), 0) self.assertTrue(block.exists()) @unittest.skipUnless(os.name == "posix", "symlink fixture requires POSIX") def test_symlinks_never_traversed_or_deleted(self): block = self.block() outside = self.root / "secret" outside.write_text("keep") (block.parent / "12-34-blocklist").symlink_to(outside) (self.root / "tmp-upload" / "run-13-v4").symlink_to(block.parent, target_is_directory=True) self.assertEqual(cleanup_upload_chunks(self.root, {12, 13}, 20), 0) self.assertTrue(block.exists()) self.assertEqual(outside.read_text(), "keep") linked = self.root / "root-link" linked.symlink_to(self.root, target_is_directory=True) with self.assertRaises(ValueError): cleanup_upload_chunks(linked, {12}, 20) def test_root_missing_or_relative_is_an_error(self): with self.assertRaises(FileNotFoundError): cleanup_upload_chunks(self.root / "missing", {12}, 20) with self.assertRaises(ValueError): cleanup_upload_chunks(Path("relative"), {12}, 20) def test_noncanonical_encoding_and_unknown_job_are_not_owned(self): for marker in ("genarrative-rust-cache-v1:other-job:1:00000000", "genarrative-rust-cache-v1:backend-tests:01:00000000"): encoded = base64.urlsafe_b64encode(base64.b64encode(marker.encode())).decode() self.assertIsNone(decode_owner(encoded)) self.assertIsNone(decode_owner("not-base64")) if __name__ == "__main__": unittest.main()