268 lines
9.2 KiB
Python
268 lines
9.2 KiB
Python
|
|
"""Tests for multi-mailbox support."""
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
import json
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from google_mail.models import MultiMailboxData
|
||
|
|
from google_mail.services.mailbox import MailboxService
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def multi_mailbox_services(tmp_path):
|
||
|
|
"""Create two mailbox services with different data."""
|
||
|
|
work_path = tmp_path / "work.json"
|
||
|
|
work_path.write_text(
|
||
|
|
json.dumps(
|
||
|
|
{
|
||
|
|
"mailbox": {"email": "alex@acmecorp.com", "name": "Alex Morgan"},
|
||
|
|
"contacts": [{"email": "pam@acmecorp.com", "name": "Pam Chen"}],
|
||
|
|
"folders": [],
|
||
|
|
"emails": [
|
||
|
|
{
|
||
|
|
"email_id": "1",
|
||
|
|
"folder": "INBOX",
|
||
|
|
"subject": "Work email",
|
||
|
|
"from_addr": "pam@acmecorp.com",
|
||
|
|
"to_addr": "alex@acmecorp.com",
|
||
|
|
"date": "2026-04-14T09:00:00Z",
|
||
|
|
"message_id": "<work1@acmecorp.com>",
|
||
|
|
"body_text": "This is a work email.",
|
||
|
|
"is_read": False,
|
||
|
|
"is_important": False,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"next_email_id": 10,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
)
|
||
|
|
personal_path = tmp_path / "personal.json"
|
||
|
|
personal_path.write_text(
|
||
|
|
json.dumps(
|
||
|
|
{
|
||
|
|
"mailbox": {"email": "alex.m@gmail.com", "name": "Alex"},
|
||
|
|
"contacts": [{"email": "friend@gmail.com", "name": "Best Friend"}],
|
||
|
|
"folders": [],
|
||
|
|
"emails": [
|
||
|
|
{
|
||
|
|
"email_id": "1",
|
||
|
|
"folder": "INBOX",
|
||
|
|
"subject": "Personal email",
|
||
|
|
"from_addr": "friend@gmail.com",
|
||
|
|
"to_addr": "alex.m@gmail.com",
|
||
|
|
"date": "2026-04-14T10:00:00Z",
|
||
|
|
"message_id": "<personal1@gmail.com>",
|
||
|
|
"body_text": "Hey Alex! Want to grab lunch?",
|
||
|
|
"is_read": False,
|
||
|
|
"is_important": False,
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"next_email_id": 10,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
work_svc = MailboxService(work_path)
|
||
|
|
work_svc.load()
|
||
|
|
personal_svc = MailboxService(personal_path)
|
||
|
|
personal_svc.load()
|
||
|
|
|
||
|
|
return {"work": work_svc, "personal": personal_svc}
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def outputdir(tmp_path):
|
||
|
|
out = tmp_path / "output" / "google_mail"
|
||
|
|
out.mkdir(parents=True)
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(autouse=True)
|
||
|
|
def _patch_server_globals(multi_mailbox_services, outputdir):
|
||
|
|
import google_mail.state as state
|
||
|
|
|
||
|
|
state.set_mailboxes(multi_mailbox_services)
|
||
|
|
state.set_snapshot_paths(final_path=outputdir / "final.json")
|
||
|
|
yield
|
||
|
|
state.set_mailboxes({})
|
||
|
|
state.set_snapshot_paths(final_path=None, bundle_state_path=None)
|
||
|
|
|
||
|
|
|
||
|
|
class TestListMailboxes:
|
||
|
|
def test_lists_all_mailboxes(self):
|
||
|
|
from google_mail.server import mail_list_mailboxes
|
||
|
|
|
||
|
|
result = json.loads(asyncio.run(mail_list_mailboxes()))
|
||
|
|
assert result["total"] == 2
|
||
|
|
ids = [m["mailbox_id"] for m in result["mailboxes"]]
|
||
|
|
assert "work" in ids
|
||
|
|
assert "personal" in ids
|
||
|
|
|
||
|
|
def test_mailbox_info(self):
|
||
|
|
from google_mail.server import mail_list_mailboxes
|
||
|
|
|
||
|
|
result = json.loads(asyncio.run(mail_list_mailboxes()))
|
||
|
|
work = next(m for m in result["mailboxes"] if m["mailbox_id"] == "work")
|
||
|
|
assert work["email"] == "alex@acmecorp.com"
|
||
|
|
assert work["email_count"] == 1
|
||
|
|
assert work["contact_count"] == 1
|
||
|
|
|
||
|
|
|
||
|
|
class TestMailboxIsolation:
|
||
|
|
def test_get_emails_from_work(self):
|
||
|
|
from google_mail.server import mail_get_emails
|
||
|
|
|
||
|
|
result = json.loads(asyncio.run(mail_get_emails(mailbox_id="work")))
|
||
|
|
assert result["total"] == 1
|
||
|
|
assert result["emails"][0]["subject"] == "Work email"
|
||
|
|
|
||
|
|
def test_get_emails_from_personal(self):
|
||
|
|
from google_mail.server import mail_get_emails
|
||
|
|
|
||
|
|
result = json.loads(asyncio.run(mail_get_emails(mailbox_id="personal")))
|
||
|
|
assert result["total"] == 1
|
||
|
|
assert result["emails"][0]["subject"] == "Personal email"
|
||
|
|
|
||
|
|
def test_contacts_are_isolated(self):
|
||
|
|
from google_mail.server import mail_get_contacts
|
||
|
|
|
||
|
|
work_result = json.loads(asyncio.run(mail_get_contacts(mailbox_id="work")))
|
||
|
|
assert len(work_result["contacts"]) == 1
|
||
|
|
assert work_result["contacts"][0]["name"] == "Pam Chen"
|
||
|
|
|
||
|
|
personal_result = json.loads(asyncio.run(mail_get_contacts(mailbox_id="personal")))
|
||
|
|
assert len(personal_result["contacts"]) == 1
|
||
|
|
assert personal_result["contacts"][0]["name"] == "Best Friend"
|
||
|
|
|
||
|
|
def test_send_email_in_correct_mailbox(self):
|
||
|
|
from google_mail.server import mail_send_email
|
||
|
|
|
||
|
|
result = json.loads(
|
||
|
|
asyncio.run(
|
||
|
|
mail_send_email(
|
||
|
|
to="pam@acmecorp.com",
|
||
|
|
subject="Test from work",
|
||
|
|
body="Sent from work mailbox",
|
||
|
|
mailbox_id="work",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert result["status"] == "sent"
|
||
|
|
assert result["email"]["from"] == "alex@acmecorp.com"
|
||
|
|
|
||
|
|
def test_send_email_wrong_mailbox_contact(self):
|
||
|
|
from google_mail.server import mail_send_email
|
||
|
|
|
||
|
|
# pam@acmecorp.com is a work contact, not personal
|
||
|
|
result = json.loads(
|
||
|
|
asyncio.run(
|
||
|
|
mail_send_email(
|
||
|
|
to="pam@acmecorp.com",
|
||
|
|
subject="Should fail",
|
||
|
|
body="Wrong mailbox",
|
||
|
|
mailbox_id="personal",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
)
|
||
|
|
assert "error" in result # recipient not found in personal contacts
|
||
|
|
|
||
|
|
def test_invalid_mailbox_id(self):
|
||
|
|
from google_mail.server import mail_get_emails
|
||
|
|
|
||
|
|
with pytest.raises(ValueError, match="not found"):
|
||
|
|
asyncio.run(mail_get_emails(mailbox_id="nonexistent"))
|
||
|
|
|
||
|
|
def test_write_to_one_doesnt_affect_other(self):
|
||
|
|
from google_mail.server import mail_create_folder, mail_get_folders
|
||
|
|
|
||
|
|
# Create folder in work
|
||
|
|
asyncio.run(mail_create_folder(folder_name="Projects", mailbox_id="work"))
|
||
|
|
|
||
|
|
# Verify it's in work
|
||
|
|
work_folders = json.loads(asyncio.run(mail_get_folders(mailbox_id="work")))
|
||
|
|
folder_names = [f["name"] for f in work_folders["folders"]]
|
||
|
|
assert "Projects" in folder_names
|
||
|
|
|
||
|
|
# Verify it's NOT in personal
|
||
|
|
personal_folders = json.loads(asyncio.run(mail_get_folders(mailbox_id="personal")))
|
||
|
|
personal_names = [f["name"] for f in personal_folders["folders"]]
|
||
|
|
assert "Projects" not in personal_names
|
||
|
|
|
||
|
|
|
||
|
|
class TestMultiMailboxSnapshot:
|
||
|
|
def test_snapshot_includes_all_mailboxes(self, outputdir):
|
||
|
|
from google_mail.server import mail_send_email
|
||
|
|
|
||
|
|
final = outputdir / "final.json"
|
||
|
|
|
||
|
|
asyncio.run(
|
||
|
|
mail_send_email(
|
||
|
|
to="pam@acmecorp.com",
|
||
|
|
subject="Trigger snapshot",
|
||
|
|
body="test",
|
||
|
|
mailbox_id="work",
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
assert final.exists()
|
||
|
|
snapshot = json.loads(final.read_text())
|
||
|
|
# Multi-mailbox format
|
||
|
|
assert "mailboxes" in snapshot
|
||
|
|
assert "work" in snapshot["mailboxes"]
|
||
|
|
assert "personal" in snapshot["mailboxes"]
|
||
|
|
|
||
|
|
|
||
|
|
class TestMultiMailboxStateTools:
|
||
|
|
def test_export_state_returns_typed_multi_mailbox_state(self):
|
||
|
|
from google_mail.server import export_state
|
||
|
|
|
||
|
|
state = asyncio.run(export_state())
|
||
|
|
|
||
|
|
assert isinstance(state, MultiMailboxData)
|
||
|
|
assert set(state.mailboxes) == {"work", "personal"}
|
||
|
|
|
||
|
|
def test_import_state_accepts_multi_mailbox_dict(self):
|
||
|
|
import google_mail.state as mail_state
|
||
|
|
from google_mail.server import import_state, mail_list_mailboxes
|
||
|
|
|
||
|
|
state = {
|
||
|
|
"mailboxes": {
|
||
|
|
"replacement": {
|
||
|
|
"mailbox": {"email": "replacement@example.com", "name": "Replacement"},
|
||
|
|
"contacts": [],
|
||
|
|
"folders": [],
|
||
|
|
"emails": [],
|
||
|
|
"next_email_id": 1,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
result = asyncio.run(import_state(state))
|
||
|
|
|
||
|
|
assert result == {"ok": True}
|
||
|
|
assert set(mail_state.get_mailboxes()) == {"replacement"}
|
||
|
|
listed = json.loads(asyncio.run(mail_list_mailboxes()))
|
||
|
|
assert listed["mailboxes"][0]["mailbox_id"] == "replacement"
|
||
|
|
|
||
|
|
def test_import_state_flat_payload_replaces_multi_mailbox_registry(self):
|
||
|
|
import google_mail.state as mail_state
|
||
|
|
from google_mail.server import import_state, mail_list_mailboxes
|
||
|
|
|
||
|
|
state = {
|
||
|
|
"mailbox": {"email": "replacement@example.com", "name": "Replacement"},
|
||
|
|
"contacts": [],
|
||
|
|
"folders": [],
|
||
|
|
"emails": [],
|
||
|
|
"next_email_id": 1,
|
||
|
|
}
|
||
|
|
|
||
|
|
result = asyncio.run(import_state(state))
|
||
|
|
|
||
|
|
assert result == {"ok": True}
|
||
|
|
assert set(mail_state.get_mailboxes()) == {"default"}
|
||
|
|
listed = json.loads(asyncio.run(mail_list_mailboxes()))
|
||
|
|
assert listed["total"] == 1
|
||
|
|
assert listed["mailboxes"][0]["mailbox_id"] == "default"
|
||
|
|
assert listed["mailboxes"][0]["email"] == "replacement@example.com"
|