imap-migration-tools/test/test_imap_compare.py
Javier Callico ac3307bf1d
Align names for scripts and commands (#40)
* 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.
2026-02-15 08:39:37 -05:00

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