Files
Tirsvad 07b46f0180 MIL-003 e-mail alert (Step 2)
Delivers MIL-003 (lecture Step 2): an alert e-mail with title, price and buy link is sent over SMTP (port 587, STARTTLS) when the price is below the target 100. SMTP address, e-mail address and password are read from `.env` with python-dotenv (`.env.example` lists them). Tests use fakes and never open a network or SMTP connection.

Review: code against QC-PY-001 as RC-012 (Go, autonomous, not independent). 33 pytest tests, ruff and mypy --strict pass. The Step 2 screenshots come from a run with a stubbed SMTP connection; no real mail was sent.

Task: MIL-003#1
Task: MIL-003#2
Task: MIL-003#3
Task: MIL-003#4
Task: MIL-003#5
2026-10-28 14:44:18 +08:00

79 lines
2.0 KiB
Python

from amazon_price_tracker.application.alert import (
PriceCheck,
build_message,
check_price,
)
from amazon_price_tracker.domain.alert_message import AlertMessage
from amazon_price_tracker.domain.product import Product
URL = "https://example.test/p"
class FakeFetcher:
def __init__(self) -> None:
self.urls: list[str] = []
def fetch(self, url: str) -> str:
self.urls.append(url)
return "<html></html>"
class FakeParser:
def __init__(self, product: Product) -> None:
self.product = product
def parse(self, html: str) -> Product:
return self.product
class FakeNotifier:
def __init__(self) -> None:
self.sent: list[AlertMessage] = []
def send(self, message: AlertMessage) -> None:
self.sent.append(message)
def _run(price: float) -> tuple[PriceCheck, FakeNotifier, FakeFetcher]:
notifier = FakeNotifier()
fetcher = FakeFetcher()
result = check_price(
fetcher, FakeParser(Product("Pot", price)), notifier, URL, 100.0
)
return result, notifier, fetcher
def test_build_message_contains_title_price_and_link() -> None:
message = build_message("Instant Pot", 79.5, URL)
assert message.subject == "Amazon Price Alert!"
assert "Instant Pot" in message.body
assert "$79.50" in message.body
assert URL in message.body
def test_check_price_sends_once_when_below_target() -> None:
result, notifier, fetcher = _run(99.99)
assert result.alert_sent
assert result.product == Product("Pot", 99.99)
assert fetcher.urls == [URL]
assert len(notifier.sent) == 1
assert "$99.99" in notifier.sent[0].body
def test_check_price_does_not_send_when_equal_to_target() -> None:
result, notifier, fetcher = _run(100.0)
assert not result.alert_sent
assert fetcher.urls == [URL]
assert notifier.sent == []
def test_check_price_does_not_send_when_above_target() -> None:
result, notifier, fetcher = _run(150.0)
assert not result.alert_sent
assert fetcher.urls == [URL]
assert notifier.sent == []