feat: Add various tools for document processing and management

- Introduced `docx-extractor` for extracting content and metadata from Word .docx files.
- Added `docx2md` for converting Word .docx files to Markdown format.
- Implemented `gpt` for generating text using OpenAI's GPT models.
- Created `html2text` for converting HTML content to plain text.
- Developed `mbox2eml` for converting MBOX mailbox files to individual .eml files.
- Added `md2pdf` for converting Markdown files to PDF format.
- Introduced `mdscraper` for combining text files into a single markdown document.
- Created `pdf-extractor` for extracting content and metadata from PDF files.
- Developed `pdf2md` for converting PDF files to Markdown format.
- Implemented `protonmail` for managing ProtonMail emails via Bridge.
- Added `schedule` for managing systemd timers and services.
- Introduced `search` for managing searchable collections of files.
- Added `text-extractor` for extracting content and metadata from text files.
- Removed outdated recommendations document.
This commit is contained in:
2026-02-22 22:18:41 -08:00
parent eab6f8b535
commit 6d0332e32b
75 changed files with 62 additions and 235 deletions

View File

View File

@@ -0,0 +1,195 @@
"""Tests for protonmail tool."""
import subprocess
import sys
from email.message import Message
from pathlib import Path
from unittest.mock import patch
from protonmail.main import (
extract_email_address,
generate_email_filename,
get_sync_dir,
list_emails_local,
sanitize_email,
sanitize_filename,
search_emails_local,
)
class TestCLI:
"""CLI interface tests."""
def test_version(self) -> None:
"""--version prints version string."""
result = subprocess.run(
[sys.executable, "-m", "protonmail.main", "--version"],
capture_output=True,
text=True,
)
assert "protonmail" in result.stdout
assert "0.1.0" in result.stdout
def test_help(self) -> None:
"""--help shows usage information."""
result = subprocess.run(
[sys.executable, "-m", "protonmail.main", "--help"],
capture_output=True,
text=True,
)
assert result.returncode == 0
assert "ProtonMail" in result.stdout
class TestSanitize:
"""Tests for filename sanitization."""
def test_sanitize_filename_basic(self) -> None:
"""Basic filename sanitization."""
assert sanitize_filename("hello world") == "hello world"
def test_sanitize_filename_special_chars(self) -> None:
"""Special characters replaced with underscores."""
assert sanitize_filename('file<>:"/\\|?*name') == "file_________name"
def test_sanitize_filename_truncation(self) -> None:
"""Long filenames get truncated."""
long_name = "x" * 300
result = sanitize_filename(long_name)
assert len(result) == 200
def test_sanitize_email_basic(self) -> None:
"""Email sanitization replaces @."""
assert sanitize_email("user@domain.com") == "user_domain.com"
def test_sanitize_email_special(self) -> None:
"""Email with angle brackets cleaned."""
assert sanitize_email("<user@domain.com>") == "user_domain.com"
class TestExtractEmail:
"""Tests for email address extraction."""
def test_angle_brackets(self) -> None:
"""Extract email from angle brackets."""
assert extract_email_address("Name <user@example.com>") == "user@example.com"
def test_plain_email(self) -> None:
"""Extract plain email address."""
assert extract_email_address("user@example.com") == "user@example.com"
def test_no_email(self) -> None:
"""Return trimmed string when no email present."""
assert extract_email_address("unknown") == "unknown"
class TestGenerateFilename:
"""Tests for email filename generation."""
def test_basic_filename(self) -> None:
"""Generate filename from message headers."""
msg = Message()
msg["Date"] = "Thu, 17 Jul 2024 01:14:40 +0000"
msg["From"] = "sender@example.com"
msg["To"] = "recipient@example.com"
msg["Subject"] = "Test Subject"
filename = generate_email_filename(msg)
assert filename.startswith("2024-07-17_01-14-40_from_sender_example.com_to_recipient_example.com_")
assert filename.endswith(".eml")
assert "Test Subject" in filename
def test_missing_date(self) -> None:
"""Handle missing date gracefully."""
msg = Message()
msg["From"] = "sender@example.com"
msg["To"] = "recipient@example.com"
filename = generate_email_filename(msg)
assert "unknown-date" in filename
def test_length_limit(self) -> None:
"""Filename respects max_length."""
msg = Message()
msg["Date"] = "Thu, 17 Jul 2024 01:14:40 +0000"
msg["From"] = "sender@example.com"
msg["To"] = "recipient@example.com"
msg["Subject"] = "x" * 300
filename = generate_email_filename(msg, max_length=255)
assert len(filename) <= 255
class TestGetSyncDir:
"""Tests for sync directory resolution."""
def test_protonmail_data_dir_env(self) -> None:
"""PROTONMAIL_DATA_DIR takes priority."""
with patch.dict("os.environ", {"PROTONMAIL_DATA_DIR": "/custom/path"}):
assert get_sync_dir() == Path("/custom/path")
def test_ddata_fallback(self) -> None:
"""Falls back to DDATA when PROTONMAIL_DATA_DIR not set."""
env = {"DDATA": "/mydata"}
with patch.dict("os.environ", env, clear=True):
assert get_sync_dir() == Path("/mydata/messages/email/protonmail")
def test_default(self) -> None:
"""Default path when no env vars set."""
with patch.dict("os.environ", {}, clear=True):
assert get_sync_dir() == Path("/data/messages/email/protonmail")
class TestListEmailsLocal:
"""Tests for local email listing."""
def test_missing_cache(self, tmp_path: Path) -> None:
"""Returns False when cache doesn't exist."""
with patch("protonmail.main.get_sync_dir", return_value=tmp_path / "nope"):
assert list_emails_local() is False
def test_empty_folder(self, tmp_path: Path) -> None:
"""Shows message when folder is empty."""
inbox = tmp_path / "INBOX"
inbox.mkdir()
with patch("protonmail.main.get_sync_dir", return_value=tmp_path):
assert list_emails_local() is True
def test_lists_eml_files(self, tmp_path: Path) -> None:
"""Lists .eml files from cache."""
inbox = tmp_path / "INBOX"
inbox.mkdir()
eml_content = b"From: test@example.com\nSubject: Hello\nDate: Thu, 1 Jan 2024 00:00:00 +0000\n\nBody"
(inbox / "test.eml").write_bytes(eml_content)
with patch("protonmail.main.get_sync_dir", return_value=tmp_path):
assert list_emails_local() is True
class TestSearchEmailsLocal:
"""Tests for local email search."""
def test_missing_cache(self, tmp_path: Path) -> None:
"""Returns False when cache doesn't exist."""
with patch("protonmail.main.get_sync_dir", return_value=tmp_path / "nope"):
assert search_emails_local("test") is False
def test_finds_matching_email(self, tmp_path: Path) -> None:
"""Finds emails matching query."""
inbox = tmp_path / "INBOX"
inbox.mkdir()
eml_content = b"From: test@example.com\nSubject: Invoice 123\nDate: Thu, 1 Jan 2024 00:00:00 +0000\n\nBody"
(inbox / "test.eml").write_bytes(eml_content)
with patch("protonmail.main.get_sync_dir", return_value=tmp_path):
assert search_emails_local("invoice") is True
def test_no_match(self, tmp_path: Path) -> None:
"""Returns True but prints nothing when no match."""
inbox = tmp_path / "INBOX"
inbox.mkdir()
eml_content = b"From: test@example.com\nSubject: Hello\nDate: Thu, 1 Jan 2024 00:00:00 +0000\n\nBody"
(inbox / "test.eml").write_bytes(eml_content)
with patch("protonmail.main.get_sync_dir", return_value=tmp_path):
assert search_emails_local("nonexistent") is True