* 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.
258 lines
8.6 KiB
Python
258 lines
8.6 KiB
Python
"""
|
|
Tests for compare_imap_folders.py
|
|
|
|
Tests cover:
|
|
- Basic folder comparison between accounts
|
|
- Matching counts
|
|
- Mismatched counts
|
|
- Missing folders on destination
|
|
- Empty folder handling
|
|
- Configuration validation
|
|
"""
|
|
|
|
import imaplib
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
|
|
import imap_compare as compare_imap_folders
|
|
from conftest import temp_argv, temp_env
|
|
|
|
|
|
def _mock_compare_env(src_port, dest_port):
|
|
return {
|
|
"SRC_IMAP_HOST": f"imap://localhost:{src_port}",
|
|
"SRC_IMAP_USERNAME": "src_user",
|
|
"SRC_IMAP_PASSWORD": "p",
|
|
"DEST_IMAP_HOST": f"imap://localhost:{dest_port}",
|
|
"DEST_IMAP_USERNAME": "dest_user",
|
|
"DEST_IMAP_PASSWORD": "p",
|
|
}
|
|
|
|
|
|
class TestFolderComparison:
|
|
"""Tests for folder comparison functionality."""
|
|
|
|
def test_matching_counts(self, mock_server_factory, capsys):
|
|
"""Test comparison when source and destination have matching counts."""
|
|
data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB", b"Subject: 2\r\n\r\nB"],
|
|
"Sent": [b"Subject: 3\r\n\r\nB"],
|
|
}
|
|
_, _, p1, p2 = mock_server_factory(data, data.copy())
|
|
|
|
env = _mock_compare_env(p1, p2)
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "INBOX" in captured.out
|
|
assert "Sent" in captured.out
|
|
|
|
def test_mismatched_counts(self, mock_server_factory, capsys):
|
|
"""Test comparison when counts differ."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB", b"Subject: 2\r\n\r\nB", b"Subject: 3\r\n\r\nB"],
|
|
}
|
|
dest_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB"],
|
|
}
|
|
_, _, p1, p2 = mock_server_factory(src_data, dest_data)
|
|
|
|
env = _mock_compare_env(p1, p2)
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
# Source has 3, dest has 1
|
|
assert "3" in captured.out
|
|
assert "1" in captured.out
|
|
|
|
def test_folder_missing_on_destination(self, mock_server_factory, capsys):
|
|
"""Test when a folder exists on source but not destination."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB"],
|
|
"Archive": [b"Subject: 2\r\n\r\nB"],
|
|
}
|
|
dest_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB"],
|
|
}
|
|
_, _, p1, p2 = mock_server_factory(src_data, dest_data)
|
|
|
|
env = _mock_compare_env(p1, p2)
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
# Archive should show N/A for destination
|
|
assert "Archive" in captured.out
|
|
assert "N/A" in captured.out
|
|
|
|
|
|
class TestEmptyFolders:
|
|
"""Tests for empty folder handling."""
|
|
|
|
def test_empty_folders(self, mock_server_factory, capsys):
|
|
"""Test comparison with empty folders."""
|
|
src_data = {"INBOX": [], "Empty": []}
|
|
dest_data = {"INBOX": [], "Empty": []}
|
|
|
|
_, _, p1, p2 = mock_server_factory(src_data, dest_data)
|
|
|
|
env = _mock_compare_env(p1, p2)
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "INBOX" in captured.out
|
|
assert "0" in captured.out
|
|
|
|
|
|
class TestGetEmailCount:
|
|
"""Tests for get_email_count function."""
|
|
|
|
def test_successful_count(self, single_mock_server):
|
|
"""Test successful email count."""
|
|
data = {"INBOX": [b"Subject: 1\r\n\r\nB", b"Subject: 2\r\n\r\nB"]}
|
|
_, port = single_mock_server(data)
|
|
|
|
conn = imaplib.IMAP4("localhost", port)
|
|
conn.login("user", "pass")
|
|
|
|
result = compare_imap_folders.get_email_count(conn, "INBOX")
|
|
assert result == 2
|
|
|
|
conn.logout()
|
|
|
|
def test_nonexistent_folder(self, single_mock_server):
|
|
"""Test count for non-existent folder returns None."""
|
|
data = {"INBOX": []}
|
|
_, port = single_mock_server(data)
|
|
|
|
conn = imaplib.IMAP4("localhost", port)
|
|
conn.login("user", "pass")
|
|
|
|
result = compare_imap_folders.get_email_count(conn, "NonExistent")
|
|
assert result is None
|
|
|
|
conn.logout()
|
|
|
|
|
|
class TestConfigValidation:
|
|
"""Tests for configuration validation."""
|
|
|
|
def test_missing_source_credentials(self, capsys):
|
|
"""Test that missing source credentials cause exit."""
|
|
env = {
|
|
"DEST_IMAP_HOST": "localhost",
|
|
"DEST_IMAP_USERNAME": "dest",
|
|
"DEST_IMAP_PASSWORD": "p",
|
|
}
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
compare_imap_folders.main()
|
|
|
|
assert exc_info.value.code == 2
|
|
|
|
def test_missing_dest_credentials(self, capsys):
|
|
"""Test that missing destination credentials cause exit."""
|
|
env = {
|
|
"SRC_IMAP_HOST": "localhost",
|
|
"SRC_IMAP_USERNAME": "src",
|
|
"SRC_IMAP_PASSWORD": "p",
|
|
}
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
compare_imap_folders.main()
|
|
|
|
assert exc_info.value.code == 2
|
|
|
|
|
|
class TestTotals:
|
|
"""Tests for total calculation."""
|
|
|
|
def test_total_calculation(self, mock_server_factory, capsys):
|
|
"""Test that totals are calculated correctly."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB", b"Subject: 2\r\n\r\nB"],
|
|
"Sent": [b"Subject: 3\r\n\r\nB", b"Subject: 4\r\n\r\nB", b"Subject: 5\r\n\r\nB"],
|
|
}
|
|
dest_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB"],
|
|
"Sent": [b"Subject: 3\r\n\r\nB"],
|
|
}
|
|
_, _, p1, p2 = mock_server_factory(src_data, dest_data)
|
|
|
|
env = _mock_compare_env(p1, p2)
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py"]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
# Total source: 5, Total dest: 2, Diff: 3
|
|
assert "TOTAL" in captured.out
|
|
assert "5" in captured.out
|
|
assert "2" in captured.out
|
|
|
|
|
|
class TestLocalFolderComparison:
|
|
"""Tests for comparing IMAP folders to local backup folders."""
|
|
|
|
def test_local_source_to_imap_dest(self, single_mock_server, tmp_path, capsys):
|
|
"""Local source folder list drives the comparison; destination is IMAP."""
|
|
# Local source: INBOX has 2 emails, Archive has 1
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
(inbox_path / "1_a.eml").write_bytes(b"Subject: A\r\n\r\nBody")
|
|
(inbox_path / "2_b.eml").write_bytes(b"Subject: B\r\n\r\nBody")
|
|
|
|
archive_path = tmp_path / "Archive"
|
|
archive_path.mkdir()
|
|
(archive_path / "1_c.eml").write_bytes(b"Subject: C\r\n\r\nBody")
|
|
|
|
# Destination IMAP: INBOX has 1, Archive is missing
|
|
dest_data = {"INBOX": [b"Subject: 1\r\n\r\nB"]}
|
|
_, port = single_mock_server(dest_data)
|
|
|
|
env = {
|
|
"DEST_IMAP_HOST": f"imap://localhost:{port}",
|
|
"DEST_IMAP_USERNAME": "dest_user",
|
|
"DEST_IMAP_PASSWORD": "p",
|
|
}
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py", "--src-path", str(tmp_path)]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "INBOX" in captured.out
|
|
assert "Archive" in captured.out
|
|
# Destination should show N/A for missing Archive
|
|
assert "N/A" in captured.out
|
|
|
|
def test_imap_source_to_local_dest(self, single_mock_server, tmp_path, capsys):
|
|
"""IMAP source folder list drives the comparison; destination is local."""
|
|
# Source IMAP: INBOX has 2, Sent has 1
|
|
src_data = {
|
|
"INBOX": [b"Subject: 1\r\n\r\nB", b"Subject: 2\r\n\r\nB"],
|
|
"Sent": [b"Subject: 3\r\n\r\nB"],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
# Local dest: INBOX has 1, Sent missing
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
(inbox_path / "1_a.eml").write_bytes(b"Subject: A\r\n\r\nBody")
|
|
env = {
|
|
"SRC_IMAP_HOST": f"imap://localhost:{port}",
|
|
"SRC_IMAP_USERNAME": "src_user",
|
|
"SRC_IMAP_PASSWORD": "p",
|
|
}
|
|
with temp_env(env), temp_argv(["compare_imap_folders.py", "--dest-path", str(tmp_path)]):
|
|
compare_imap_folders.main()
|
|
|
|
captured = capsys.readouterr()
|
|
assert "INBOX" in captured.out
|
|
assert "Sent" in captured.out
|
|
assert "N/A" in captured.out
|