49 lines
2.7 KiB
Python
49 lines
2.7 KiB
Python
|
|
"""Opt in with RTEL_AUDIT_CORE_SOURCE; uses the real receiver, synthetic custody."""
|
||
|
|
import io
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
import sys
|
||
|
|
import tempfile
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
|
||
|
|
from alert_ack import Actor, Store
|
||
|
|
|
||
|
|
|
||
|
|
@unittest.skipUnless(os.environ.get('RTEL_AUDIT_CORE_SOURCE'), 'set RTEL_AUDIT_CORE_SOURCE for real receiver check')
|
||
|
|
class NativeAuditTests(unittest.TestCase):
|
||
|
|
def test_real_receiver_accepts_and_deduplicates_after_lost_reply(self):
|
||
|
|
sys.path.insert(0, os.environ['RTEL_AUDIT_CORE_SOURCE'])
|
||
|
|
from audit_core.ingestion import IngestionApplication
|
||
|
|
from audit_core.senders import SenderIdentity, SenderRegistry
|
||
|
|
from audit_core.sqlite_backend import SQLiteAuditBackend
|
||
|
|
with tempfile.TemporaryDirectory() as tmp:
|
||
|
|
store = Store(Path(tmp) / 'ack.db')
|
||
|
|
identity = store.receive({'version': '4', 'receiver': 'railiance-admin-email', 'alerts': [
|
||
|
|
{'status': 'firing', 'fingerprint': '0123456789abcdef', 'startsAt': '2026-09-28T00:00:00Z',
|
||
|
|
'labels': {'alertname': 'ControlledFailure', 'owner': 'railiance-telemetry'}}]}, 1790553600)[0]
|
||
|
|
store.acknowledge(identity, Actor('https://issuer.example', 'fixture-human', 'tenant:platform',
|
||
|
|
('railiance-admin',), 1790554500, 'c' * 32), 'fixture-decision', 1790553600)
|
||
|
|
sender = SenderIdentity(name='railiance-telemetry', tokens=('fixture-only',),
|
||
|
|
sources=frozenset({'railiance-telemetry'}), tenants=frozenset({'tenant:platform'}),
|
||
|
|
evidence_kind='load-bearing', may_read=False)
|
||
|
|
app = IngestionApplication(SQLiteAuditBackend(str(Path(tmp) / 'audit.db')), SenderRegistry([sender]))
|
||
|
|
calls = []
|
||
|
|
def send(event):
|
||
|
|
raw = json.dumps(event).encode()
|
||
|
|
env = {'REQUEST_METHOD': 'POST', 'PATH_INFO': '/v1/events',
|
||
|
|
'CONTENT_LENGTH': str(len(raw)), 'wsgi.input': io.BytesIO(raw),
|
||
|
|
'HTTP_AUTHORIZATION': 'Bearer fixture-only', 'HTTP_IDEMPOTENCY_KEY': event['id']}
|
||
|
|
response = {}
|
||
|
|
body = b''.join(app(env, lambda status, headers: response.update(status=int(status[:3]))))
|
||
|
|
calls.append(response['status'])
|
||
|
|
if len(calls) == 1:
|
||
|
|
raise TimeoutError('simulated lost response')
|
||
|
|
return response['status'], json.loads(body)
|
||
|
|
store.drain(send)
|
||
|
|
store = Store(Path(tmp) / 'ack.db')
|
||
|
|
store.drain(send)
|
||
|
|
self.assertEqual(calls, [202, 200])
|
||
|
|
self.assertEqual(store.get(identity)['audit_status'], 'delivered')
|