import io import json import pytest from informed_decision.intake_service import IntakeError, IntakeService, TokenReview, AUDIENCE from informed_decision.store import Store from test_intake import envelope USERNAME = 'system:serviceaccount:state-hub:state-hub' APPROVAL = '12345678-1234-1234-1234-123456789abc' def token_review(body): assert body['spec'] == {'token': 'projected-token', 'audiences': [AUDIENCE]} return {'status': {'authenticated': True, 'audiences': [AUDIENCE], 'user': {'username': USERNAME, 'uid': 'workload-uid'}}} @pytest.fixture def setup(tmp_path): private = tmp_path / 'private' private.mkdir(mode=0o700) store = Store(private / 'review.sqlite') submitters, groups = tmp_path / 'submitters.json', tmp_path / 'groups.json' submitters.write_text(json.dumps({USERNAME: {'submitter_id': 'state-hub', 'memo_classes': ['credential-lane'], 'review_groups': ['net-kingdom-admins'], 'daily_limit': 2}})) groups.write_text(json.dumps({'net-kingdom-admins': {'subject': 'keycape-human', 'display_name': 'Reviewer'}})) calls = [] def create(body): calls.append(body) assert body['human_control'] is True and body['required_count'] == 1 return {'id': APPROVAL, 'status': 'requested', 'binding': { **body['binding'], 'human_control': True, 'digest': 'sha256:' + 'a' * 64}} service = IntakeService(store, TokenReview(token_review), create, submitters_path=submitters, groups_path=groups) return service, store, calls, submitters def request(service, body, token='projected-token', path='/v1/memos'): raw = json.dumps(body).encode() environ = {'PATH_INFO': path, 'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'application/json', 'CONTENT_LENGTH': str(len(raw)), 'HTTP_AUTHORIZATION': 'Bearer ' + token, 'wsgi.input': io.BytesIO(raw)} response = [] result = b''.join(service(environ, lambda status, headers: response.append(status))) return int(response[0].split()[0]), json.loads(result) def test_pose_replay_and_restart(setup): service, store, calls, _ = setup status, receipt = request(service, envelope()) assert status == 201 assert receipt['approval_id'] == APPROVAL memo = store.memo(receipt['memo_id']) assert memo.binding.principal.id == 'keycape-human' assert memo.sealed is False assert store.memos_for('keycape-human') == [memo] service.store = Store(store.path) assert request(service, envelope()) == (200, receipt) assert len(calls) == 1 with store._connection() as db: assert db.execute('SELECT COUNT(*) FROM documents').fetchone()[0] == 1 assert db.execute('SELECT COUNT(*) FROM dispositions').fetchone()[0] == 0 def test_revocation_refuses_even_replay(setup): service, _, calls, config = setup assert request(service, envelope())[0] == 201 config.write_text('{}') assert request(service, envelope()) == (403, {'error': 'submitter_unknown'}) assert len(calls) == 1 def test_conflicts_precede_create(setup): service, _, calls, _ = setup assert request(service, envelope())[0] == 201 changed = envelope() changed['memo']['brief'] += ' Changed' assert request(service, changed) == (409, {'error': 'idempotency_conflict'}) changed = envelope() changed['idempotency_key'] = 'different-key' assert request(service, changed) == (409, {'error': 'memo_id_taken'}) assert len(calls) == 1 def test_uncertain_create_never_reposts_or_loads(setup): service, store, calls, _ = setup def lost(body): calls.append(body) raise OSError('sensitive upstream body') service.creator = lost assert request(service, envelope()) == (503, {'error': 'approval_create_unresolved'}) service.store = Store(store.path) assert request(service, envelope()) == (409, {'error': 'approval_create_unresolved'}) assert len(calls) == 1 with store._connection() as db: assert db.execute('SELECT COUNT(*) FROM memos').fetchone()[0] == 0 assert db.execute('SELECT COUNT(*) FROM documents').fetchone()[0] == 0 def test_created_receipt_recovers_without_create(setup): service, store, calls, _ = setup # Fail the load transaction, after the receipt has been persisted. with store._connection() as db: db.execute("CREATE TRIGGER fail_load BEFORE INSERT ON memos BEGIN SELECT RAISE(ABORT,'failure'); END") assert request(service, envelope())[0] == 503 with store._connection() as db: assert db.execute('SELECT COUNT(*) FROM documents').fetchone()[0] == 0 assert db.execute('SELECT state FROM intake_ledger').fetchone()[0] == 'created' db.execute('DROP TRIGGER fail_load') assert request(service, envelope())[0] == 201 assert len(calls) == 1 def test_budget_and_no_browser_routes(setup): service, _, calls, config = setup data = json.loads(config.read_text()) data[USERNAME]['daily_limit'] = 1 config.write_text(json.dumps(data)) assert request(service, envelope())[0] == 201 another = envelope() another['memo']['id'] = 'another-memo' another['idempotency_key'] = 'another-key' assert request(service, another) == (429, {'error': 'submitter_budget_exhausted'}) assert request(service, envelope(), path='/review')[0] == 404 assert len(calls) == 1 @pytest.mark.parametrize('status', [ {'authenticated': False}, {'authenticated': True, 'audiences': ['wrong'], 'user': {'username': USERNAME, 'uid': 'x'}}, {'authenticated': True, 'audiences': [AUDIENCE], 'user': {'username': 'human', 'uid': 'x'}}, {'authenticated': True, 'audiences': [AUDIENCE], 'user': {'username': USERNAME}}, ]) def test_token_review_fails_closed(status): with pytest.raises(IntakeError) as error: TokenReview(lambda body: {'status': status}).authenticate('hidden-token') assert error.value.code == 'workload_authentication_failed' def test_notifications_retry_and_disabled_writeback_stays_pending(setup): from informed_decision.intake_delivery import IntakeDeliveryWorker service, store, _, _ = setup assert request(service, envelope())[0] == 201 seen = [] now = [100.0] def notify(delivery_id, payload): seen.append((delivery_id, payload)) return len(seen) > 1 worker = IntakeDeliveryWorker(store, notify=notify, clock=lambda: now[0]) assert worker.run_once() == 0 now[0] += 10 assert worker.run_once() == 1 assert seen[0] == seen[1] assert 'brief' not in seen[0][1] and 'packet' not in seen[0][1] with store._connection() as db: assert db.execute("SELECT state FROM intake_deliveries WHERE kind='memo-posed'").fetchone()[0] == 'pending' def test_terminal_disposition_queues_writeback_atomically(setup): from informed_decision.disposition import Actor, ActorKind, Verb from informed_decision.provenance import Claim, Route from informed_decision.intake_delivery import IntakeDeliveryWorker service, store, _, _ = setup env = envelope() env['serves']['resolves'] = 'on-answer' status, receipt = request(service, env) assert status == 201 p = store.present(receipt['memo_id'], principal_sub='keycape-human', tenant=Claim('tenant:platform', Route.REGISTRATION), principal_type=Claim('human', Route.AUTHENTICATION)) actor = Actor('keycape-human', ActorKind.PERSON) store.acknowledge(p.id, actor, store.memo(receipt['memo_id']).required_ack_ids) d = store.record_disposition(p.id, actor, Verb.DECLINE, operation_id='decline-once', reasons=('wrong_scope',)) store.record_disposition(p.id, actor, Verb.DECLINE, operation_id='decline-once', reasons=('wrong_scope',)) calls = [] def transport(method, url, payload): calls.append(payload) return 200, {} worker = IntakeDeliveryWorker(store, hub_transport=transport, hub_base_url='http://hub', env={}) assert worker.run_once() == 0 and calls == [] worker.env = {'INFD_HUB_WRITEBACK': '1'} assert worker.run_once() == 1 assert d.id in calls[0]['rationale'] assert worker.run_once() == 0 def test_create_adapter_has_only_create_route(): from informed_decision.intake_approval import IntakeApprovalClient calls = [] class Transport: def request(self, method, url, *, headers, body): calls.append((method, url, json.loads(body))) assert headers['Authorization'] == 'Bearer workload-token' return 201, {'id': APPROVAL} client = IntakeApprovalClient('https://engine.example', lambda: 'workload-token', transport=Transport()) assert client({'binding': {'actor': 'informed-decision'}, 'human_control': True, 'required_count': 1}) == {'id': APPROVAL} assert calls[0][0:2] == ('POST', 'https://engine.example/v1/approvals') assert calls[0][2]['pdp_path'] is False assert 'validity' in calls[0][2]