mirror of
https://github.com/khoaliber/LetterFeed.git
synced 2026-03-02 13:18:27 +00:00
feat: per newsletter folder move setting
This commit is contained in:
@@ -51,7 +51,9 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
|
||||
"""Create a new newsletter."""
|
||||
logger.info(f"Creating new newsletter with name '{newsletter.name}'")
|
||||
db_newsletter = Newsletter(
|
||||
name=newsletter.name, extract_content=newsletter.extract_content
|
||||
name=newsletter.name,
|
||||
extract_content=newsletter.extract_content,
|
||||
move_to_folder=newsletter.move_to_folder,
|
||||
)
|
||||
db.add(db_newsletter)
|
||||
db.commit()
|
||||
@@ -79,6 +81,8 @@ def update_newsletter(
|
||||
return None
|
||||
|
||||
db_newsletter.name = newsletter_update.name
|
||||
db_newsletter.move_to_folder = newsletter_update.move_to_folder
|
||||
db_newsletter.extract_content = newsletter_update.extract_content
|
||||
|
||||
# Simple approach: delete existing senders and add new ones
|
||||
for sender in db_newsletter.senders:
|
||||
|
||||
@@ -11,6 +11,7 @@ class Newsletter(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String)
|
||||
move_to_folder = Column(String, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
extract_content = Column(Boolean, default=False)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ class NewsletterBase(BaseModel):
|
||||
"""Base schema for a newsletter."""
|
||||
|
||||
name: str
|
||||
move_to_folder: str | None = None
|
||||
extract_content: bool = False
|
||||
|
||||
|
||||
|
||||
@@ -146,9 +146,10 @@ def _process_single_email(
|
||||
logger.debug(f"Marking email with id={num} as read")
|
||||
mail.store(num, "+FLAGS", "\\Seen")
|
||||
|
||||
if settings.move_to_folder:
|
||||
logger.debug(f"Moving email with id={num} to {settings.move_to_folder}")
|
||||
mail.copy(num, settings.move_to_folder)
|
||||
move_folder = newsletter.move_to_folder or settings.move_to_folder
|
||||
if move_folder:
|
||||
logger.debug(f"Moving email with id={num} to {move_folder}")
|
||||
mail.copy(num, move_folder)
|
||||
mail.store(num, "+FLAGS", "\\Deleted")
|
||||
|
||||
|
||||
|
||||
@@ -110,6 +110,23 @@ def test_create_newsletter(db_session: Session):
|
||||
assert newsletter.senders[0].email == unique_email
|
||||
|
||||
|
||||
def test_create_newsletter_with_move_to_folder(db_session: Session):
|
||||
"""Test creating a newsletter with the move_to_folder attribute."""
|
||||
unique_email = f"sender_{uuid.uuid4()}@test.com"
|
||||
newsletter_data = NewsletterCreate(
|
||||
name="Test Newsletter with Folder",
|
||||
sender_emails=[unique_email],
|
||||
move_to_folder="Archive/Test",
|
||||
extract_content=True,
|
||||
)
|
||||
newsletter = create_newsletter(db_session, newsletter_data)
|
||||
retrieved_newsletter = get_newsletter(db_session, newsletter.id)
|
||||
|
||||
assert retrieved_newsletter.name == "Test Newsletter with Folder"
|
||||
assert retrieved_newsletter.move_to_folder == "Archive/Test"
|
||||
assert retrieved_newsletter.extract_content is True
|
||||
|
||||
|
||||
def test_get_newsletter(db_session: Session):
|
||||
"""Test getting a single newsletter."""
|
||||
unique_email = f"sender_{uuid.uuid4()}@test.com"
|
||||
|
||||
97
backend/app/tests/test_email_processor.py
Normal file
97
backend/app/tests/test_email_processor.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import imaplib
|
||||
from email.message import Message
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.crud.newsletters import create_newsletter
|
||||
from app.crud.settings import create_or_update_settings
|
||||
from app.schemas.newsletters import NewsletterCreate
|
||||
from app.schemas.settings import SettingsCreate
|
||||
from app.services.email_processor import _process_single_email
|
||||
|
||||
|
||||
def test_process_single_email_with_newsletter_move_folder(db_session: Session):
|
||||
"""Test that the per-newsletter move_to_folder is used when set, overriding the global setting."""
|
||||
# 1. ARRANGE
|
||||
# Global settings with a move folder
|
||||
settings_data = SettingsCreate(
|
||||
imap_server="test.com",
|
||||
imap_username="test",
|
||||
imap_password="password",
|
||||
move_to_folder="GlobalArchive",
|
||||
)
|
||||
settings = create_or_update_settings(db_session, settings_data)
|
||||
|
||||
# Newsletter with a specific move folder
|
||||
newsletter_data = NewsletterCreate(
|
||||
name="Test Newsletter",
|
||||
sender_emails=["test@example.com"],
|
||||
)
|
||||
newsletter = create_newsletter(db_session, newsletter_data)
|
||||
newsletter.move_to_folder = "NewsletterArchive"
|
||||
db_session.commit()
|
||||
|
||||
# Mock IMAP mail object
|
||||
mock_mail = MagicMock(spec=imaplib.IMAP4_SSL)
|
||||
|
||||
# Mock email message
|
||||
msg = Message()
|
||||
msg["From"] = "test@example.com"
|
||||
msg["Subject"] = "Test Email"
|
||||
msg["Message-ID"] = "<test-message-id>"
|
||||
|
||||
# Mock mail.fetch to return the message
|
||||
mock_mail.fetch.return_value = ("OK", [(b"1 (RFC822)", msg.as_bytes())])
|
||||
|
||||
sender_map = {"test@example.com": newsletter}
|
||||
|
||||
# 2. ACT
|
||||
_process_single_email("1", mock_mail, db_session, sender_map, settings)
|
||||
|
||||
# 3. ASSERT
|
||||
# Check that the email was moved to the newsletter-specific folder
|
||||
mock_mail.copy.assert_called_once_with("1", "NewsletterArchive")
|
||||
mock_mail.store.assert_any_call("1", "+FLAGS", "\\Deleted")
|
||||
|
||||
|
||||
def test_process_single_email_with_global_move_folder(db_session: Session):
|
||||
"""Test that the global move_to_folder is used when the per-newsletter setting is not set."""
|
||||
# 1. ARRANGE
|
||||
# Global settings with a move folder
|
||||
settings_data = SettingsCreate(
|
||||
imap_server="test.com",
|
||||
imap_username="test",
|
||||
imap_password="password",
|
||||
move_to_folder="GlobalArchive",
|
||||
)
|
||||
settings = create_or_update_settings(db_session, settings_data)
|
||||
|
||||
# Newsletter without a specific move folder
|
||||
newsletter_data = NewsletterCreate(
|
||||
name="Test Newsletter",
|
||||
sender_emails=["test@example.com"],
|
||||
)
|
||||
newsletter = create_newsletter(db_session, newsletter_data)
|
||||
|
||||
# Mock IMAP mail object
|
||||
mock_mail = MagicMock(spec=imaplib.IMAP4_SSL)
|
||||
|
||||
# Mock email message
|
||||
msg = Message()
|
||||
msg["From"] = "test@example.com"
|
||||
msg["Subject"] = "Test Email"
|
||||
msg["Message-ID"] = "<test-message-id-2>"
|
||||
|
||||
# Mock mail.fetch to return the message
|
||||
mock_mail.fetch.return_value = ("OK", [(b"1 (RFC822)", msg.as_bytes())])
|
||||
|
||||
sender_map = {"test@example.com": newsletter}
|
||||
|
||||
# 2. ACT
|
||||
_process_single_email("1", mock_mail, db_session, sender_map, settings)
|
||||
|
||||
# 3. ASSERT
|
||||
# Check that the email was moved to the global folder
|
||||
mock_mail.copy.assert_called_once_with("1", "GlobalArchive")
|
||||
mock_mail.store.assert_any_call("1", "+FLAGS", "\\Deleted")
|
||||
Reference in New Issue
Block a user