Skip to content

Commit b42ba0a

Browse files
committed
[FIX] edi_core_oca: handle OperationalError and IntegrityError exceptions to prevent finally block execution in aborted transaction state
1 parent b7ca2bc commit b42ba0a

File tree

4 files changed

+84
-30
lines changed

4 files changed

+84
-30
lines changed

edi_core_oca/models/edi_backend.py

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import traceback
1111
from io import StringIO
1212

13+
from psycopg2 import IntegrityError, OperationalError
14+
1315
from odoo import exceptions, fields, models
1416
from odoo.exceptions import UserError
1517

@@ -237,6 +239,11 @@ def exchange_send(self, exchange_record):
237239
_logger.debug(
238240
"%s send failed. Marked as errored.", exchange_record.identifier
239241
)
242+
except (OperationalError, IntegrityError):
243+
# We don't want the finally block to be executed in this case as
244+
# the cursor is already in an aborted state and any query will fail.
245+
res = "__sql_error__"
246+
raise
240247
else:
241248
# TODO: maybe the send handler should return desired message and state
242249
message = exchange_record._exchange_status_message("send_ok")
@@ -248,16 +255,18 @@ def exchange_send(self, exchange_record):
248255
)
249256
res = message
250257
finally:
251-
exchange_record.write(
252-
{
253-
"edi_exchange_state": state,
254-
"exchange_error": error,
255-
"exchange_error_traceback": traceback,
256-
# FIXME: this should come from _compute_exchanged_on
257-
# but somehow it's failing in send tests (in record tests it works).
258-
"exchanged_on": fields.Datetime.now(),
259-
}
260-
)
258+
if res != "__sql_error__":
259+
exchange_record.write(
260+
{
261+
"edi_exchange_state": state,
262+
"exchange_error": error,
263+
"exchange_error_traceback": traceback,
264+
# FIXME: this should come from _compute_exchanged_on
265+
# but somehow it's failing in send tests
266+
# (in record tests it works).
267+
"exchanged_on": fields.Datetime.now(),
268+
}
269+
)
261270
exchange_record.notify_action_complete("send", message=message)
262271
return res
263272

