157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
import os
|
|
import unittest
|
|
from unittest.mock import Mock, patch
|
|
|
|
from app import EmailMessage, EmailMonitor
|
|
|
|
|
|
EMAIL_ENV = {
|
|
"EMAIL_USER": "user@example.com",
|
|
"EMAIL_PASSWORD": "password",
|
|
}
|
|
|
|
|
|
class EmailMonitorConfigTests(unittest.TestCase):
|
|
@patch.dict(
|
|
os.environ,
|
|
{**EMAIL_ENV, "DISCORD_WEBHOOK_URL": "https://discord.example/webhook"},
|
|
clear=True,
|
|
)
|
|
def test_discord_only_configuration_remains_supported(self):
|
|
monitor = EmailMonitor()
|
|
|
|
self.assertEqual(monitor.discord_webhook_url, "https://discord.example/webhook")
|
|
self.assertIsNone(monitor.webhook_url)
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{
|
|
**EMAIL_ENV,
|
|
"WEBHOOK_URL": "https://agents.example/webhook",
|
|
"WEBHOOK_TOKEN": "123:signed-token",
|
|
"DISCORD_CHANNEL_ID": "456",
|
|
},
|
|
clear=True,
|
|
)
|
|
def test_agents_webhook_only_configuration_is_supported(self):
|
|
monitor = EmailMonitor()
|
|
|
|
self.assertIsNone(monitor.discord_webhook_url)
|
|
self.assertEqual(monitor.discord_channel_id, 456)
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{**EMAIL_ENV, "WEBHOOK_URL": "https://agents.example/webhook"},
|
|
clear=True,
|
|
)
|
|
def test_partial_agents_webhook_configuration_is_rejected(self):
|
|
with self.assertRaises(SystemExit):
|
|
EmailMonitor()
|
|
|
|
|
|
class EmailMonitorNotificationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.email = EmailMessage(
|
|
subject="テスト件名",
|
|
sender="sender@example.com",
|
|
date="Fri, 18 Jul 2026 12:00:00 +0900",
|
|
body="メール本文",
|
|
uid="42",
|
|
)
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{**EMAIL_ENV, "DISCORD_WEBHOOK_URL": "https://discord.example/webhook"},
|
|
clear=True,
|
|
)
|
|
@patch("app.requests.post")
|
|
def test_existing_discord_delivery_remains_supported(self, post):
|
|
post.return_value = Mock(status_code=204, text="")
|
|
monitor = EmailMonitor()
|
|
|
|
self.assertTrue(monitor.send_to_discord(self.email))
|
|
|
|
post.assert_called_once()
|
|
self.assertEqual(post.call_args.args[0], "https://discord.example/webhook")
|
|
embed = post.call_args.kwargs["json"]["embeds"][0]
|
|
self.assertEqual(embed["fields"][0]["value"], "テスト件名")
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{
|
|
**EMAIL_ENV,
|
|
"WEBHOOK_URL": "https://agents.example/webhook",
|
|
"WEBHOOK_TOKEN": "123:signed-token",
|
|
"DISCORD_CHANNEL_ID": "456",
|
|
"WEBHOOK_SOURCE": "email-satellite",
|
|
},
|
|
clear=True,
|
|
)
|
|
@patch("app.requests.post")
|
|
def test_agents_webhook_payload_matches_agents_contract(self, post):
|
|
response = Mock(status_code=202, text="Accepted")
|
|
post.return_value = response
|
|
monitor = EmailMonitor()
|
|
|
|
self.assertTrue(monitor.send_to_agents_webhook(self.email))
|
|
|
|
post.assert_called_once()
|
|
url = post.call_args.args[0]
|
|
kwargs = post.call_args.kwargs
|
|
self.assertEqual(url, "https://agents.example/webhook")
|
|
self.assertEqual(kwargs["headers"]["Authorization"], "Bearer 123:signed-token")
|
|
self.assertEqual(kwargs["json"]["source"], "email-satellite")
|
|
self.assertEqual(kwargs["json"]["channel_id"], 456)
|
|
self.assertIn("テスト件名", kwargs["json"]["prompt"])
|
|
self.assertIn("メール本文", kwargs["json"]["prompt"])
|
|
self.assertIn("信頼できない外部入力", kwargs["json"]["prompt"])
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{
|
|
**EMAIL_ENV,
|
|
"DISCORD_WEBHOOK_URL": "https://discord.example/webhook",
|
|
"WEBHOOK_URL": "https://agents.example/webhook",
|
|
"WEBHOOK_TOKEN": "123:signed-token",
|
|
"DISCORD_CHANNEL_ID": "456",
|
|
},
|
|
clear=True,
|
|
)
|
|
def test_dual_mode_requires_both_destinations_to_succeed(self):
|
|
monitor = EmailMonitor()
|
|
|
|
with patch.object(monitor, "send_to_discord", return_value=True), patch.object(
|
|
monitor, "send_to_agents_webhook", return_value=False
|
|
):
|
|
self.assertFalse(monitor.send_notifications(self.email))
|
|
|
|
with patch.object(monitor, "send_to_discord", return_value=True), patch.object(
|
|
monitor, "send_to_agents_webhook", return_value=True
|
|
):
|
|
self.assertTrue(monitor.send_notifications(self.email))
|
|
|
|
@patch.dict(
|
|
os.environ,
|
|
{**EMAIL_ENV, "DISCORD_WEBHOOK_URL": "https://discord.example/webhook"},
|
|
clear=True,
|
|
)
|
|
@patch("app.time.sleep")
|
|
def test_email_is_marked_read_only_after_notifications_succeed(self, _sleep):
|
|
monitor = EmailMonitor()
|
|
|
|
with patch.object(monitor, "send_notifications", return_value=False), patch.object(
|
|
monitor, "mark_email_as_read"
|
|
) as mark_read:
|
|
monitor.send_notifications_with_rate_limit([self.email], rate_limit=0)
|
|
mark_read.assert_not_called()
|
|
|
|
with patch.object(monitor, "send_notifications", return_value=True), patch.object(
|
|
monitor, "mark_email_as_read"
|
|
) as mark_read:
|
|
monitor.send_notifications_with_rate_limit([self.email], rate_limit=0)
|
|
mark_read.assert_called_once_with("42")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|