diff --git a/edi_core_oca/models/edi_backend.py b/edi_core_oca/models/edi_backend.py index dfeefbdeb..9b94da8f4 100644 --- a/edi_core_oca/models/edi_backend.py +++ b/edi_core_oca/models/edi_backend.py @@ -295,12 +295,20 @@ def exchange_send(self, exchange_record): return res def _swallable_exceptions(self): - # TODO: improve this list + # These errors are permanent because retrying the same data will fail again. + # They should be swallowed so the exchange can move to an error state. + # + # OperationalError is excluded because it may be transient and should be + # re-raised to allow retries. return ( ValueError, FileNotFoundError, exceptions.UserError, exceptions.ValidationError, + IntegrityError, + TypeError, + LookupError, # covers KeyError / IndexError + AttributeError, ) def _send_retryable_exceptions(self): diff --git a/edi_core_oca/tests/test_backend_process.py b/edi_core_oca/tests/test_backend_process.py index de259115e..dda6af90e 100644 --- a/edi_core_oca/tests/test_backend_process.py +++ b/edi_core_oca/tests/test_backend_process.py @@ -105,12 +105,28 @@ def test_process_outbound_record(self): record.action_exchange_process() def test_process_record_with_integrity_error(self): + # IntegrityError is permanent (e.g. a DB unique constraint + # violation): retrying the same data would fail again, so it must + # be swallowed and move the record to an error state instead of + # being re-raised and leaving it stuck in "input_received". 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) + with self.assertRaisesRegex(IntegrityError, "SQL error"): + self.record.with_context( + test_break_process=IntegrityError("SQL error"), + _edi_process_break_on_error=True, + ).action_exchange_process() + + self.record.with_context( + test_break_process=IntegrityError("SQL error") + ).action_exchange_process() + self.assertRecordValues( + self.record, + [ + { + "edi_exchange_state": "input_processed_error", + "exchange_error": "SQL error", + } + ], + ) # TODO: test ack file are processed