Files
skills/aws-email-check/tests/test_replay.py
T

242 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Offline tests for explicit recovery of the last generated report."""
from contextlib import redirect_stdout
from email.message import EmailMessage
from io import StringIO
import json
from pathlib import Path
import sys
from tempfile import TemporaryDirectory
import unittest
from unittest.mock import patch
from zoneinfo import ZoneInfo
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
import check_mail
class FakeIMAP:
def __init__(self, message, uid=b"42"):
self.message = message
self.uid_value = uid
self.fetches = 0
def __enter__(self):
return self
def __exit__(self, *_args):
pass
def login(self, *_args):
return "OK", [b"logged in"]
def select(self, *_args, **_kwargs):
return "OK", [b"1"]
def response(self, name):
assert name == "UIDVALIDITY"
return "OK", [b"123"]
def uid(self, command, *_args):
if command == "SEARCH":
return "OK", [self.uid_value]
if command == "FETCH":
self.fetches += 1
return "OK", [
(b'1 (UID 42 INTERNALDATE "28-Sep-2026 10:00:00 +0800" BODY[] {1}', self.message),
b")",
]
raise AssertionError(command)
class ReplayTests(unittest.TestCase):
def setUp(self):
self.temp = TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.config = self.root / "config.json"
self.state = self.root / "state" / "processed.json"
self.config.write_text(json.dumps({
"imap": {
"host": "imap.example.com",
"username": "reader@example.com",
"authorization_code": "test-only",
},
"timezone": "Asia/Shanghai",
"state_file": "state/processed.json",
}), encoding="utf-8")
mail = EmailMessage()
mail["Subject"] = "AWS Account on Hold: Response Required"
mail["Message-ID"] = "<report-42@example.com>"
mail.set_content("AWS Account ID: 123456789012\nYour account has been placed on hold.")
self.fake = FakeIMAP(mail.as_bytes())
# Keep the fake INTERNALDATE within the test's day without changing production filtering.
window = check_mail.day_window(ZoneInfo("Asia/Shanghai"), "2026-09-28")
fixed_day = patch.object(check_mail, "day_window", return_value=window)
fixed_day.start()
self.addCleanup(fixed_day.stop)
def run_main(self, *args):
output = StringIO()
with redirect_stdout(output):
check_mail.main(["--config", str(self.config), *args])
return output.getvalue()
def test_dedup_and_explicit_replay_without_connection(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=self.fake):
first = self.run_main()
second = self.run_main()
self.assertIn("【AWS账号风控邮件】", first)
self.assertIn("AWS账号ID:123456789012", first)
self.assertEqual(second, check_mail.NO_NEW + "\n")
self.assertEqual(self.fake.fetches, 1)
before = self.state.read_bytes()
with patch.object(check_mail.imaplib, "IMAP4_SSL", side_effect=AssertionError("unexpected connection")):
replay = self.run_main("--replay-last")
self.assertEqual(replay, first)
self.assertEqual(self.state.read_bytes(), before)
self.assertEqual(json.loads(before)["last_report"], first.removesuffix("\n"))
def test_empty_scan_does_not_replace_previous_report(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=self.fake):
first = self.run_main()
empty = FakeIMAP(self.fake.message, uid=b"")
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=empty):
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
self.assertEqual(self.run_main("--replay-last"), first)
def test_older_batch_survives_next_report_and_empty_scan(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=self.fake):
first = self.run_main()
newer_mail = EmailMessage()
newer_mail["Subject"] = "AWS Account on Hold: Second Notice"
newer_mail["Message-ID"] = "<report-43@example.com>"
newer_mail.set_content("AWS Account ID: 999999999999\nYour account has been placed on hold.")
newer = FakeIMAP(newer_mail.as_bytes(), uid=b"43")
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=newer):
second = self.run_main()
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
state_before = self.state.read_bytes()
with patch.object(check_mail.imaplib, "IMAP4_SSL", side_effect=AssertionError("unexpected connection")):
ids = self.run_main("--list-report-batches").splitlines()
self.assertEqual(len(ids), 2)
self.assertEqual(self.run_main("--replay-id", ids[1]), first)
self.assertEqual(self.run_main("--replay-id", ids[0]), second)
self.assertEqual(self.run_main("--replay-last"), second)
self.assertEqual(self.state.read_bytes(), state_before)
def test_history_is_bounded(self):
state = {"mailboxes": {}}
for number in range(check_mail.REPORT_HISTORY_LIMIT + 4):
check_mail.save_report_batch(state, f"report {number}")
history = check_mail.report_history(state)
self.assertEqual(len(history), check_mail.REPORT_HISTORY_LIMIT)
self.assertEqual(history[0]["report"], "report 4")
self.assertEqual(history[-1]["report"], f"report {check_mail.REPORT_HISTORY_LIMIT + 3}")
self.assertEqual(len({item["id"] for item in history}), len(history))
def test_legacy_last_report_remains_replayable(self):
self.state.parent.mkdir(parents=True, exist_ok=True)
self.state.write_text(json.dumps({"mailboxes": {}, "last_report": "旧报告"}), encoding="utf-8")
with patch.object(check_mail.imaplib, "IMAP4_SSL", side_effect=AssertionError("unexpected connection")):
self.assertEqual(self.run_main("--list-report-batches"), "legacy\n")
self.assertEqual(self.run_main("--replay-id", "legacy"), "旧报告\n")
self.assertEqual(self.run_main("--replay-last"), "旧报告\n")
def test_missing_batch_and_invalid_history_are_clear_errors(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", side_effect=AssertionError("unexpected connection")):
with self.assertRaisesRegex(RuntimeError, "报告批次 ID 不存在"):
self.run_main("--replay-id", "missing")
self.state.parent.mkdir(parents=True, exist_ok=True)
self.state.write_text('{"mailboxes": {}, "report_history": [42]}', encoding="utf-8")
with self.assertRaisesRegex(ValueError, "报告历史格式无效"):
self.run_main("--list-report-batches")
def test_no_saved_report_has_clear_error(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", side_effect=AssertionError("unexpected connection")):
with self.assertRaisesRegex(RuntimeError, "没有可重放的上次报告"):
self.run_main("--replay-last")
self.state.parent.mkdir(parents=True, exist_ok=True)
self.state.write_text('{"mailboxes": {}}', encoding="utf-8")
with self.assertRaisesRegex(RuntimeError, "没有可重放的上次报告"):
self.run_main("--replay-last")
def test_local_eml_mode_does_not_write_state(self):
folder = self.root / "samples"
folder.mkdir()
mail = EmailMessage()
mail["Subject"] = "AWS Account on Hold: Response Required"
mail["Date"] = "Mon, 28 Sep 2026 10:00:00 +0800"
mail.set_content("AWS Account ID: 123456789012\nYour account has been placed on hold.")
(folder / "sample.eml").write_bytes(mail.as_bytes())
self.assertIn("【AWS账号风控邮件】", self.run_main("--eml-dir", str(folder), "--date", "2026-09-28"))
self.assertFalse(self.state.exists())
def test_filtered_code_is_deduplicated_without_report_history(self):
mail = EmailMessage()
mail["Subject"] = "AWS 验证您的身份"
mail["Message-ID"] = "<code-42@example.com>"
mail.set_content("AWS 验证码 123456,用于登录。如果发现异常活动,请启用 MFA。")
fake = FakeIMAP(mail.as_bytes())
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=fake):
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
self.assertEqual(fake.fetches, 1)
state = json.loads(self.state.read_text(encoding="utf-8"))
entry = next(iter(state["mailboxes"].values()))
self.assertEqual(entry["uids"], ["42"])
self.assertEqual(entry["message_ids"], ["<code-42@example.com>"])
self.assertNotIn("report_history", state)
self.assertNotIn("last_report", state)
def test_filtered_code_preserves_previous_report_batch(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=self.fake):
first = self.run_main()
before = json.loads(self.state.read_text(encoding="utf-8"))
mail = EmailMessage()
mail["Subject"] = "AWS verification code"
mail["Message-ID"] = "<code-43@example.com>"
mail.set_content("Verification code: 123456. Use this code to sign in.")
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=FakeIMAP(mail.as_bytes(), uid=b"43")):
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
after = json.loads(self.state.read_text(encoding="utf-8"))
self.assertEqual(after["report_history"], before["report_history"])
self.assertEqual(self.run_main("--replay-last"), first)
def test_filtered_access_validated_notice_is_deduplicated_without_history(self):
mail = EmailMessage()
mail["Subject"] = "Fw: Your Request For Accessing AWS Resources Has Been Validated"
mail["Message-ID"] = "<validated-42@example.com>"
mail.set_content("Your AWS resource-access request has completed verification.")
fake = FakeIMAP(mail.as_bytes())
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=fake):
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
self.assertEqual(fake.fetches, 1)
state = json.loads(self.state.read_text(encoding="utf-8"))
entry = next(iter(state["mailboxes"].values()))
self.assertEqual(entry["uids"], ["42"])
self.assertEqual(entry["message_ids"], ["<validated-42@example.com>"])
self.assertNotIn("report_history", state)
self.assertNotIn("last_report", state)
def test_filtered_access_validated_notice_preserves_previous_report(self):
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=self.fake):
first = self.run_main()
before = json.loads(self.state.read_text(encoding="utf-8"))
mail = EmailMessage()
mail["Subject"] = "Fw: 您访问 AWS 资源的请求已得到验证"
mail["Message-ID"] = "<validated-43@example.com>"
mail.set_content("<p>现在可以访问 AWS 资源。</p>", subtype="html")
with patch.object(check_mail.imaplib, "IMAP4_SSL", return_value=FakeIMAP(mail.as_bytes(), uid=b"43")):
self.assertEqual(self.run_main(), check_mail.NO_NEW + "\n")
after = json.loads(self.state.read_text(encoding="utf-8"))
self.assertEqual(after["report_history"], before["report_history"])
self.assertEqual(after["last_report"], before["last_report"])
self.assertEqual(self.run_main("--replay-last"), first)
if __name__ == "__main__":
unittest.main()