* 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.
566 lines
21 KiB
Python
566 lines
21 KiB
Python
"""
|
|
Tests for backup_imap_emails.py
|
|
|
|
Tests cover:
|
|
- Basic email backup to local files
|
|
- Incremental backup (skip existing)
|
|
- Multiple folder backup
|
|
- Filename sanitization
|
|
- 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_backup as backup_imap_emails
|
|
from conftest import temp_argv, temp_env
|
|
from providers import provider_gmail
|
|
from utils import imap_common
|
|
|
|
|
|
def _mock_imap_env(port):
|
|
return {
|
|
"SRC_IMAP_HOST": f"imap://localhost:{port}",
|
|
"SRC_IMAP_USERNAME": "user",
|
|
"SRC_IMAP_PASSWORD": "pass",
|
|
"MAX_WORKERS": "1",
|
|
"BATCH_SIZE": "1",
|
|
}
|
|
|
|
|
|
class TestBackupBasic:
|
|
"""Tests for basic backup functionality."""
|
|
|
|
def test_single_email_backup(self, single_mock_server, tmp_path):
|
|
"""Test backing up a single email to local directory."""
|
|
src_data = {"INBOX": [b"Subject: Test Email\r\nMessage-ID: <1@test>\r\n\r\nBody content"]}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
backup_imap_emails.main()
|
|
|
|
# Check that backup was created
|
|
inbox_path = tmp_path / "INBOX"
|
|
assert inbox_path.exists()
|
|
|
|
eml_files = list(inbox_path.glob("*.eml"))
|
|
assert len(eml_files) == 1
|
|
|
|
# Check content
|
|
content = eml_files[0].read_bytes()
|
|
assert b"Test Email" in content or b"Body content" in content
|
|
|
|
def test_multiple_emails_backup(self, single_mock_server, tmp_path):
|
|
"""Test backing up multiple emails."""
|
|
src_data = {
|
|
"INBOX": [
|
|
b"Subject: Email 1\r\nMessage-ID: <1@test>\r\n\r\nBody 1",
|
|
b"Subject: Email 2\r\nMessage-ID: <2@test>\r\n\r\nBody 2",
|
|
b"Subject: Email 3\r\nMessage-ID: <3@test>\r\n\r\nBody 3",
|
|
]
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
backup_imap_emails.main()
|
|
|
|
inbox_path = tmp_path / "INBOX"
|
|
eml_files = list(inbox_path.glob("*.eml"))
|
|
assert len(eml_files) == 3
|
|
|
|
|
|
class TestIncrementalBackup:
|
|
"""Tests for incremental backup functionality."""
|
|
|
|
def test_skip_existing_emails(self, single_mock_server, tmp_path):
|
|
"""Test that existing emails are skipped during incremental backup."""
|
|
src_data = {
|
|
"INBOX": [
|
|
b"Subject: Email 1\r\nMessage-ID: <1@test>\r\n\r\nBody 1",
|
|
b"Subject: Email 2\r\nMessage-ID: <2@test>\r\n\r\nBody 2",
|
|
]
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
# Create pre-existing file
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
existing_file = inbox_path / "1_Email_1.eml"
|
|
existing_file.write_bytes(b"existing content")
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
backup_imap_emails.main()
|
|
|
|
# Should still have original content (not overwritten)
|
|
assert existing_file.read_bytes() == b"existing content"
|
|
|
|
# But second email should be backed up
|
|
eml_files = list(inbox_path.glob("*.eml"))
|
|
assert len(eml_files) == 2
|
|
|
|
|
|
class TestMultipleFolderBackup:
|
|
"""Tests for backing up multiple folders."""
|
|
|
|
def test_backup_all_folders(self, single_mock_server, tmp_path):
|
|
"""Test backing up emails from multiple folders."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: Inbox\r\nMessage-ID: <1@test>\r\n\r\nC"],
|
|
"Sent": [b"Subject: Sent\r\nMessage-ID: <2@test>\r\n\r\nC"],
|
|
"Archive": [b"Subject: Archive\r\nMessage-ID: <3@test>\r\n\r\nC"],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
backup_imap_emails.main()
|
|
|
|
assert (tmp_path / "INBOX").exists()
|
|
assert (tmp_path / "Sent").exists()
|
|
assert (tmp_path / "Archive").exists()
|
|
|
|
def test_backup_single_folder(self, single_mock_server, tmp_path):
|
|
"""Test backing up a specific folder only."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: Inbox\r\nMessage-ID: <1@test>\r\n\r\nC"],
|
|
"Sent": [b"Subject: Sent\r\nMessage-ID: <2@test>\r\n\r\nC"],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path), "INBOX"]):
|
|
backup_imap_emails.main()
|
|
|
|
assert (tmp_path / "INBOX").exists()
|
|
# Sent should NOT be backed up
|
|
assert not (tmp_path / "Sent").exists()
|
|
|
|
|
|
class TestEmptyFolderHandling:
|
|
"""Tests for empty folder handling."""
|
|
|
|
def test_empty_folder(self, single_mock_server, tmp_path):
|
|
"""Test handling of empty folders."""
|
|
src_data = {"INBOX": [], "Empty": []}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
backup_imap_emails.main()
|
|
|
|
|
|
class TestConfigValidation:
|
|
"""Tests for configuration validation."""
|
|
|
|
def test_missing_credentials(self, capsys, tmp_path):
|
|
"""Test that missing credentials cause exit."""
|
|
with temp_env({}), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path)]):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
backup_imap_emails.main()
|
|
|
|
assert exc_info.value.code == 2
|
|
|
|
def test_missing_dest_path(self, capsys):
|
|
"""Test that missing destination path causes exit."""
|
|
env = {
|
|
"SRC_IMAP_HOST": "localhost",
|
|
"SRC_IMAP_USERNAME": "user",
|
|
"SRC_IMAP_PASSWORD": "pass",
|
|
}
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py"]):
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
backup_imap_emails.main()
|
|
|
|
assert exc_info.value.code == 2
|
|
|
|
|
|
class TestGetExistingUids:
|
|
"""Tests for get_existing_uids function."""
|
|
|
|
def test_empty_directory(self, tmp_path):
|
|
"""Test with empty directory."""
|
|
result = backup_imap_emails.get_existing_uids(str(tmp_path))
|
|
assert result == set()
|
|
|
|
def test_nonexistent_directory(self):
|
|
"""Test with non-existent directory."""
|
|
result = backup_imap_emails.get_existing_uids("/nonexistent/path")
|
|
assert result == set()
|
|
|
|
def test_existing_files(self, tmp_path):
|
|
"""Test extraction of UIDs from existing files."""
|
|
(tmp_path / "1_Subject.eml").touch()
|
|
(tmp_path / "2_Another.eml").touch()
|
|
(tmp_path / "100_Long_Subject.eml").touch()
|
|
|
|
result = backup_imap_emails.get_existing_uids(str(tmp_path))
|
|
assert result == {"1", "2", "100"}
|
|
|
|
def test_ignore_non_matching_files(self, tmp_path):
|
|
"""Test that non-matching files are ignored."""
|
|
(tmp_path / "not_an_email.txt").touch()
|
|
(tmp_path / "random.eml").touch() # No underscore
|
|
(tmp_path / "1_Subject.eml").touch()
|
|
|
|
result = backup_imap_emails.get_existing_uids(str(tmp_path))
|
|
assert result == {"1"}
|
|
|
|
|
|
class TestGmailLabelsPreservation:
|
|
"""Tests for Gmail labels manifest functionality."""
|
|
|
|
def test_is_label_folder_user_labels(self):
|
|
"""Test that user labels are correctly identified."""
|
|
assert provider_gmail.is_label_folder("Work") is True
|
|
assert provider_gmail.is_label_folder("Personal") is True
|
|
assert provider_gmail.is_label_folder("Projects/2024") is True
|
|
assert provider_gmail.is_label_folder("INBOX") is True
|
|
|
|
def test_is_label_folder_gmail_labels(self):
|
|
"""Test that Gmail system labels are correctly identified."""
|
|
assert provider_gmail.is_label_folder("[Gmail]/Sent Mail") is True
|
|
assert provider_gmail.is_label_folder("[Gmail]/Starred") is True
|
|
|
|
def test_is_label_folder_system_folders(self):
|
|
"""Test that Gmail system folders are excluded."""
|
|
assert provider_gmail.is_label_folder("[Gmail]/All Mail") is False
|
|
assert provider_gmail.is_label_folder("[Gmail]/Spam") is False
|
|
assert provider_gmail.is_label_folder("[Gmail]/Trash") is False
|
|
assert provider_gmail.is_label_folder("[Gmail]/Drafts") is False
|
|
assert provider_gmail.is_label_folder("[Gmail]/Bin") is False
|
|
|
|
def test_load_labels_manifest_nonexistent(self, tmp_path):
|
|
"""Test loading manifest when file doesn't exist."""
|
|
result = imap_common.load_manifest(str(tmp_path), "labels_manifest.json")
|
|
assert result == {}
|
|
|
|
def test_load_labels_manifest_existing(self, tmp_path):
|
|
"""Test loading existing manifest file."""
|
|
import json
|
|
|
|
manifest_data = {
|
|
"<msg1@test.com>": ["INBOX", "Work"],
|
|
"<msg2@test.com>": ["Sent Mail", "Personal"],
|
|
}
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
manifest_path.write_text(json.dumps(manifest_data))
|
|
|
|
result = imap_common.load_manifest(str(tmp_path), "labels_manifest.json")
|
|
assert result == manifest_data
|
|
|
|
def test_load_labels_manifest_invalid_json(self, tmp_path):
|
|
"""Test loading invalid JSON manifest file."""
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
manifest_path.write_text("not valid json {{{")
|
|
|
|
result = imap_common.load_manifest(str(tmp_path), "labels_manifest.json")
|
|
assert result == {}
|
|
|
|
def test_get_message_ids_in_folder(self, single_mock_server):
|
|
src_data = {
|
|
"INBOX": [
|
|
b"Subject: A\r\nMessage-ID: <msg1@test.com>\r\n\r\nBody",
|
|
b"Subject: B\r\nMessage-ID: <msg2@test.com>\r\n\r\nBody",
|
|
]
|
|
}
|
|
_server, port = single_mock_server(src_data)
|
|
|
|
conn = imaplib.IMAP4("localhost", port)
|
|
conn.login("user", "pass")
|
|
|
|
result = backup_imap_emails.get_message_ids_in_folder(conn, "INBOX", None)
|
|
|
|
assert "<msg1@test.com>" in result
|
|
assert "<msg2@test.com>" in result
|
|
conn.logout()
|
|
|
|
def test_get_message_info_in_folder_read_status(self, single_mock_server):
|
|
src_data = {
|
|
"INBOX": [
|
|
{"uid": 1, "flags": {"\\Seen"}, "content": b"Message-ID: <msg1@test.com>\r\n"},
|
|
{"uid": 2, "flags": set(), "content": b"Message-ID: <msg2@test.com>\r\n"},
|
|
{"uid": 3, "flags": {"\\Seen", "\\Answered"}, "content": b"Message-ID: <msg3@test.com>\r\n"},
|
|
]
|
|
}
|
|
_server, port = single_mock_server(src_data)
|
|
|
|
conn = imaplib.IMAP4("localhost", port)
|
|
conn.login("user", "pass")
|
|
|
|
result = backup_imap_emails.get_message_info_in_folder(conn, "INBOX", None)
|
|
|
|
assert "<msg1@test.com>" in result
|
|
assert "\\Seen" in result["<msg1@test.com>"]["flags"]
|
|
assert "<msg2@test.com>" in result
|
|
assert result["<msg2@test.com>"]["flags"] == []
|
|
assert "<msg3@test.com>" in result
|
|
assert "\\Seen" in result["<msg3@test.com>"]["flags"]
|
|
assert "\\Answered" in result["<msg3@test.com>"]["flags"]
|
|
conn.logout()
|
|
|
|
def test_build_labels_manifest(self, single_mock_server, tmp_path):
|
|
src_data = {
|
|
"INBOX": [b"Message-ID: <msg1@test.com>\r\n"],
|
|
"Work": [b"Message-ID: <msg1@test.com>\r\n"],
|
|
"[Gmail]/Sent Mail": [b"Message-ID: <msg2@test.com>\r\n"],
|
|
"[Gmail]/All Mail": [
|
|
{"uid": 1, "flags": {"\\Seen", "\\Flagged"}, "content": b"Message-ID: <msg1@test.com>\r\n"},
|
|
{"uid": 2, "flags": set(), "content": b"Message-ID: <msg2@test.com>\r\n"},
|
|
],
|
|
}
|
|
_server, port = single_mock_server(src_data)
|
|
|
|
conn = imaplib.IMAP4("localhost", port)
|
|
conn.login("user", "pass")
|
|
|
|
result = backup_imap_emails.build_labels_manifest(conn, str(tmp_path))
|
|
|
|
assert "<msg1@test.com>" in result
|
|
assert "<msg2@test.com>" in result
|
|
assert "INBOX" in result["<msg1@test.com>"]["labels"]
|
|
assert "Work" in result["<msg1@test.com>"]["labels"]
|
|
assert "\\Seen" in result["<msg1@test.com>"]["flags"]
|
|
assert "\\Flagged" in result["<msg1@test.com>"]["flags"]
|
|
assert "Sent Mail" in result["<msg2@test.com>"]["labels"]
|
|
assert result["<msg2@test.com>"]["flags"] == []
|
|
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
assert manifest_path.exists()
|
|
conn.logout()
|
|
|
|
def test_preserve_labels_flag_integration(self, single_mock_server, tmp_path):
|
|
"""Test --preserve-labels flag creates manifest."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
"Work": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
"[Gmail]/All Mail": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with (
|
|
temp_env(env),
|
|
temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path), "--preserve-labels", "[Gmail]/All Mail"]),
|
|
):
|
|
backup_imap_emails.main()
|
|
|
|
# Check manifest was created
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
assert manifest_path.exists()
|
|
|
|
def test_manifest_only_flag(self, single_mock_server, tmp_path):
|
|
"""Test --manifest-only flag creates manifest without downloading emails."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
"Work": [b"Subject: Work Email\r\nMessage-ID: <2@test>\r\n\r\nBody"],
|
|
"[Gmail]/All Mail": [
|
|
b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody",
|
|
b"Subject: Work Email\r\nMessage-ID: <2@test>\r\n\r\nBody",
|
|
],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path), "--manifest-only"]):
|
|
# Should exit with code 0 after creating manifest
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
backup_imap_emails.main()
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
# Check manifest was created
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
assert manifest_path.exists()
|
|
|
|
# Check NO email folders were created (no download happened)
|
|
# Only the manifest file should exist
|
|
items = list(tmp_path.iterdir())
|
|
assert len(items) == 1
|
|
assert items[0].name == "labels_manifest.json"
|
|
|
|
def test_gmail_system_folders_constant(self):
|
|
"""Test that GMAIL_SYSTEM_FOLDERS contains expected entries."""
|
|
expected = {
|
|
"[Gmail]/All Mail",
|
|
"[Gmail]/Spam",
|
|
"[Gmail]/Trash",
|
|
"[Gmail]/Drafts",
|
|
"[Gmail]/Bin",
|
|
"[Gmail]/Important",
|
|
}
|
|
assert provider_gmail.GMAIL_SYSTEM_FOLDERS == expected
|
|
|
|
def test_gmail_mode_flag(self, single_mock_server, tmp_path):
|
|
"""Test --gmail-mode flag backs up All Mail and creates manifest."""
|
|
src_data = {
|
|
"INBOX": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
"Work": [b"Subject: Work Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
"[Gmail]/All Mail": [b"Subject: Inbox Email\r\nMessage-ID: <1@test>\r\n\r\nBody"],
|
|
}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
env = _mock_imap_env(port)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py", "--dest-path", str(tmp_path), "--gmail-mode"]):
|
|
backup_imap_emails.main()
|
|
|
|
# Check manifest was created
|
|
manifest_path = tmp_path / "labels_manifest.json"
|
|
assert manifest_path.exists()
|
|
|
|
# Check only [Gmail]/All Mail folder was backed up (not INBOX or Work)
|
|
all_mail_folder = tmp_path / "[Gmail]" / "All Mail"
|
|
assert all_mail_folder.exists()
|
|
|
|
# Should NOT have created separate INBOX or Work folders
|
|
inbox_folder = tmp_path / "INBOX"
|
|
work_folder = tmp_path / "Work"
|
|
assert not inbox_folder.exists()
|
|
assert not work_folder.exists()
|
|
|
|
|
|
class TestDeleteOrphanLocalFiles:
|
|
"""Tests for delete_orphan_local_files function."""
|
|
|
|
def test_delete_orphan_files(self, tmp_path):
|
|
"""Test that local files not on server are deleted."""
|
|
# Create a local folder with some .eml files
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
|
|
# Create files with UIDs 1, 2, 3
|
|
(inbox_path / "1_Test_Email.eml").write_bytes(b"content1")
|
|
(inbox_path / "2_Another_Email.eml").write_bytes(b"content2")
|
|
(inbox_path / "3_Third_Email.eml").write_bytes(b"content3")
|
|
|
|
# Server only has UIDs 1 and 3
|
|
server_uids = {"1", "3"}
|
|
|
|
deleted = backup_imap_emails.delete_orphan_local_files(str(inbox_path), server_uids)
|
|
|
|
# UID 2 should be deleted
|
|
assert deleted == 1
|
|
assert (inbox_path / "1_Test_Email.eml").exists()
|
|
assert not (inbox_path / "2_Another_Email.eml").exists()
|
|
assert (inbox_path / "3_Third_Email.eml").exists()
|
|
|
|
def test_delete_multiple_orphans(self, tmp_path):
|
|
"""Test deleting multiple orphan files."""
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
|
|
# Create files with UIDs 1-5
|
|
for i in range(1, 6):
|
|
(inbox_path / f"{i}_Email_{i}.eml").write_bytes(f"content{i}".encode())
|
|
|
|
# Server only has UID 3
|
|
server_uids = {"3"}
|
|
|
|
deleted = backup_imap_emails.delete_orphan_local_files(str(inbox_path), server_uids)
|
|
|
|
# 4 files should be deleted (UIDs 1, 2, 4, 5)
|
|
assert deleted == 4
|
|
assert not (inbox_path / "1_Email_1.eml").exists()
|
|
assert not (inbox_path / "2_Email_2.eml").exists()
|
|
assert (inbox_path / "3_Email_3.eml").exists()
|
|
assert not (inbox_path / "4_Email_4.eml").exists()
|
|
assert not (inbox_path / "5_Email_5.eml").exists()
|
|
|
|
def test_no_orphans(self, tmp_path):
|
|
"""Test when all local files exist on server."""
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
|
|
(inbox_path / "1_Email.eml").write_bytes(b"content")
|
|
(inbox_path / "2_Email.eml").write_bytes(b"content")
|
|
|
|
server_uids = {"1", "2"}
|
|
|
|
deleted = backup_imap_emails.delete_orphan_local_files(str(inbox_path), server_uids)
|
|
|
|
assert deleted == 0
|
|
assert (inbox_path / "1_Email.eml").exists()
|
|
assert (inbox_path / "2_Email.eml").exists()
|
|
|
|
def test_nonexistent_folder(self, tmp_path):
|
|
"""Test with nonexistent folder path."""
|
|
deleted = backup_imap_emails.delete_orphan_local_files(str(tmp_path / "nonexistent"), {"1", "2"})
|
|
assert deleted == 0
|
|
|
|
def test_ignore_non_eml_files(self, tmp_path):
|
|
"""Test that non-.eml files are ignored."""
|
|
inbox_path = tmp_path / "INBOX"
|
|
inbox_path.mkdir()
|
|
|
|
(inbox_path / "1_Email.eml").write_bytes(b"content")
|
|
(inbox_path / "notes.txt").write_bytes(b"some notes")
|
|
(inbox_path / "2_data.json").write_bytes(b"{}")
|
|
|
|
# Server only has UID 1
|
|
server_uids = {"1"}
|
|
|
|
deleted = backup_imap_emails.delete_orphan_local_files(str(inbox_path), server_uids)
|
|
|
|
# Only .eml files should be considered
|
|
assert deleted == 0
|
|
assert (inbox_path / "notes.txt").exists()
|
|
assert (inbox_path / "2_data.json").exists()
|
|
|
|
|
|
class TestDestDeleteBackupArgument:
|
|
"""Tests for --dest-delete argument in backup script."""
|
|
|
|
def test_dest_delete_default_false(self):
|
|
"""Test that --dest-delete defaults to False."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("folder", nargs="?")
|
|
parser.add_argument("--dest-delete", action="store_true", default=False)
|
|
args = parser.parse_args([])
|
|
assert args.dest_delete is False
|
|
|
|
def test_dest_delete_when_set(self):
|
|
"""Test that --dest-delete can be set to True."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("folder", nargs="?")
|
|
parser.add_argument("--dest-delete", action="store_true", default=False)
|
|
args = parser.parse_args(["--dest-delete"])
|
|
assert args.dest_delete is True
|
|
|
|
|
|
class TestDestDeleteBackupEnvVar:
|
|
"""End-to-end tests for DEST_DELETE env var wiring in backup script."""
|
|
|
|
def test_dest_delete_enabled_via_env_var_deletes_orphans(self, single_mock_server, tmp_path):
|
|
"""If DEST_DELETE=true and server folder is empty, local orphans are deleted."""
|
|
src_data = {"INBOX": []}
|
|
_, port = single_mock_server(src_data)
|
|
|
|
backup_root = tmp_path / "backup"
|
|
inbox_path = backup_root / "INBOX"
|
|
inbox_path.mkdir(parents=True)
|
|
orphan = inbox_path / "1_Orphan.eml"
|
|
orphan.write_bytes(b"Subject: Orphan\r\nMessage-ID: <orphan@test>\r\n\r\nBody")
|
|
|
|
env = _mock_imap_env(port)
|
|
env.update(
|
|
{
|
|
"BACKUP_LOCAL_PATH": str(backup_root),
|
|
"DEST_DELETE": "true",
|
|
}
|
|
)
|
|
with temp_env(env), temp_argv(["backup_imap_emails.py"]):
|
|
backup_imap_emails.main()
|
|
|
|
assert not orphan.exists()
|