diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index d4af65ff6..e6d4b9f6a 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -10,6 +10,8 @@ import traceback from io import StringIO +from psycopg2 import IntegrityError, OperationalError + from odoo import exceptions, fields, models from odoo.exceptions import UserError @@ -239,6 +241,11 @@ def exchange_send(self, exchange_record): _logger.debug( "%s send failed. Marked as errored.", exchange_record.identifier ) + except (OperationalError, IntegrityError): + # We don't want the finally block to be executed in this case as + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: # TODO: maybe the send handler should return desired message and state message = exchange_record._exchange_status_message("send_ok") @@ -250,16 +257,18 @@ def exchange_send(self, exchange_record): ) res = message finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) exchange_record.notify_action_complete("send", message=message) return res @@ -439,20 +448,27 @@ def exchange_process(self, exchange_record): error = _get_exception_msg(err) state = "input_processed_error" res = f"Error: {error}" + except (OperationalError, IntegrityError): + # We don't want the finally block to be executed in this case as + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: error = traceback = None state = "input_processed" finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) if ( state == "input_processed_error" and old_state != "input_processed_error" @@ -500,22 +516,29 @@ def exchange_receive(self, exchange_record): state = "input_receive_error" message = exchange_record._exchange_status_message("receive_ko") res = f"Input error: {error}" + except (OperationalError, IntegrityError): + # We don't want the finally block to be executed in this case as + # the cursor is already in an aborted state and any query will fail. + res = "__sql_error__" + raise else: message = exchange_record._exchange_status_message("receive_ok") error = traceback = None state = "input_received" res = message finally: - exchange_record.write( - { - "edi_exchange_state": state, - "exchange_error": error, - "exchange_error_traceback": traceback, - # FIXME: this should come from _compute_exchanged_on - # but somehow it's failing in send tests (in record tests it works). - "exchanged_on": fields.Datetime.now(), - } - ) + if res != "__sql_error__": + exchange_record.write( + { + "edi_exchange_state": state, + "exchange_error": error, + "exchange_error_traceback": traceback, + # FIXME: this should come from _compute_exchanged_on + # but somehow it's failing in send tests + # (in record tests it works). + "exchanged_on": fields.Datetime.now(), + } + ) exchange_record.notify_action_complete("receive", message=message) return res diff --git a/edi_core_oca/tests/test_backend_input.py b/edi_core_oca/tests/test_backend_input.py index 4b2b92dde..44065c927 100644 --- a/edi_core_oca/tests/test_backend_input.py +++ b/edi_core_oca/tests/test_backend_input.py @@ -2,6 +2,8 @@ # @author: Simone Orsi # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +from psycopg2 import OperationalError + from odoo.orm.model_classes import add_to_registry from .common import EDIBackendCommonTestCase @@ -84,3 +86,12 @@ def test_receive_allow_empty_file_record(self): # Check the record self.assertEqual(self.record._get_file_content(), "") self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) + + def test_receive_record_with_operational_error(self): + self.record.edi_exchange_state = "input_pending" + with self.assertRaises(OperationalError): + self.backend.with_context( + test_break_receive=OperationalError("SQL error") + ).exchange_receive(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_pending"}]) + self.assertFalse(self.record.exchange_error) diff --git a/edi_core_oca/tests/test_backend_output.py b/edi_core_oca/tests/test_backend_output.py index a884003ee..39f2d57a8 100644 --- a/edi_core_oca/tests/test_backend_output.py +++ b/edi_core_oca/tests/test_backend_output.py @@ -6,6 +6,7 @@ from unittest import mock from freezegun import freeze_time +from psycopg2 import OperationalError from odoo import fields, tools from odoo.exceptions import UserError @@ -129,3 +130,13 @@ def test_send_not_generated_record(self): err.exception.args[0], f"Record ID={record.id} has no file to send!" ) mocked.assert_not_called() + + def test_send_record_with_operational_error(self): + self.record.write({"edi_exchange_state": "output_pending"}) + self.record._set_file_content(f"TEST {self.record.id}") + with self.assertRaises(OperationalError): + self.backend.with_context( + test_break_send=OperationalError("SQL error") + ).exchange_send(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "output_pending"}]) + self.assertFalse(self.record.exchange_error) diff --git a/edi_core_oca/tests/test_backend_process.py b/edi_core_oca/tests/test_backend_process.py index 4e2bc59a2..bb6f4ffeb 100644 --- a/edi_core_oca/tests/test_backend_process.py +++ b/edi_core_oca/tests/test_backend_process.py @@ -5,6 +5,7 @@ import base64 from freezegun import freeze_time +from psycopg2 import IntegrityError from odoo import fields from odoo.exceptions import UserError @@ -109,4 +110,13 @@ def test_process_outbound_record(self): with self.assertRaises(UserError): record.action_exchange_process() + def test_process_record_with_integrity_error(self): + self.record.write({"edi_exchange_state": "input_received"}) + with self.assertRaises(IntegrityError): + self.backend.with_context( + test_break_process=IntegrityError("SQL error") + ).exchange_process(self.record) + self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}]) + self.assertFalse(self.record.exchange_error) + # TODO: test ack file are processed