incomming mail processing
This commit is contained in:
@@ -15,6 +15,7 @@ from backend.app.main import app
|
||||
from backend.app.core.config import settings
|
||||
from backend.app.database import get_connection, hash_password
|
||||
from backend.app.services.email_service import get_smtp_settings
|
||||
from backend.app.services.inbound_email_service import get_imap_settings, poll_inbox_once
|
||||
from backend.app.services.login_throttle import clear_login_failures
|
||||
from backend.app.services.otp_service import current_code
|
||||
from backend.app.services.password_reset import create_reset_token
|
||||
@@ -147,6 +148,8 @@ def test_configuration_requires_authentication_and_admin_role():
|
||||
assert client.get('/api/admin/users', headers=login_headers('bob')).status_code == 403
|
||||
assert client.get('/api/admin/smtp').status_code == 401
|
||||
assert client.get('/api/admin/smtp', headers=login_headers('bob')).status_code == 403
|
||||
assert client.get('/api/admin/imap').status_code == 401
|
||||
assert client.get('/api/admin/imap', headers=login_headers('bob')).status_code == 403
|
||||
|
||||
|
||||
def test_admin_can_select_multiple_themes():
|
||||
@@ -234,6 +237,105 @@ def test_admin_reports_smtp_validation_errors():
|
||||
assert failed_validation.headers['X-Request-ID']
|
||||
|
||||
|
||||
def test_admin_can_save_imap_settings():
|
||||
headers = login_headers()
|
||||
original = get_imap_settings()
|
||||
with get_connection() as conn:
|
||||
original_row = conn.execute('SELECT value FROM app_settings WHERE name = ?', ('imap',)).fetchone()
|
||||
|
||||
response = client.put('/api/admin/imap', headers=headers, json={
|
||||
'imap_host': 'imap.example.com',
|
||||
'imap_port': 993,
|
||||
'imap_username': 'collector@example.com',
|
||||
'imap_password': 'secret-imap-password',
|
||||
'imap_mailbox': 'INBOX',
|
||||
'imap_use_ssl': True,
|
||||
'imap_poll_interval_seconds': 60,
|
||||
})
|
||||
assert response.status_code == 200
|
||||
assert response.json()['imap_host'] == 'imap.example.com'
|
||||
assert response.json()['password_configured'] is True
|
||||
assert response.json()['configured'] is True
|
||||
assert 'imap_password' not in response.json()
|
||||
|
||||
if original_row is None:
|
||||
with get_connection() as conn:
|
||||
conn.execute('DELETE FROM app_settings WHERE name = ?', ('imap',))
|
||||
conn.commit()
|
||||
else:
|
||||
with get_connection() as conn:
|
||||
conn.execute(
|
||||
'UPDATE app_settings SET value = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?',
|
||||
(original_row['value'], 'imap'),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def test_poll_inbox_once_reads_unseen_messages_and_marks_them_seen():
|
||||
class FakeImapClient:
|
||||
def __init__(self):
|
||||
self.actions = []
|
||||
self.raw_message = (
|
||||
b'From: Alice <alice@example.com>\n'
|
||||
b'To: capture@linklog.example\n'
|
||||
b'Subject: Polled inbox entry #AI\n'
|
||||
b'Message-ID: <poll-1@example.com>\n'
|
||||
b'Content-Type: text/plain; charset="utf-8"\n\n'
|
||||
b'Collected from IMAP.\nhttps://example.com/polled\n'
|
||||
)
|
||||
|
||||
def login(self, username, password):
|
||||
self.actions.append(('login', username, password))
|
||||
return 'OK', [b'Logged in']
|
||||
|
||||
def select(self, mailbox):
|
||||
self.actions.append(('select', mailbox))
|
||||
return 'OK', [b'1']
|
||||
|
||||
def uid(self, command, *args):
|
||||
self.actions.append(('uid', command, *args))
|
||||
if command == 'search':
|
||||
return 'OK', [b'200']
|
||||
if command == 'fetch':
|
||||
return 'OK', [(b'200 (BODY[] {42}', self.raw_message), b')']
|
||||
if command == 'store':
|
||||
return 'OK', [b'200 (FLAGS (\\Seen))']
|
||||
raise AssertionError(command)
|
||||
|
||||
def logout(self):
|
||||
self.actions.append(('logout',))
|
||||
return 'BYE', [b'Logged out']
|
||||
|
||||
summary = poll_inbox_once(
|
||||
{
|
||||
'imap_host': 'imap.example.com',
|
||||
'imap_port': 993,
|
||||
'imap_username': 'collector@example.com',
|
||||
'imap_password': 'secret',
|
||||
'imap_mailbox': 'INBOX',
|
||||
'imap_use_ssl': True,
|
||||
'imap_poll_interval_seconds': 60,
|
||||
},
|
||||
client_factory=lambda _: FakeImapClient(),
|
||||
)
|
||||
|
||||
assert summary == {'status': 'ok', 'processed': 1, 'stored': 1, 'ignored': 0}
|
||||
with get_connection() as conn:
|
||||
link = conn.execute(
|
||||
'SELECT title, url, comment, user_id FROM links WHERE url = ?',
|
||||
('https://example.com/polled',),
|
||||
).fetchone()
|
||||
inbound = conn.execute(
|
||||
'SELECT status FROM inbound_email_messages WHERE mailbox = ? AND uid = ?',
|
||||
('INBOX', '200'),
|
||||
).fetchone()
|
||||
|
||||
assert link['user_id'] == 'user-1'
|
||||
assert link['title'] == 'Polled inbox entry'
|
||||
assert link['comment'] == 'Collected from IMAP.'
|
||||
assert inbound['status'] == 'stored'
|
||||
|
||||
|
||||
def test_request_id_is_preserved_and_sensitive_error_text_is_not_returned():
|
||||
headers = login_headers()
|
||||
with get_connection() as conn:
|
||||
|
||||
Reference in New Issue
Block a user