mail notification

This commit is contained in:
Bastian Wagner
2026-08-15 20:47:02 +02:00
parent 7c9e19ba0b
commit 2aba1265af
16 changed files with 519 additions and 2 deletions

View File

@@ -28,3 +28,11 @@ class FakeGarminUploader:
if self.error is not None:
raise self.error
return self.result
class FakeNotifier:
def __init__(self) -> None:
self.sent: list[dict] = []
def send(self, *, to_address: str, subject: str, body: str) -> None:
self.sent.append({"to_address": to_address, "subject": subject, "body": body})

View File

@@ -10,7 +10,7 @@ from app.mywhoosh.client import MyWhooshDeviceConflictError
from app.mywhoosh.models import MyWhooshActivity
from app.sync.manager import SyncManager
from tests.sync.conftest import FakeFitConverter, _create_user
from tests.sync.fakes import FakeGarminUploader, FakeMyWhooshClient
from tests.sync.fakes import FakeGarminUploader, FakeMyWhooshClient, FakeNotifier
@pytest.mark.asyncio
@@ -229,6 +229,82 @@ async def test_device_conflict_sets_distinct_action_reason(seeded_user: SyncUser
assert reloaded.health_state == HealthState.ACTION_REQUIRED
@pytest.mark.asyncio
async def test_notifies_on_new_action_required_when_opted_in(session_factory, cipher, settings) -> None:
with session_factory() as session:
user = _create_user(session, cipher)
user.notify_email_enabled = True
user.notification_email = "alerts@example.com"
session.commit()
user_id = user.id
notifier = FakeNotifier()
manager = SyncManager(
session_factory=session_factory,
credential_cipher=cipher,
settings=settings,
mywhoosh_factory=lambda token_store: DeviceConflictMyWhooshClient(),
garmin_factory=lambda email, password, tokenstore: FakeGarminUploader(),
fit_converter=FakeFitConverter(),
notifier=notifier,
)
await manager.sync_user(user_id)
assert len(notifier.sent) == 1
assert notifier.sent[0]["to_address"] == "alerts@example.com"
assert "another device" in notifier.sent[0]["body"]
@pytest.mark.asyncio
async def test_does_not_notify_when_not_opted_in(session_factory, cipher, settings) -> None:
user_id = None
with session_factory() as session:
user = _create_user(session, cipher)
user_id = user.id
notifier = FakeNotifier()
manager = SyncManager(
session_factory=session_factory,
credential_cipher=cipher,
settings=settings,
mywhoosh_factory=lambda token_store: DeviceConflictMyWhooshClient(),
garmin_factory=lambda email, password, tokenstore: FakeGarminUploader(),
fit_converter=FakeFitConverter(),
notifier=notifier,
)
await manager.sync_user(user_id)
assert notifier.sent == []
@pytest.mark.asyncio
async def test_does_not_renotify_for_unresolved_unchanged_reason(session_factory, cipher, settings) -> None:
with session_factory() as session:
user = _create_user(session, cipher)
user.notify_email_enabled = True
user.notification_email = "alerts@example.com"
session.commit()
user_id = user.id
notifier = FakeNotifier()
manager = SyncManager(
session_factory=session_factory,
credential_cipher=cipher,
settings=settings,
mywhoosh_factory=lambda token_store: DeviceConflictMyWhooshClient(),
garmin_factory=lambda email, password, tokenstore: FakeGarminUploader(),
fit_converter=FakeFitConverter(),
notifier=notifier,
)
await manager.sync_user(user_id)
await manager.sync_user(user_id)
assert len(notifier.sent) == 1
@pytest.mark.asyncio
async def test_mfa_code_reaches_real_garmin_uploader_via_sync_manager(
session_factory, cipher, settings, seeded_user: SyncUser