86 lines
4.9 KiB
Python
86 lines
4.9 KiB
Python
"""Failure ordering, repair retention and exact failed-receipt preservation."""
|
|
from pathlib import Path
|
|
import importlib.util
|
|
import json
|
|
from types import SimpleNamespace
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
spec = importlib.util.spec_from_file_location('fixture', HERE / 'test_recovery.py')
|
|
t = importlib.util.module_from_spec(spec); spec.loader.exec_module(t)
|
|
r = t.r; m = t.m; C = t.C
|
|
|
|
|
|
class EnvelopeTests(unittest.TestCase):
|
|
def execute(self, *, fail_json=False, additional_missing=False):
|
|
temp = tempfile.TemporaryDirectory(); self.addCleanup(temp.cleanup)
|
|
area = Path(temp.name)
|
|
raw = (HERE.parent / 'attempt-r2-preflight/rollout-receipt.json').read_bytes()
|
|
failed = area / 'native.json'; failed.write_bytes(raw)
|
|
state = SimpleNamespace(installed=False, rollout_called=False)
|
|
before_raw = t.raw_inventory(other_missing=additional_missing)
|
|
fake = SimpleNamespace(REPO=m.REPO, inventory_script=m.inventory_script, patch_for=lambda d,c: [])
|
|
class Runtime:
|
|
def __init__(self, cfg): self.receipt = {'events': []}
|
|
def target(self): pass
|
|
def deployment(self): return {}
|
|
def ready_pod(self, d): return C['pod']
|
|
def shell(self, pod, script, args=(), data=None, timeout=90):
|
|
if script == C['guard_command'][2]: return b''
|
|
if script == r.inventory_script(fake): return before_raw
|
|
if script == (r.ORIGINAL / 'rollback-probe.sh').read_text():
|
|
return json.dumps({k:True for k in ['clean_push','intermediate_secret_refused','quarantine_discarded','scanner_unavailable_refused','clean_recovery','temporary_probe_removed']}).encode()
|
|
if script == (HERE / 'install_fragment.sh').read_text():
|
|
state.installed=True
|
|
return b'{"installed":true,"existing_policy":true,"atomic_no_replace":true}'
|
|
raise AssertionError('unexpected-shell-operation')
|
|
def inventory(self, pod, allowed):
|
|
result = r.parse_before(t.raw_inventory(), fake, C)
|
|
result[r.TARGET][r.HOOK] = dict(mode='755', uid=1000, gid=1000, sha256=C['old_hook_sha256'])
|
|
return result
|
|
def apply(self, binary):
|
|
state.rollout_called=True
|
|
if fail_json: raise RuntimeError('simulated-json-rollout-failure')
|
|
self.receipt['events'] = [{'event':'activation-verified'}]
|
|
failed.write_text(json.dumps(self.receipt))
|
|
fake.Rollout=Runtime
|
|
def command(argv, **kw):
|
|
if 'configmap' in argv: return SimpleNamespace(returncode=0, stdout=(r.ORIGINAL/'before-hook').read_bytes(), stderr=b'')
|
|
if 'pod' in argv: return SimpleNamespace(returncode=0, stdout=C['pod_uid'].encode(), stderr=b'')
|
|
raise AssertionError('unexpected-command')
|
|
recovery = r.Recovery(fake, C, 'b'*64)
|
|
for name,value in [('FAILED',failed),('ARCHIVE',area/'archived.json'),('OUTPUT',area/'r3.json')]:
|
|
p=patch.object(r,name,value);p.start();self.addCleanup(p.stop)
|
|
with patch.object(r.subprocess,'run',side_effect=command), patch('builtins.print'):
|
|
try: recovery.run(Path('/tmp/sync-scanner-candidate/receive-secret-scan-candidate'))
|
|
except RuntimeError as error: state.error = str(error)
|
|
state.area=area;state.raw=raw;state.receipt=recovery.receipt
|
|
return state
|
|
|
|
def test_success_preserves_original_failed_bytes_and_binds_r3(self):
|
|
state=self.execute()
|
|
self.assertFalse(hasattr(state,'error'))
|
|
self.assertEqual((state.area/'archived.json').read_bytes(),state.raw)
|
|
self.assertTrue(state.installed and state.rollout_called)
|
|
self.assertEqual(state.receipt['events'][-1]['event'],'recovery-and-activation-verified')
|
|
self.assertEqual(json.loads((state.area/'native.json').read_bytes())['recovery_packet_sha256'],'b'*64)
|
|
|
|
def test_json_failure_keeps_coverage_repair_and_original_failed_receipt(self):
|
|
state=self.execute(fail_json=True)
|
|
self.assertEqual(state.error,'simulated-json-rollout-failure')
|
|
self.assertTrue(state.installed and state.rollout_called)
|
|
self.assertEqual((state.area/'archived.json').read_bytes(),state.raw)
|
|
self.assertIn('repair-verified',[x['event'] for x in state.receipt['events']])
|
|
self.assertNotIn('recovery-and-activation-verified',[x['event'] for x in state.receipt['events']])
|
|
|
|
def test_another_uncovered_repository_stops_before_install_or_archive(self):
|
|
state=self.execute(additional_missing=True)
|
|
self.assertEqual(state.error,'additional-uncovered-repository')
|
|
self.assertFalse(state.installed or state.rollout_called)
|
|
self.assertEqual((state.area/'native.json').read_bytes(),state.raw)
|
|
self.assertFalse((state.area/'archived.json').exists())
|
|
|
|
|
|
if __name__ == '__main__':unittest.main()
|