diff --git a/README.md b/README.md
index 7c5ddf577..76f29bd17 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Available addons
----------------
addon | version | maintainers | summary
--- | --- | --- | ---
-[queue_job](queue_job/) | 19.0.2.0.2 |
| Job Queue
+[queue_job](queue_job/) | 19.0.2.0.3 |
| Job Queue
[test_queue_job](test_queue_job/) | 19.0.2.0.0 |
| Queue Job Tests
diff --git a/queue_job/README.rst b/queue_job/README.rst
index 85162311a..35828a1b3 100644
--- a/queue_job/README.rst
+++ b/queue_job/README.rst
@@ -11,7 +11,7 @@ Job Queue
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !! source digest: sha256:888b7dd2d71a49efa9e71a6e383ed5ababed3d72fa03ce75ffcc680dd8f1ba68
+ !! source digest: sha256:3fa70ebecb05fe5e014778d029da704bcc083caf386201d7df82944dc3885c4d
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Mature-brightgreen.png
diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py
index 2cd3f8394..d071eba23 100644
--- a/queue_job/__manifest__.py
+++ b/queue_job/__manifest__.py
@@ -2,7 +2,7 @@
{
"name": "Job Queue",
- "version": "19.0.2.0.2",
+ "version": "19.0.2.0.3",
"author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)",
"website": "https://github.com/OCA/queue",
"license": "LGPL-3",
diff --git a/queue_job/controllers/main.py b/queue_job/controllers/main.py
index b4a5a596c..46da185d4 100644
--- a/queue_job/controllers/main.py
+++ b/queue_job/controllers/main.py
@@ -214,7 +214,8 @@ def _get_failure_values(cls, job, traceback_txt, orig_exception):
readonly=False,
)
def runjob(self, job_uuid, **kw):
- env = http.request.env(user=SUPERUSER_ID)
+ http.request.update_env(user=SUPERUSER_ID)
+ env = http.request.env
job = self._acquire_job(env, job_uuid)
if not job:
return ""
diff --git a/queue_job/static/description/index.html b/queue_job/static/description/index.html
index 24c6d315c..7cdc29df1 100644
--- a/queue_job/static/description/index.html
+++ b/queue_job/static/description/index.html
@@ -372,7 +372,7 @@
This addon adds an integrated Job Queue to Odoo.
diff --git a/queue_job/tests/__init__.py b/queue_job/tests/__init__.py index 16bcdff96..696549a65 100644 --- a/queue_job/tests/__init__.py +++ b/queue_job/tests/__init__.py @@ -1,3 +1,4 @@ +from . import test_run_job_controller_http from . import test_run_rob_controller from . import test_runner_channels from . import test_runner_runner diff --git a/queue_job/tests/test_run_job_controller_http.py b/queue_job/tests/test_run_job_controller_http.py new file mode 100644 index 000000000..6aaa30d01 --- /dev/null +++ b/queue_job/tests/test_run_job_controller_http.py @@ -0,0 +1,85 @@ +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +from unittest import mock + +from odoo import SUPERUSER_ID, http +from odoo.tests.common import HttpCase, tagged + +from ..controllers.main import RunJobController + + +@tagged("-at_install", "post_install") +class TestRunJobHttp(HttpCase): + """End-to-end tests for the ``/queue_job/runjob`` endpoint. + + ``runjob`` is declared ``auth="none"``, so ``_auth_method_none`` pins + ``transaction.default_env`` to an env with ``uid=None``. The controller + must additionally repoint ``default_env`` to a superuser env, otherwise + any flush during ``job.perform()`` that goes through + ``Transaction.flush() -> default_env.flush_all()`` recomputes stored + fields with ``self.env.user`` as an empty recordset. See #922. + """ + + def _capture_default_env_uid_at_acquire(self): + """Hit ``/queue_job/runjob`` and observe ``transaction.default_env.uid`` + at the moment ``_acquire_job`` is entered. + + ``_acquire_job`` is patched to return ``None`` so the endpoint exits + without performing the job. This avoids the commit inside + ``_acquire_job``, which is forbidden inside tests, and is sufficient + to observe the env state established by the request-handling + machinery: nothing between ``update_env`` and ``_acquire_job`` + touches ``transaction.default_env``, so the value seen here is the + same value a job would see at ``perform()`` time. + + HttpCase runs the HTTP server in the same process, so class-level + ``mock.patch`` calls take effect on the server side as well. + """ + captured = {} + + def spy(cls, env, job_uuid): + captured["default_env_uid"] = env.transaction.default_env.uid + return None + + with mock.patch.object(RunJobController, "_acquire_job", classmethod(spy)): + response = self.url_open( + f"/queue_job/runjob?db={self.env.cr.dbname}&job_uuid=stub" + ) + response.raise_for_status() + + return captured.get("default_env_uid") + + def test_runjob_pins_default_env_to_superuser(self): + """``runjob`` must set ``transaction.default_env`` to a superuser env. + + Verifies that by the time ``_acquire_job`` is entered, + ``env.transaction.default_env.uid`` is ``SUPERUSER_ID``. This is the + state that lets flushes triggered by the job recompute stored fields + with a real ``self.env.user`` rather than an empty recordset. + """ + default_env_uid = self._capture_default_env_uid_at_acquire() + self.assertEqual(default_env_uid, SUPERUSER_ID) + + def test_runjob_without_updating_default_env_leaves_uid_none(self): + """Repointing ``default_env`` is the specific mechanism this relies on. + + Neutralises ``http.request.update_env`` for the duration of the + request, matching what happens if a ``runjob`` implementation only + obtains a local superuser env via ``http.request.env(user=...)`` + without invoking ``update_env``. The observable ``default_env.uid`` + then remains at the ``None`` value installed by + ``_auth_method_none``. This isolates ``default_env`` repointing as + the load-bearing behavior of ``runjob``: any future refactor that + drops it will trip this test. + """ + + def no_op_update_env(self_, user=None, context=None, su=None): + """Alternative ``Request.update_env`` that intentionally does + nothing. Simulates a controller that never asks the request to + update its environment. + """ + + with mock.patch.object(http.Request, "update_env", no_op_update_env): + default_env_uid = self._capture_default_env_uid_at_acquire() + + self.assertIsNone(default_env_uid)