diff --git a/notify-service/notify-api/src/notify_api/resources/v1/notify.py b/notify-service/notify-api/src/notify_api/resources/v1/notify.py index 99c6bd0b..01f01892 100644 --- a/notify-service/notify-api/src/notify_api/resources/v1/notify.py +++ b/notify-service/notify-api/src/notify_api/resources/v1/notify.py @@ -34,8 +34,20 @@ def send_notification(body: NotificationRequest): """Create and send EMAIL notification endpoint.""" body.notify_type = Notification.NotificationType.EMAIL notification = notify.queue_publish(body) - - return jsonify(notification.json), HTTPStatus.OK + # Eagerly build response dict to avoid ObjectDeletedError if the + # delivery service processes and deletes the row before we respond. + try: + response = notification.json + except Exception: + # Fallback if the notification row was already deleted + response = { + "id": getattr(notification, "id", None), + "recipients": getattr(notification, "recipients", None), + "notifyStatus": getattr(notification.status_code, "name", None) + if hasattr(notification, "status_code") and notification.status_code + else None, + } + return jsonify(response), HTTPStatus.OK @bp.route("/", methods=["GET", "OPTIONS"]) diff --git a/notify-service/notify-api/src/notify_api/services/notify_service.py b/notify-service/notify-api/src/notify_api/services/notify_service.py index aec6cfa4..940983c9 100644 --- a/notify-service/notify-api/src/notify_api/services/notify_service.py +++ b/notify-service/notify-api/src/notify_api/services/notify_service.py @@ -28,6 +28,7 @@ NotificationRequest, SafeList, ) +from notify_api.models.db import db from notify_api.services.gcp_queue import GcpQueue, queue logger = StructuredLogging.get_logger() @@ -368,6 +369,11 @@ def _process_single_recipient( # Update notification status NotifyService._update_notification_status(notification, provider, Notification.NotificationStatus.QUEUED) + # Expunge from session so accessing attributes later won't trigger + # a lazy-load SELECT (which would fail if the delivery service + # already processed and deleted the row). + db.session.expunge(notification) + return notification except Exception as err: diff --git a/notify-service/notify-api/tests/unit/services/test_notify_service.py b/notify-service/notify-api/tests/unit/services/test_notify_service.py index 63889712..2e75f204 100644 --- a/notify-service/notify-api/tests/unit/services/test_notify_service.py +++ b/notify-service/notify-api/tests/unit/services/test_notify_service.py @@ -383,10 +383,11 @@ def test_queue_publish_exception(app): assert result.status_code == Notification.NotificationStatus.FAILURE @staticmethod + @patch("notify_api.services.notify_service.db") @patch("notify_api.services.notify_service.queue") @patch("notify_api.services.notify_service.GcpQueue") @patch("notify_api.services.notify_service.Notification") - def test_process_single_recipient_success(mock_notification_class, mock_gcp_queue, mock_queue): + def test_process_single_recipient_success(mock_notification_class, mock_gcp_queue, mock_queue, mock_db): """Test successful single recipient processing.""" # Setup mocks mock_notification = Mock() @@ -417,6 +418,7 @@ def test_process_single_recipient_success(mock_notification_class, mock_gcp_queu mock_request, "test@example.com", "GC_NOTIFY" ) mock_update_status.assert_called_once() + mock_db.session.expunge.assert_called_once_with(mock_notification) @staticmethod @patch("notify_api.services.notify_service.queue")