import io from pathlib import Path import sys import tempfile import unittest from unittest.mock import MagicMock,patch import zipfile sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) import scaleway_forgejo_archive as primary from capture_forgejo_archive import validate_archive class BackupTiers(unittest.TestCase): def archive(self,path,package=False): with zipfile.ZipFile(path,'w') as z: z.writestr('forgejo-db.sql','fixture');z.writestr('repos/owner/repo.git/HEAD','ref: refs/heads/main') z.writestr('data/lfs/unique','keep');z.writestr('data/attachments/unique','keep') if package: z.writestr('data/packages/blob','bulk') def test_full_keeps_packages_essentials_rejects_them(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'archive.zip';self.archive(p,True);validate_archive(p) with self.assertRaises(ValueError): validate_archive(p,'essentials') def test_essentials_keeps_unique_files(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'archive.zip';self.archive(p);validate_archive(p,'essentials') def client(self): c=MagicMock();c.create_multipart_upload.return_value={'UploadId':'fixture'} c.upload_part.return_value={'ETag':'part-etag'} c.complete_multipart_upload.return_value={'VersionId':'fixture-version'} return c def test_failed_upload_aborts_only_its_multipart(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'source';p.write_bytes(b'abc');c=self.client();c.upload_part.side_effect=RuntimeError('fixture') receipt={} with self.assertRaises(RuntimeError): primary.transfer(c,p,Path(d)/'out',receipt) c.abort_multipart_upload.assert_called_once();c.delete_object.assert_not_called() self.assertTrue(receipt['multipart_aborted']) def test_download_is_version_pinned_and_hash_verified(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'source';p.write_bytes(b'abc');c=self.client() c.get_object.return_value={'ContentLength':3,'Body':io.BytesIO(b'abc')} receipt={};primary.transfer(c,p,Path(d)/'out',receipt) self.assertEqual(c.get_object.call_args.kwargs['VersionId'],'fixture-version') self.assertTrue(receipt['download_hash_matches']);c.abort_multipart_upload.assert_not_called() def test_corrupt_download_does_not_pass(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'source';p.write_bytes(b'abc');c=self.client() c.get_object.return_value={'ContentLength':3,'Body':io.BytesIO(b'xyz')} with self.assertRaises(ValueError): primary.transfer(c,p,Path(d)/'out',{}) c.delete_object.assert_not_called()