The bridge forward was down 2026-10-05 20:30 to about 00:20 and dropped the first request after reconnecting; 13 timer runs reported credential_unavailable without health showing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 7780@bnt-lap001 Assistant-Session: 622cd705-cb31-4322-ba08-dd42e44fa583
110 lines
4.5 KiB
Python
110 lines
4.5 KiB
Python
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from inbound_library import intake, vault
|
|
from inbound_library.__main__ import run_due
|
|
|
|
SECRET = 's3cret-imap-password'
|
|
|
|
|
|
class FakeBao:
|
|
def __init__(self, deny_read=False, down=False):
|
|
self.calls, self.deny_read, self.down = [], deny_read, down
|
|
|
|
def __call__(self, addr, method, path, token=None, body=None):
|
|
self.calls.append((method, path, token))
|
|
if self.down:
|
|
raise vault.CredentialUnavailable(f'OpenBao unreachable at {addr}: URLError')
|
|
if path == 'auth/approle/login':
|
|
assert body == {'role_id': 'role-1', 'secret_id': 'secret-1'}
|
|
return {'auth': {'client_token': 'tok-1'}}
|
|
if path == 'platform/data/workloads/inbound-library/imap':
|
|
if self.deny_read:
|
|
raise vault.CredentialUnavailable('OpenBao GET platform/… returned 403')
|
|
return {'data': {'data': {'IMAP_USERNAME': 'library@coulomb.social',
|
|
'IMAP_PASSWORD': SECRET, 'IMAP_HOST': 'imap.ionos.de',
|
|
'IMAP_PORT': '993', 'IMAP_FOLDER': 'INBOX'}}}
|
|
if path == 'auth/token/revoke-self':
|
|
return {}
|
|
raise AssertionError(path)
|
|
|
|
|
|
class Vault(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.approle = Path(self.tmp.name) / 'approle'
|
|
self.approle.mkdir()
|
|
(self.approle / 'role_id').write_text('role-1\n')
|
|
(self.approle / 'secret_id').write_text('secret-1\n')
|
|
self.spec = {'addr': 'http://bao', 'approle_dir': str(self.approle),
|
|
'kv_path': 'platform/workloads/inbound-library/imap'}
|
|
|
|
def tearDown(self):
|
|
self.tmp.cleanup()
|
|
|
|
def test_login_read_and_revoke(self):
|
|
bao = FakeBao()
|
|
fields = vault.fetch(self.spec, call=bao)
|
|
self.assertEqual(fields['IMAP_PASSWORD'], SECRET)
|
|
self.assertEqual([c[1] for c in bao.calls], [
|
|
'auth/approle/login', 'platform/data/workloads/inbound-library/imap',
|
|
'auth/token/revoke-self'])
|
|
|
|
def test_dropped_first_request_is_retried_once(self):
|
|
attempts = []
|
|
|
|
def flaky(addr, method, path, token, body, timeout):
|
|
attempts.append(path)
|
|
if len(attempts) == 1:
|
|
raise vault._Unreachable('OpenBao unreachable at x: URLError')
|
|
return {'ok': True}
|
|
original = vault._once
|
|
vault._once = flaky
|
|
try:
|
|
self.assertEqual(vault._call('http://bao', 'GET', 'sys/health'), {'ok': True})
|
|
attempts.clear()
|
|
vault._once = lambda *a: (_ for _ in ()).throw(vault._Unreachable('down'))
|
|
with self.assertRaises(vault.CredentialUnavailable):
|
|
vault._call('http://bao', 'GET', 'sys/health')
|
|
finally:
|
|
vault._once = original
|
|
|
|
def test_denied_read_still_revokes_and_hides_secret(self):
|
|
bao = FakeBao(deny_read=True)
|
|
with self.assertRaises(vault.CredentialUnavailable) as caught:
|
|
vault.fetch(self.spec, call=bao)
|
|
self.assertEqual(bao.calls[-1][1], 'auth/token/revoke-self')
|
|
self.assertNotIn('secret-1', str(caught.exception))
|
|
|
|
def test_missing_approle_files(self):
|
|
(self.approle / 'secret_id').unlink()
|
|
with self.assertRaises(vault.CredentialUnavailable):
|
|
vault.fetch(self.spec, call=FakeBao())
|
|
|
|
def test_mailbox_built_from_vault_fields(self):
|
|
box = intake.mailbox_from_config(
|
|
{'credential': self.spec}, environ={},
|
|
fetch=lambda spec: vault.fetch(spec, call=FakeBao()))
|
|
self.assertEqual((box.user, box.host, box.port, box.folder),
|
|
('library@coulomb.social', 'imap.ionos.de', 993, 'INBOX'))
|
|
|
|
def test_run_due_reports_unreachable_bao_without_secret_and_still_matches(self):
|
|
root = Path(self.tmp.name) / 'lib'
|
|
(root / 'registry').mkdir(parents=True)
|
|
(root / 'registry' / 'topics.json').write_text(json.dumps({'version': 1, 'topics': {}}))
|
|
original = vault.fetch
|
|
vault.fetch = lambda spec: original(spec, call=FakeBao(down=True))
|
|
try:
|
|
report = run_due(root, {'credential': self.spec}, post=lambda *a: None)
|
|
finally:
|
|
vault.fetch = original
|
|
self.assertFalse(report['ok'])
|
|
self.assertEqual(report['intake']['status'], 'credential_unavailable')
|
|
self.assertIn('notices', report)
|
|
self.assertNotIn(SECRET, json.dumps(report))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|