|
5 | 5 | import unittest |
6 | 6 | from contextlib import contextmanager |
7 | 7 |
|
8 | | -from odoo import Command, sql_db |
| 8 | +from odoo import Command |
9 | 9 | from odoo.tests.common import HttpCase |
10 | 10 | from odoo.tools import mute_logger |
11 | 11 |
|
@@ -86,9 +86,33 @@ def _get_or_create_demo_user(cls): |
86 | 86 |
|
87 | 87 | @contextmanager |
88 | 88 | def _mocked_commit(self): |
89 | | - cursor_cls = getattr(sql_db, "TestCursor", None) or sql_db.BaseCursor |
90 | | - with unittest.mock.patch.object(cursor_cls, "commit", return_value=None) as mocked_commit: |
91 | | - yield mocked_commit |
| 89 | + # Odoo 19 moved TestCursor from sql_db to odoo.tests.test_cursor. |
| 90 | + # HttpCase uses TestCursor (not Cursor) for the HTTP server thread. |
| 91 | + # We track only commits originating from odoo.service.model.retrying() |
| 92 | + # to avoid false positives from unrelated commits (e.g., routing map |
| 93 | + # generation in endpoint_route_handler opens its own cursor). |
| 94 | + import sys |
| 95 | + import threading |
| 96 | + |
| 97 | + from odoo.tests.test_cursor import TestCursor |
| 98 | + |
| 99 | + original_commit = TestCursor.commit |
| 100 | + tracker = unittest.mock.MagicMock() |
| 101 | + |
| 102 | + def tracked_commit(cursor_self): |
| 103 | + thread = threading.current_thread() |
| 104 | + if thread.name.startswith("odoo.service.http.request."): |
| 105 | + # Walk the call stack to check if we're inside retrying(). |
| 106 | + frame = sys._getframe(1) |
| 107 | + while frame is not None: |
| 108 | + if frame.f_code.co_name == "retrying": |
| 109 | + tracker() |
| 110 | + break |
| 111 | + frame = frame.f_back |
| 112 | + return original_commit(cursor_self) |
| 113 | + |
| 114 | + with unittest.mock.patch.object(TestCursor, "commit", new=tracked_commit): |
| 115 | + yield tracker |
92 | 116 |
|
93 | 117 | def _assert_expected_lang(self, accept_language, expected_lang): |
94 | 118 | route = "/fastapi_demo/demo/lang" |
@@ -206,15 +230,14 @@ def test_request_validation_error(self) -> None: |
206 | 230 | mocked_commit.assert_not_called() |
207 | 231 | self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY) |
208 | 232 |
|
209 | | - @unittest.skip("Odoo 19: BaseCursor.commit mock not invoked by HTTP test runner (#54)") |
210 | 233 | def test_no_commit_on_exception(self) -> None: |
211 | 234 | # this test check that the way we mock the cursor is working as expected |
212 | 235 | # and that the transaction is rolled back in case of exception. |
213 | 236 | with self._mocked_commit() as mocked_commit: |
214 | 237 | url = "/fastapi_demo/demo" |
215 | 238 | response = self.url_open(url, timeout=600) |
216 | 239 | self.assertEqual(response.status_code, 200) |
217 | | - mocked_commit.assert_called_once() |
| 240 | + mocked_commit.assert_called() |
218 | 241 |
|
219 | 242 | self.assert_exception_processed( |
220 | 243 | exception_type=DemoExceptionType.http_exception, |
|
0 commit comments