mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 11:22:21 +00:00
fix: Address code review feedback on notification error handling.
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
committed by
Elisiário Couto
parent
d58894d07c
commit
88037f328d
@@ -61,17 +61,13 @@ def send_sync_failure_notification(ctx: click.Context, notification: dict):
|
|||||||
info("Sending sync failure notification to Discord")
|
info("Sending sync failure notification to Discord")
|
||||||
webhook = DiscordWebhook(url=ctx.obj["notifications"]["discord"]["webhook"])
|
webhook = DiscordWebhook(url=ctx.obj["notifications"]["discord"]["webhook"])
|
||||||
|
|
||||||
# Determine color and title based on failure type
|
color = "ffaa00" # Orange for sync failure
|
||||||
if notification.get("type") == "sync_final_failure":
|
title = "⚠️ Sync Failure"
|
||||||
color = "ff0000" # Red for final failure
|
|
||||||
title = "🚨 Sync Final Failure"
|
# Build description with account info if available
|
||||||
description = (
|
description = "Account sync failed"
|
||||||
f"Sync failed permanently after {notification['retry_count']} attempts"
|
if notification.get("account_id"):
|
||||||
)
|
description = f"Account {notification['account_id']} sync failed"
|
||||||
else:
|
|
||||||
color = "ffaa00" # Orange for retry
|
|
||||||
title = "⚠️ Sync Failure"
|
|
||||||
description = f"Sync failed (attempt {notification['retry_count']}/{notification['max_retries']}). Will retry automatically..."
|
|
||||||
|
|
||||||
embed = DiscordEmbed(
|
embed = DiscordEmbed(
|
||||||
title=title,
|
title=title,
|
||||||
|
|||||||
@@ -87,19 +87,14 @@ def send_sync_failure_notification(ctx: click.Context, notification: dict):
|
|||||||
bot_url = f"https://api.telegram.org/bot{token}/sendMessage"
|
bot_url = f"https://api.telegram.org/bot{token}/sendMessage"
|
||||||
info("Sending sync failure notification to Telegram")
|
info("Sending sync failure notification to Telegram")
|
||||||
|
|
||||||
message = "*🚨 [Leggen](https://github.com/elisiariocouto/leggen)*\n"
|
message = "*⚠️ [Leggen](https://github.com/elisiariocouto/leggen)*\n"
|
||||||
message += "*Sync Failed*\n\n"
|
message += "*Sync Failed*\n\n"
|
||||||
message += escape_markdown(f"Error: {notification['error']}\n")
|
|
||||||
|
|
||||||
if notification.get("type") == "sync_final_failure":
|
# Add account info if available
|
||||||
message += escape_markdown(
|
if notification.get("account_id"):
|
||||||
f"❌ Final failure after {notification['retry_count']} attempts\n"
|
message += escape_markdown(f"Account: {notification['account_id']}\n")
|
||||||
)
|
|
||||||
else:
|
message += escape_markdown(f"Error: {notification['error']}\n")
|
||||||
message += escape_markdown(
|
|
||||||
f"🔄 Attempt {notification['retry_count']}/{notification['max_retries']}\n"
|
|
||||||
)
|
|
||||||
message += escape_markdown("Will retry automatically...\n")
|
|
||||||
|
|
||||||
res = requests.post(
|
res = requests.post(
|
||||||
bot_url,
|
bot_url,
|
||||||
|
|||||||
@@ -52,11 +52,17 @@ class NotificationService:
|
|||||||
|
|
||||||
async def send_expiry_notification(self, notification_data: Dict[str, Any]) -> None:
|
async def send_expiry_notification(self, notification_data: Dict[str, Any]) -> None:
|
||||||
"""Send notification about account expiry"""
|
"""Send notification about account expiry"""
|
||||||
if self._is_discord_enabled():
|
try:
|
||||||
await self._send_discord_expiry(notification_data)
|
if self._is_discord_enabled():
|
||||||
|
await self._send_discord_expiry(notification_data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send Discord expiry notification: {e}")
|
||||||
|
|
||||||
if self._is_telegram_enabled():
|
try:
|
||||||
await self._send_telegram_expiry(notification_data)
|
if self._is_telegram_enabled():
|
||||||
|
await self._send_telegram_expiry(notification_data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send Telegram expiry notification: {e}")
|
||||||
|
|
||||||
def _filter_transactions(
|
def _filter_transactions(
|
||||||
self, transactions: List[Dict[str, Any]]
|
self, transactions: List[Dict[str, Any]]
|
||||||
@@ -262,7 +268,6 @@ class NotificationService:
|
|||||||
logger.info(f"Sent Discord expiry notification: {notification_data}")
|
logger.info(f"Sent Discord expiry notification: {notification_data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send Discord expiry notification: {e}")
|
logger.error(f"Failed to send Discord expiry notification: {e}")
|
||||||
raise
|
|
||||||
|
|
||||||
async def _send_telegram_expiry(self, notification_data: Dict[str, Any]) -> None:
|
async def _send_telegram_expiry(self, notification_data: Dict[str, Any]) -> None:
|
||||||
"""Send Telegram expiry notification"""
|
"""Send Telegram expiry notification"""
|
||||||
@@ -288,17 +293,22 @@ class NotificationService:
|
|||||||
logger.info(f"Sent Telegram expiry notification: {notification_data}")
|
logger.info(f"Sent Telegram expiry notification: {notification_data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send Telegram expiry notification: {e}")
|
logger.error(f"Failed to send Telegram expiry notification: {e}")
|
||||||
raise
|
|
||||||
|
|
||||||
async def send_sync_failure_notification(
|
async def send_sync_failure_notification(
|
||||||
self, notification_data: Dict[str, Any]
|
self, notification_data: Dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Send notification about sync failure"""
|
"""Send notification about sync failure"""
|
||||||
if self._is_discord_enabled():
|
try:
|
||||||
await self._send_discord_sync_failure(notification_data)
|
if self._is_discord_enabled():
|
||||||
|
await self._send_discord_sync_failure(notification_data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send Discord sync failure notification: {e}")
|
||||||
|
|
||||||
if self._is_telegram_enabled():
|
try:
|
||||||
await self._send_telegram_sync_failure(notification_data)
|
if self._is_telegram_enabled():
|
||||||
|
await self._send_telegram_sync_failure(notification_data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send Telegram sync failure notification: {e}")
|
||||||
|
|
||||||
async def _send_discord_sync_failure(
|
async def _send_discord_sync_failure(
|
||||||
self, notification_data: Dict[str, Any]
|
self, notification_data: Dict[str, Any]
|
||||||
@@ -326,7 +336,6 @@ class NotificationService:
|
|||||||
logger.info(f"Sent Discord sync failure notification: {notification_data}")
|
logger.info(f"Sent Discord sync failure notification: {notification_data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send Discord sync failure notification: {e}")
|
logger.error(f"Failed to send Discord sync failure notification: {e}")
|
||||||
raise
|
|
||||||
|
|
||||||
async def _send_telegram_sync_failure(
|
async def _send_telegram_sync_failure(
|
||||||
self, notification_data: Dict[str, Any]
|
self, notification_data: Dict[str, Any]
|
||||||
@@ -354,4 +363,3 @@ class NotificationService:
|
|||||||
logger.info(f"Sent Telegram sync failure notification: {notification_data}")
|
logger.info(f"Sent Telegram sync failure notification: {notification_data}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to send Telegram sync failure notification: {e}")
|
logger.error(f"Failed to send Telegram sync failure notification: {e}")
|
||||||
raise
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ from leggen.services.notification_service import NotificationService
|
|||||||
|
|
||||||
# Constants for notification
|
# Constants for notification
|
||||||
EXPIRED_DAYS_LEFT = 0
|
EXPIRED_DAYS_LEFT = 0
|
||||||
ACCOUNT_SYNC_RETRY_COUNT = 1
|
|
||||||
ACCOUNT_SYNC_MAX_RETRIES = 1
|
|
||||||
|
|
||||||
|
|
||||||
class SyncService:
|
class SyncService:
|
||||||
@@ -175,20 +173,13 @@ class SyncService:
|
|||||||
logs.append(error_msg)
|
logs.append(error_msg)
|
||||||
|
|
||||||
# Send notification for account sync failure
|
# Send notification for account sync failure
|
||||||
try:
|
await self.notifications.send_sync_failure_notification(
|
||||||
await self.notifications.send_sync_failure_notification(
|
{
|
||||||
{
|
"account_id": account_id,
|
||||||
"account_id": account_id,
|
"error": error_msg,
|
||||||
"error": error_msg,
|
"type": "account_sync_failure",
|
||||||
"type": "account_sync_failure",
|
}
|
||||||
"retry_count": ACCOUNT_SYNC_RETRY_COUNT,
|
)
|
||||||
"max_retries": ACCOUNT_SYNC_MAX_RETRIES,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except Exception as notif_error:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to send sync failure notification: {notif_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
end_time = datetime.now()
|
end_time = datetime.now()
|
||||||
duration = (end_time - start_time).total_seconds()
|
duration = (end_time - start_time).total_seconds()
|
||||||
@@ -277,7 +268,11 @@ class SyncService:
|
|||||||
self._sync_status.is_running = False
|
self._sync_status.is_running = False
|
||||||
|
|
||||||
async def _check_requisition_expiry(self, requisitions: List[dict]) -> None:
|
async def _check_requisition_expiry(self, requisitions: List[dict]) -> None:
|
||||||
"""Check requisitions for expiry and send notifications"""
|
"""Check requisitions for expiry and send notifications.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
requisitions: List of requisition dictionaries to check
|
||||||
|
"""
|
||||||
for req in requisitions:
|
for req in requisitions:
|
||||||
requisition_id = req.get("id", "unknown")
|
requisition_id = req.get("id", "unknown")
|
||||||
institution_id = req.get("institution_id", "unknown")
|
institution_id = req.get("institution_id", "unknown")
|
||||||
@@ -288,17 +283,14 @@ class SyncService:
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
f"Requisition {requisition_id} for {institution_id} has expired"
|
f"Requisition {requisition_id} for {institution_id} has expired"
|
||||||
)
|
)
|
||||||
try:
|
await self.notifications.send_expiry_notification(
|
||||||
await self.notifications.send_expiry_notification(
|
{
|
||||||
{
|
"bank": institution_id,
|
||||||
"bank": institution_id,
|
"requisition_id": requisition_id,
|
||||||
"requisition_id": requisition_id,
|
"status": "expired",
|
||||||
"status": "expired",
|
"days_left": EXPIRED_DAYS_LEFT,
|
||||||
"days_left": EXPIRED_DAYS_LEFT,
|
}
|
||||||
}
|
)
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Failed to send expiry notification: {e}")
|
|
||||||
|
|
||||||
async def sync_specific_accounts(
|
async def sync_specific_accounts(
|
||||||
self, account_ids: List[str], force: bool = False, trigger_type: str = "manual"
|
self, account_ids: List[str], force: bool = False, trigger_type: str = "manual"
|
||||||
|
|||||||
@@ -217,8 +217,11 @@ class TestSyncNotifications:
|
|||||||
sync_service.gocardless, "get_account_details"
|
sync_service.gocardless, "get_account_details"
|
||||||
) as mock_get_details,
|
) as mock_get_details,
|
||||||
patch.object(
|
patch.object(
|
||||||
sync_service.notifications, "send_sync_failure_notification"
|
sync_service.notifications, "_send_discord_sync_failure"
|
||||||
) as mock_send_notification,
|
) as mock_discord_notification,
|
||||||
|
patch.object(
|
||||||
|
sync_service.notifications, "_send_telegram_sync_failure"
|
||||||
|
) as mock_telegram_notification,
|
||||||
patch.object(
|
patch.object(
|
||||||
sync_service.database, "persist_sync_operation", return_value=1
|
sync_service.database, "persist_sync_operation", return_value=1
|
||||||
),
|
),
|
||||||
@@ -238,15 +241,14 @@ class TestSyncNotifications:
|
|||||||
# Make account details fail
|
# Make account details fail
|
||||||
mock_get_details.side_effect = Exception("API Error")
|
mock_get_details.side_effect = Exception("API Error")
|
||||||
|
|
||||||
# Make notification sending fail
|
# Make both notification methods fail
|
||||||
mock_send_notification.side_effect = Exception("Notification Error")
|
mock_discord_notification.side_effect = Exception("Discord Error")
|
||||||
|
mock_telegram_notification.side_effect = Exception("Telegram Error")
|
||||||
|
|
||||||
# Execute: Run sync - should not raise exception from notification
|
# Execute: Run sync - should not raise exception from notification
|
||||||
try:
|
result = await sync_service.sync_all_accounts()
|
||||||
result = await sync_service.sync_all_accounts()
|
|
||||||
# The sync should complete with errors but not crash
|
# The sync should complete with errors but not crash from notifications
|
||||||
assert result.success is False
|
assert result.success is False
|
||||||
assert len(result.errors) > 0
|
assert len(result.errors) > 0
|
||||||
except Exception as e:
|
assert "API Error" in result.errors[0]
|
||||||
# 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