* Add comprehensive tests for IMAP email restoration functionality - Implement tests for loading labels manifest, including both old and new formats. - Add tests for extracting flags from the manifest. - Create tests for parsing .eml files, including edge cases. - Develop tests for retrieving .eml files from a directory. - Validate configuration settings and handle missing credentials. - Integrate tests for restoring emails from a backup, including Gmail mode and deletion of orphaned emails. - Test progress caching and backup folder discovery. - Ensure robust handling of email labels and flags during restoration. - Verify that the restore process correctly interacts with a mock IMAP server. * Add comprehensive tests for IMAP utilities and compression support - Introduced tests for `imap_common.py` covering environment variable verification, IMAP connection handling, folder name normalization, MIME header decoding, message details extraction, duplicate detection, filename sanitization, and trash folder detection. - Added tests for `imap_compress.py` to validate the functionality of the `_CompressedSocket` wrapper, ensuring data compression and decompression works as expected. - Implemented integration tests to verify that compression is enabled during IMAP connection setup.
342 lines
14 KiB
Python
342 lines
14 KiB
Python
"""
|
|
Tests for imap_session.py
|
|
|
|
Tests cover:
|
|
- ensure_connection() behavior with and without OAuth2
|
|
- ensure_folder_session() connection change detection
|
|
- Folder selection and re-selection logic
|
|
- Error handling for connection and folder failures
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../src")))
|
|
|
|
from core import imap_session
|
|
|
|
|
|
class TestBuildImapConf:
|
|
"""Tests for build_imap_conf function."""
|
|
|
|
def test_password_auth_returns_correct_dict(self):
|
|
"""Test builds correct config for password authentication."""
|
|
conf = imap_session.build_imap_conf("imap.example.com", "user@example.com", "pass123")
|
|
|
|
assert conf["host"] == "imap.example.com"
|
|
assert conf["user"] == "user@example.com"
|
|
assert conf["password"] == "pass123"
|
|
assert conf["oauth2_token"] is None
|
|
assert conf["oauth2"] is None
|
|
|
|
def test_oauth2_auth_acquires_token(self):
|
|
"""Test acquires token and builds oauth2 config when client_id is provided."""
|
|
with patch.object(
|
|
imap_session.imap_oauth2, "acquire_token", return_value=("my_token", "microsoft")
|
|
) as mock_acquire:
|
|
conf = imap_session.build_imap_conf(
|
|
"outlook.office365.com",
|
|
"user@example.com",
|
|
"pass",
|
|
client_id="cid",
|
|
client_secret="csecret",
|
|
label="source",
|
|
)
|
|
|
|
mock_acquire.assert_called_once_with("outlook.office365.com", "cid", "user@example.com", "csecret", "source")
|
|
assert conf["host"] == "outlook.office365.com"
|
|
assert conf["user"] == "user@example.com"
|
|
assert conf["password"] == "pass"
|
|
assert conf["oauth2_token"] == "my_token"
|
|
assert conf["oauth2"] == {
|
|
"provider": "microsoft",
|
|
"client_id": "cid",
|
|
"email": "user@example.com",
|
|
"client_secret": "csecret",
|
|
}
|
|
|
|
def test_no_client_id_skips_oauth2(self):
|
|
"""Test that oauth2 is None when client_id is not provided."""
|
|
with patch.object(imap_session.imap_oauth2, "acquire_token") as mock_acquire:
|
|
conf = imap_session.build_imap_conf("imap.example.com", "user", "pass")
|
|
|
|
mock_acquire.assert_not_called()
|
|
assert conf["oauth2"] is None
|
|
assert conf["oauth2_token"] is None
|
|
|
|
def test_empty_client_id_skips_oauth2(self):
|
|
"""Test that empty string client_id is treated as no OAuth2."""
|
|
with patch.object(imap_session.imap_oauth2, "acquire_token") as mock_acquire:
|
|
conf = imap_session.build_imap_conf("imap.example.com", "user", "pass", client_id="")
|
|
|
|
mock_acquire.assert_not_called()
|
|
assert conf["oauth2"] is None
|
|
|
|
def test_none_client_secret_passed_through(self):
|
|
"""Test that client_secret=None is correctly passed to acquire_token and stored."""
|
|
with patch.object(imap_session.imap_oauth2, "acquire_token", return_value=("tok", "microsoft")):
|
|
conf = imap_session.build_imap_conf("outlook.office365.com", "user@example.com", "pass", client_id="cid")
|
|
|
|
assert conf["oauth2"]["client_secret"] is None
|
|
|
|
def test_label_forwarded_to_acquire_token(self):
|
|
"""Test that label is passed through to acquire_token."""
|
|
with patch.object(imap_session.imap_oauth2, "acquire_token", return_value=("tok", "google")) as mock_acquire:
|
|
imap_session.build_imap_conf(
|
|
"imap.gmail.com", "user@gmail.com", "pass", client_id="cid", client_secret="sec", label="destination"
|
|
)
|
|
|
|
mock_acquire.assert_called_once_with("imap.gmail.com", "cid", "user@gmail.com", "sec", "destination")
|
|
|
|
|
|
class TestEnsureConnection:
|
|
"""Tests for ensure_connection function."""
|
|
|
|
def test_with_oauth2_calls_refresh(self):
|
|
"""Test that OAuth2 token refresh is called when oauth2 config is present."""
|
|
conf = {
|
|
"host": "imap.example.com",
|
|
"user": "user@example.com",
|
|
"password": "pass",
|
|
"oauth2_token": "old_token",
|
|
"oauth2": {
|
|
"provider": "microsoft",
|
|
"client_id": "client-id",
|
|
"email": "user@example.com",
|
|
},
|
|
}
|
|
mock_conn = MagicMock()
|
|
|
|
with patch.object(imap_session.imap_oauth2, "refresh_oauth2_token") as mock_refresh:
|
|
with patch.object(
|
|
imap_session.imap_common, "ensure_connection_from_conf", return_value=mock_conn
|
|
) as mock_ensure:
|
|
result = imap_session.ensure_connection(mock_conn, conf)
|
|
|
|
mock_refresh.assert_called_once_with(conf, "old_token")
|
|
mock_ensure.assert_called_once_with(mock_conn, conf)
|
|
assert result is mock_conn
|
|
|
|
def test_without_oauth2_skips_refresh(self):
|
|
"""Test that token refresh is skipped when oauth2 config is not present."""
|
|
conf = {
|
|
"host": "imap.example.com",
|
|
"user": "user@example.com",
|
|
"password": "pass",
|
|
"oauth2": None,
|
|
}
|
|
mock_conn = MagicMock()
|
|
|
|
with patch.object(imap_session.imap_oauth2, "refresh_oauth2_token") as mock_refresh:
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=mock_conn):
|
|
imap_session.ensure_connection(mock_conn, conf)
|
|
|
|
mock_refresh.assert_not_called()
|
|
|
|
def test_without_oauth2_key_skips_refresh(self):
|
|
"""Test that token refresh is skipped when oauth2 key is missing entirely."""
|
|
conf = {
|
|
"host": "imap.example.com",
|
|
"user": "user@example.com",
|
|
"password": "pass",
|
|
}
|
|
mock_conn = MagicMock()
|
|
|
|
with patch.object(imap_session.imap_oauth2, "refresh_oauth2_token") as mock_refresh:
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=mock_conn):
|
|
imap_session.ensure_connection(mock_conn, conf)
|
|
|
|
mock_refresh.assert_not_called()
|
|
|
|
def test_returns_connection_from_ensure_connection_from_conf(self):
|
|
"""Test returns the connection from ensure_connection_from_conf."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=new_conn):
|
|
result = imap_session.ensure_connection(old_conn, conf)
|
|
|
|
assert result is new_conn
|
|
|
|
def test_returns_none_when_connection_fails(self):
|
|
"""Test returns None when ensure_connection_from_conf fails."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=None):
|
|
result = imap_session.ensure_connection(MagicMock(), conf)
|
|
|
|
assert result is None
|
|
|
|
def test_with_none_connection(self):
|
|
"""Test works correctly when passed None as connection."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(
|
|
imap_session.imap_common, "ensure_connection_from_conf", return_value=new_conn
|
|
) as mock_ensure:
|
|
result = imap_session.ensure_connection(None, conf)
|
|
|
|
mock_ensure.assert_called_once_with(None, conf)
|
|
assert result is new_conn
|
|
|
|
|
|
class TestEnsureFolderSession:
|
|
"""Tests for ensure_folder_session function."""
|
|
|
|
def test_same_connection_no_folder_select(self):
|
|
"""Test that folder is not re-selected when connection stays the same."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
mock_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=mock_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(mock_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is mock_conn
|
|
assert success is True
|
|
mock_conn.select.assert_not_called()
|
|
|
|
def test_new_connection_selects_folder(self):
|
|
"""Test that folder is selected when connection changes."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(old_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is True
|
|
new_conn.select.assert_called_once_with('"INBOX"', readonly=True)
|
|
|
|
def test_new_connection_selects_folder_readwrite(self):
|
|
"""Test that folder is selected with readonly=False when specified."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(
|
|
old_conn, conf, "[Gmail]/All Mail", readonly=False
|
|
)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is True
|
|
new_conn.select.assert_called_once_with('"[Gmail]/All Mail"', readonly=False)
|
|
|
|
def test_none_connection_returns_failure(self):
|
|
"""Test returns (None, False) when ensure_connection returns None."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
mock_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=None):
|
|
result_conn, success = imap_session.ensure_folder_session(mock_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is None
|
|
assert success is False
|
|
|
|
def test_folder_select_failure_returns_false(self):
|
|
"""Test returns (conn, False) when folder selection fails."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
new_conn.select.side_effect = Exception("Folder not found")
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(old_conn, conf, "NonExistent", readonly=True)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is False
|
|
|
|
def test_none_old_connection_selects_folder(self):
|
|
"""Test that folder is selected when old connection was None."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(None, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is True
|
|
new_conn.select.assert_called_once_with('"INBOX"', readonly=True)
|
|
|
|
def test_folder_name_with_spaces(self):
|
|
"""Test folder selection with folder names containing spaces."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(old_conn, conf, "My Folder", readonly=True)
|
|
|
|
assert success is True
|
|
new_conn.select.assert_called_once_with('"My Folder"', readonly=True)
|
|
|
|
def test_folder_select_imap_error(self):
|
|
"""Test handles IMAP-specific errors during folder selection."""
|
|
conf = {"host": "imap.example.com", "user": "user", "password": "pass"}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
new_conn.select.side_effect = Exception("IMAP error: NO SELECT failed")
|
|
|
|
with patch.object(imap_session, "ensure_connection", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(old_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is False
|
|
|
|
|
|
class TestEnsureFolderSessionWithOAuth2:
|
|
"""Integration-style tests for ensure_folder_session with OAuth2."""
|
|
|
|
def test_oauth2_refresh_triggers_folder_reselect(self):
|
|
"""Test that OAuth2 token refresh (new connection) triggers folder reselection."""
|
|
conf = {
|
|
"host": "outlook.office365.com",
|
|
"user": "user@example.com",
|
|
"password": "oauth2_token_here",
|
|
"oauth2_token": "old_token",
|
|
"oauth2": {
|
|
"provider": "microsoft",
|
|
"client_id": "client-id",
|
|
"email": "user@example.com",
|
|
},
|
|
}
|
|
old_conn = MagicMock()
|
|
new_conn = MagicMock()
|
|
|
|
# Simulate token refresh causing a new connection
|
|
with patch.object(imap_session.imap_oauth2, "refresh_oauth2_token"):
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=new_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(old_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is new_conn
|
|
assert success is True
|
|
new_conn.select.assert_called_once_with('"INBOX"', readonly=True)
|
|
|
|
def test_healthy_connection_no_reselect(self):
|
|
"""Test that healthy connection (same object) doesn't reselect folder."""
|
|
conf = {
|
|
"host": "outlook.office365.com",
|
|
"user": "user@example.com",
|
|
"password": "oauth2_token_here",
|
|
"oauth2_token": "valid_token",
|
|
"oauth2": {
|
|
"provider": "microsoft",
|
|
"client_id": "client-id",
|
|
"email": "user@example.com",
|
|
},
|
|
}
|
|
mock_conn = MagicMock()
|
|
|
|
# Simulate healthy connection (same object returned)
|
|
with patch.object(imap_session.imap_oauth2, "refresh_oauth2_token"):
|
|
with patch.object(imap_session.imap_common, "ensure_connection_from_conf", return_value=mock_conn):
|
|
result_conn, success = imap_session.ensure_folder_session(mock_conn, conf, "INBOX", readonly=True)
|
|
|
|
assert result_conn is mock_conn
|
|
assert success is True
|
|
mock_conn.select.assert_not_called()
|