railiance-platform/tests/test_recovery_evidence.py
codex 31386c8e63
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Validate OpenBao snapshot evidence and record assurance closure gates
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a06ecb-456a-71c2-b41e-0755d336e883
2026-09-06 15:24:58 +02:00

72 lines
3.8 KiB
Python

from datetime import datetime, timezone, timedelta
import hashlib
import json
from pathlib import Path
import sys
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
from recovery_evidence import recovery_signals, ROOT
from service_assurance import evaluate
NOW = datetime(2026, 9, 6, 12, tzinfo=timezone.utc)
def test_real_receipts_preserve_completion_and_eventually_expire():
signals = recovery_signals(NOW)
assert all(s['result'] == 'pass' for s in signals.values())
assert signals['apps-pg.restore']['observed_at'] == '2026-09-05T22:30:45.208512+00:00'
contract = {'cluster_uid': 'test', 'capture_max_age_seconds': 900,
'signals': {key: {'owner': 'platform', 'max_age_seconds': 2592000} for key in signals}}
later = NOW + timedelta(days=31)
result = evaluate(contract, {'schema': 'railiance-platform.observation.v1',
'cluster_uid': 'test', 'captured_at': later.isoformat(), 'signals': recovery_signals(later)}, later)
assert all(s['state'] == 'stale' for s in result['signals'].values())
@pytest.mark.parametrize('change', ['hash', 'cleanup', 'provider', 'missing_time', 'future', 'naive'])
def test_invalid_receipt_is_unavailable(tmp_path, change):
index = json.loads((ROOT / 'assurance/recovery-evidence.json').read_text())
index['receipts'] = index['receipts'][:1]
entry = index['receipts'][0]
receipt = json.loads((ROOT / entry['path']).read_text())
if change == 'cleanup': receipt['cleanup'] = False
if change == 'provider': receipt['primary_destination'] = 's3://other/'
if change == 'missing_time': del receipt['finished_at']
if change == 'future': receipt['finished_at'] = '2027-01-01T00:00:00Z'
if change == 'naive': receipt['finished_at'] = '2026-09-05T23:00:00'
path = tmp_path / entry['path']
path.parent.mkdir(parents=True)
path.write_text(json.dumps(receipt))
if change != 'hash': entry['sha256'] = hashlib.sha256(path.read_bytes()).hexdigest()
(tmp_path / 'assurance').mkdir()
(tmp_path / 'assurance/recovery-evidence.json').write_text(json.dumps(index))
assert recovery_signals(NOW, tmp_path)['apps-pg.restore']['result'] == 'unavailable'
def test_snapshot_is_stale_and_never_substitutes_for_restore():
signals = recovery_signals(NOW)
assert signals['openbao.snapshot']['observed_at'] == '2026-08-22T22:29:21Z'
assert 'openbao.restore' not in signals
contract = {'cluster_uid': 'test', 'capture_max_age_seconds': 900,
'signals': {'openbao.snapshot': {'owner': 'platform', 'max_age_seconds': 129600}}}
result = evaluate(contract, {'schema': 'railiance-platform.observation.v1',
'cluster_uid': 'test', 'captured_at': NOW.isoformat(),
'signals': {'openbao.snapshot': signals['openbao.snapshot']}}, NOW)
assert result['signals']['openbao.snapshot']['state'] == 'stale'
@pytest.mark.parametrize('key,value', [('encrypted_copy_off_host', False),
('hash_verified', False), ('cluster_id', 'other'), ('snapshot_sha256', 'invalid'),
('created_at', '2027-01-01T00:00:00Z')])
def test_snapshot_rejects_unverified_or_wrong_scope(tmp_path, key, value):
index = json.loads((ROOT / 'assurance/recovery-evidence.json').read_text())
entry = next(e for e in index['receipts'] if e['signal'] == 'openbao.snapshot')
receipt = json.loads((ROOT / entry['path']).read_text())
receipt[key] = value
path = tmp_path / entry['path']; path.parent.mkdir(parents=True)
path.write_text(json.dumps(receipt))
entry['sha256'] = hashlib.sha256(path.read_bytes()).hexdigest()
(tmp_path / 'assurance').mkdir()
index['receipts'] = [entry]
(tmp_path / 'assurance/recovery-evidence.json').write_text(json.dumps(index))
assert recovery_signals(NOW, tmp_path)['openbao.snapshot']['result'] == 'unavailable'