railiance-platform/tests/test_backup_tiers.py
codex 11322a5256
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Bind essentials recovery to explicit manifest and verified offsite provenance
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
2026-09-06 09:25:26 +02:00

75 lines
4.3 KiB
Python

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()
class EssentialsSealing(unittest.TestCase):
def test_manifest_and_unique_data_survive_bulk_exclusion(self):
from forgejo_essentials_profile import seal,MANIFEST
import json
with tempfile.TemporaryDirectory() as d:
path=Path(d)/'archive.zip'
with zipfile.ZipFile(path,'w') as z:
for name in ['forgejo-db.sql','repos/a.git/HEAD','data/lfs/original','data/attachments/original','data/actions_artifacts/original','data/packages/bulk','data/actions_log/build']:
z.writestr(name,b'fixture')
seal(path)
with zipfile.ZipFile(path) as z:
self.assertEqual(z.read('data/lfs/original'),b'fixture')
self.assertEqual(z.read('data/attachments/original'),b'fixture')
self.assertEqual(z.read('data/actions_artifacts/original'),b'fixture')
self.assertNotIn('data/packages/bulk',z.namelist())
self.assertNotIn('data/actions_log/build',z.namelist())
self.assertFalse(json.loads(z.read(MANIFEST))['package_registry_available'])
def test_over_budget_preserves_original_candidate(self):
import forgejo_essentials_profile as profile
with tempfile.TemporaryDirectory() as d:
path=Path(d)/'archive.zip'
with zipfile.ZipFile(path,'w') as z: z.writestr('repos/a.git/HEAD',b'fixture')
before=path.read_bytes()
with patch.object(profile,'BUDGET',1), self.assertRaises(ValueError): profile.seal(path)
self.assertEqual(path.read_bytes(),before)