mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 11:22:21 +00:00
feat: Add sync error and account expiry notifications.
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
committed by
Elisiário Couto
parent
5de9badfde
commit
1a2ec45f89
252
tests/unit/test_sync_notifications.py
Normal file
252
tests/unit/test_sync_notifications.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""Tests for sync service notification functionality."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from leggen.services.sync_service import SyncService
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestSyncNotifications:
|
||||
"""Test sync service notification functionality."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sync_failure_sends_notification(self):
|
||||
"""Test that sync failures trigger notifications."""
|
||||
sync_service = SyncService()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_requisitions"
|
||||
) as mock_get_requisitions,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_details"
|
||||
) as mock_get_details,
|
||||
patch.object(
|
||||
sync_service.notifications, "send_sync_failure_notification"
|
||||
) as mock_send_notification,
|
||||
patch.object(
|
||||
sync_service.database, "persist_sync_operation", return_value=1
|
||||
),
|
||||
):
|
||||
# Setup: One requisition with one account that will fail
|
||||
mock_get_requisitions.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": "req-123",
|
||||
"institution_id": "TEST_BANK",
|
||||
"status": "LN",
|
||||
"accounts": ["account-1"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Make account details fail
|
||||
mock_get_details.side_effect = Exception("API Error")
|
||||
|
||||
# Execute: Run sync which should fail for the account
|
||||
await sync_service.sync_all_accounts()
|
||||
|
||||
# Assert: Notification should be sent for the failed account
|
||||
mock_send_notification.assert_called_once()
|
||||
call_args = mock_send_notification.call_args[0][0]
|
||||
assert call_args["account_id"] == "account-1"
|
||||
assert "API Error" in call_args["error"]
|
||||
assert call_args["type"] == "account_sync_failure"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_expired_requisition_sends_notification(self):
|
||||
"""Test that expired requisitions trigger expiry notifications."""
|
||||
sync_service = SyncService()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_requisitions"
|
||||
) as mock_get_requisitions,
|
||||
patch.object(
|
||||
sync_service.notifications, "send_expiry_notification"
|
||||
) as mock_send_expiry,
|
||||
patch.object(
|
||||
sync_service.database, "persist_sync_operation", return_value=1
|
||||
),
|
||||
):
|
||||
# Setup: One expired requisition
|
||||
mock_get_requisitions.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": "req-expired",
|
||||
"institution_id": "EXPIRED_BANK",
|
||||
"status": "EX",
|
||||
"accounts": [],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Execute: Run sync
|
||||
await sync_service.sync_all_accounts()
|
||||
|
||||
# Assert: Expiry notification should be sent
|
||||
mock_send_expiry.assert_called_once()
|
||||
call_args = mock_send_expiry.call_args[0][0]
|
||||
assert call_args["requisition_id"] == "req-expired"
|
||||
assert call_args["bank"] == "EXPIRED_BANK"
|
||||
assert call_args["status"] == "expired"
|
||||
assert call_args["days_left"] == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_multiple_failures_send_multiple_notifications(self):
|
||||
"""Test that multiple account failures send multiple notifications."""
|
||||
sync_service = SyncService()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_requisitions"
|
||||
) as mock_get_requisitions,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_details"
|
||||
) as mock_get_details,
|
||||
patch.object(
|
||||
sync_service.notifications, "send_sync_failure_notification"
|
||||
) as mock_send_notification,
|
||||
patch.object(
|
||||
sync_service.database, "persist_sync_operation", return_value=1
|
||||
),
|
||||
):
|
||||
# Setup: One requisition with two accounts that will fail
|
||||
mock_get_requisitions.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": "req-123",
|
||||
"institution_id": "TEST_BANK",
|
||||
"status": "LN",
|
||||
"accounts": ["account-1", "account-2"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Make all account details fail
|
||||
mock_get_details.side_effect = Exception("API Error")
|
||||
|
||||
# Execute: Run sync
|
||||
await sync_service.sync_all_accounts()
|
||||
|
||||
# Assert: Two notifications should be sent
|
||||
assert mock_send_notification.call_count == 2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_successful_sync_no_failure_notification(self):
|
||||
"""Test that successful syncs don't send failure notifications."""
|
||||
sync_service = SyncService()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_requisitions"
|
||||
) as mock_get_requisitions,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_details"
|
||||
) as mock_get_details,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_balances"
|
||||
) as mock_get_balances,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_transactions"
|
||||
) as mock_get_transactions,
|
||||
patch.object(
|
||||
sync_service.notifications, "send_sync_failure_notification"
|
||||
) as mock_send_notification,
|
||||
patch.object(sync_service.notifications, "send_transaction_notifications"),
|
||||
patch.object(sync_service.database, "persist_account_details"),
|
||||
patch.object(sync_service.database, "persist_balance"),
|
||||
patch.object(
|
||||
sync_service.database, "process_transactions", return_value=[]
|
||||
),
|
||||
patch.object(
|
||||
sync_service.database, "persist_transactions", return_value=[]
|
||||
),
|
||||
patch.object(
|
||||
sync_service.database, "persist_sync_operation", return_value=1
|
||||
),
|
||||
):
|
||||
# Setup: One requisition with one account that succeeds
|
||||
mock_get_requisitions.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": "req-123",
|
||||
"institution_id": "TEST_BANK",
|
||||
"status": "LN",
|
||||
"accounts": ["account-1"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
mock_get_details.return_value = {
|
||||
"id": "account-1",
|
||||
"institution_id": "TEST_BANK",
|
||||
"status": "READY",
|
||||
"iban": "TEST123",
|
||||
}
|
||||
|
||||
mock_get_balances.return_value = {
|
||||
"balances": [{"balanceAmount": {"amount": "100", "currency": "EUR"}}]
|
||||
}
|
||||
|
||||
mock_get_transactions.return_value = {"transactions": {"booked": []}}
|
||||
|
||||
# Execute: Run sync
|
||||
await sync_service.sync_all_accounts()
|
||||
|
||||
# Assert: No failure notification should be sent
|
||||
mock_send_notification.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notification_failure_does_not_stop_sync(self):
|
||||
"""Test that notification failures don't stop the sync process."""
|
||||
sync_service = SyncService()
|
||||
|
||||
# Mock the dependencies
|
||||
with (
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_requisitions"
|
||||
) as mock_get_requisitions,
|
||||
patch.object(
|
||||
sync_service.gocardless, "get_account_details"
|
||||
) as mock_get_details,
|
||||
patch.object(
|
||||
sync_service.notifications, "send_sync_failure_notification"
|
||||
) as mock_send_notification,
|
||||
patch.object(
|
||||
sync_service.database, "persist_sync_operation", return_value=1
|
||||
),
|
||||
):
|
||||
# Setup: One requisition with one account that will fail
|
||||
mock_get_requisitions.return_value = {
|
||||
"results": [
|
||||
{
|
||||
"id": "req-123",
|
||||
"institution_id": "TEST_BANK",
|
||||
"status": "LN",
|
||||
"accounts": ["account-1"],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Make account details fail
|
||||
mock_get_details.side_effect = Exception("API Error")
|
||||
|
||||
# Make notification sending fail
|
||||
mock_send_notification.side_effect = Exception("Notification Error")
|
||||
|
||||
# Execute: Run sync - should not raise exception from notification
|
||||
try:
|
||||
result = await sync_service.sync_all_accounts()
|
||||
# The sync should complete with errors but not crash
|
||||
assert result.success is False
|
||||
assert len(result.errors) > 0
|
||||
except Exception as e:
|
||||
# If exception is raised, it should not be the notification error
|
||||
assert "Notification Error" not in str(e)
|
||||
Reference in New Issue
Block a user