""" 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