import base64 from datetime import datetime, timezone import hashlib import json import httpx import pytest from activity_core.custodian_capture import receive, run_capture @pytest.fixture def capture_runtime(tmp_path, monkeypatch): state, snapshots = tmp_path / 'state', tmp_path / 'snapshots' state.mkdir(mode=0o700) (state / 'inputs').mkdir() snapshots.mkdir() policy = state / 'policy.json' policy.write_text(json.dumps({'schema': 'activity-core.custodian-input-policy.v1', 'repositories': ['example'], 'max_snapshot_bytes': 1024, 'max_observation_age_seconds': 7200, 'max_future_skew_seconds': 300, 'expected_hub_instance': 'railiance01'})) for name, value in {'STATE_ROOT': state, 'SNAPSHOT_WRITE_ROOT': snapshots, 'INPUT_POLICY': policy, 'PUBLISHER_URL': 'http://publisher.invalid'}.items(): monkeypatch.setenv('ACTIVITY_CORE_CUSTODIAN_' + name, str(value)) manifest = {'schema': 'repo-manager.fleet-input.v1', 'repositories': [{'slug': 'example', 'commit': 'a' * 40, 'files': [{'path': 'workplans/task.md', 'bytes': 4, 'sha256': hashlib.sha256(b'task').hexdigest()}]}]} receipts = [{'schema': 'repo-manager.freshness-receipt.v1', 'repo_slug': 'example', 'outcome': 'applied', 'finished_at': datetime.now(timezone.utc).isoformat(), 'hub': {'verified_primary': True, 'instance_role': 'primary', 'instance_label': 'railiance01'}, **{key: 'a' * 40 for key in ('forge_commit', 'expected_commit', 'derived_commit', 'projected_commit')}}] m, r = json.dumps(manifest), json.dumps(receipts) mh, rh = hashlib.sha256(m.encode()).hexdigest(), hashlib.sha256(r.encode()).hexdigest() bundle = {'schema': 'repo-manager.custodian-capture.v1', 'manifest': m, 'receipts': r, 'manifest_sha256': mh, 'receipts_sha256': rh, 'snapshot_id': hashlib.sha256((mh + rh).encode()).hexdigest(), 'files': {'example/workplans/task.md': base64.b64encode(b'task').decode()}} return state, snapshots, policy, bundle def test_receiver_retains_before_selecting_and_identical_retry(capture_runtime): state, snapshots, policy, bundle = capture_runtime first = receive(bundle, state, snapshots, policy) assert receive(bundle, state, snapshots, policy) == first descriptor = json.loads((state / 'inputs/current.json').read_text()) assert descriptor['snapshot_id'] == bundle['snapshot_id'] assert (snapshots / bundle['snapshot_id'] / 'example/workplans/task.md').read_text() == 'task' def test_failed_capture_does_not_advance_descriptor(capture_runtime): state, snapshots, policy, bundle = capture_runtime (state / 'inputs/current.json').write_text('unchanged') bundle['files']['example/workplans/task.md'] = base64.b64encode(b'bad').decode() with pytest.raises(ValueError): receive(bundle, state, snapshots, policy) assert (state / 'inputs/current.json').read_text() == 'unchanged' assert not (snapshots / bundle['snapshot_id']).exists() def test_fixed_endpoint_transport_sends_no_forge_credential(capture_runtime): bundle = capture_runtime[3] def handler(request): assert request.method == 'POST' assert request.url.path == '/ports/repositories/custodian-snapshot' assert 'authorization' not in request.headers return httpx.Response(200, json=bundle) assert run_capture({}, transport=httpx.MockTransport(handler))['status'] == 'completed' def test_transport_error_sanitized(capture_runtime): def handler(request): return httpx.Response(500, text='sensitive upstream details') with pytest.raises(ValueError, match='bounded owner snapshot reception failed') as error: run_capture({}, transport=httpx.MockTransport(handler)) assert 'sensitive' not in str(error.value)