@@ -445,20 +454,27 @@ def exchange_process(self, exchange_record):
445454
error = _get_exception_msg(err)
446455
state = "input_processed_error"
447456
res = f"Error: {error}"
457+
except (OperationalError, IntegrityError):
458+
# We don't want the finally block to be executed in this case as
459+
# the cursor is already in an aborted state and any query will fail.
460+
res = "__sql_error__"
461+
raise
448462
else:
449463
error = traceback = None
450464
state = "input_processed"
451465
finally:
452-
exchange_record.write(
453-
{
454-
"edi_exchange_state": state,
455-
"exchange_error": error,
456-
"exchange_error_traceback": traceback,
457-
# FIXME: this should come from _compute_exchanged_on
458-
# but somehow it's failing in send tests (in record tests it works).
459-
"exchanged_on": fields.Datetime.now(),
460-
}
461-
)
466+
if res != "__sql_error__":
467+
exchange_record.write(
468+
{
469+
"edi_exchange_state": state,
470+
"exchange_error": error,
471+
"exchange_error_traceback": traceback,
472+
# FIXME: this should come from _compute_exchanged_on
473+
# but somehow it's failing in send tests
474+
# (in record tests it works).
475+
"exchanged_on": fields.Datetime.now(),
476+
}
477+
)
462478
if (
463479
state == "input_processed_error"
464480
and old_state != "input_processed_error"
@@ -506,22 +522,29 @@ def exchange_receive(self, exchange_record):
506522
state = "input_receive_error"
507523
message = exchange_record._exchange_status_message("receive_ko")
508524
res = f"Input error: {error}"
525+
except (OperationalError, IntegrityError):
526+
# We don't want the finally block to be executed in this case as
527+
# the cursor is already in an aborted state and any query will fail.
528+
res = "__sql_error__"
529+
raise
509530
else:
510531
message = exchange_record._exchange_status_message("receive_ok")
511532
error = traceback = None
512533
state = "input_received"
513534
res = message
514535
finally:
515-
exchange_record.write(
516-
{
517-
"edi_exchange_state": state,
518-
"exchange_error": error,
519-
"exchange_error_traceback": traceback,
520-
# FIXME: this should come from _compute_exchanged_on
521-
# but somehow it's failing in send tests (in record tests it works).
522-
"exchanged_on": fields.Datetime.now(),
523-
}
524-
)
536+
if res != "__sql_error__":
537+
exchange_record.write(
538+
{
539+
"edi_exchange_state": state,
540+
"exchange_error": error,
541+
"exchange_error_traceback": traceback,
542+
# FIXME: this should come from _compute_exchanged_on
543+
# but somehow it's failing in send tests
544+
# (in record tests it works).
545+
"exchanged_on": fields.Datetime.now(),
546+
}
547+
)
525548
exchange_record.notify_action_complete("receive", message=message)
526549
return res
527550

edi_core_oca/tests/test_backend_input.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
44

55
from odoo_test_helper import FakeModelLoader
6+
from psycopg2 import OperationalError
67

78
from .common import EDIBackendCommonTestCase
89

@@ -78,3 +79,12 @@ def test_receive_allow_empty_file_record(self):
7879
# Check the record
7980
self.assertEqual(self.record._get_file_content(), "")
8081
self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}])
82+
83+
def test_receive_record_with_operational_error(self):
84+
self.record.edi_exchange_state = "input_pending"
85+
with self.assertRaises(OperationalError):
86+
self.backend.with_context(
87+
test_break_receive=OperationalError("SQL error")
88+
).exchange_receive(self.record)
89+
self.assertRecordValues(self.record, [{"edi_exchange_state": "input_pending"}])
90+
self.assertFalse(self.record.exchange_error)

edi_core_oca/tests/test_backend_output.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from freezegun import freeze_time
99
from odoo_test_helper import FakeModelLoader
10+
from psycopg2 import OperationalError
1011

1112
from odoo import fields, tools
1213
from odoo.exceptions import UserError
@@ -122,3 +123,13 @@ def test_send_not_generated_record(self):
122123
err.exception.args[0], "Record ID=%d has no file to send!" % record.id
123124
)
124125
mocked.assert_not_called()
126+
127+
def test_send_record_with_operational_error(self):
128+
self.record.write({"edi_exchange_state": "output_pending"})
129+
self.record._set_file_content("TEST %d" % self.record.id)
130+
with self.assertRaises(OperationalError):
131+
self.backend.with_context(
132+
test_break_send=OperationalError("SQL error")
133+
).exchange_send(self.record)
134+
self.assertRecordValues(self.record, [{"edi_exchange_state": "output_pending"}])
135+
self.assertFalse(self.record.exchange_error)

edi_core_oca/tests/test_backend_process.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from freezegun import freeze_time
88
from odoo_test_helper import FakeModelLoader
9+
from psycopg2 import IntegrityError
910

1011
from odoo import fields
1112
from odoo.exceptions import UserError
@@ -103,4 +104,13 @@ def test_process_outbound_record(self):
103104
with self.assertRaises(UserError):
104105
record.action_exchange_process()
105106

107+
def test_process_record_with_integrity_error(self):
108+
self.record.write({"edi_exchange_state": "input_received"})
109+
with self.assertRaises(IntegrityError):
110+
self.backend.with_context(
111+
test_break_process=IntegrityError("SQL error")
112+
).exchange_process(self.record)
113+
self.assertRecordValues(self.record, [{"edi_exchange_state": "input_received"}])
114+
self.assertFalse(self.record.exchange_error)
115+
106116
# TODO: test ack file are processed

0 commit comments

Comments
 (0)