Skip to content

Commit 588919a

Browse files
committed
[ADD] queue_job: HttpCase regression tests for /queue_job/runjob
Adds an HttpJobCaseMixin in tests/common.py providing a reusable run_http_job() helper for tests that exercise /queue_job/runjob. New test_run_job_controller_http.py covers #922: - test_runjob_pins_default_env_to_superuser asserts that during job.perform() the transaction's default_env.uid is SUPERUSER_ID. - test_runjob_without_updating_default_env_leaves_uid_none substitutes an alternative runjob that only swaps the local env and verifies default_env.uid stays None, isolating default_env repointing as the load-bearing behavior of the endpoint.
1 parent 415420f commit 588919a

3 files changed

Lines changed: 141 additions & 0 deletions

File tree

queue_job/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from . import test_run_job_controller_http
12
from . import test_run_rob_controller
23
from . import test_runner_channels
34
from . import test_runner_runner

queue_job/tests/common.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,41 @@ def __repr__(self):
321321
return repr(self.calls)
322322

323323

324+
class HttpJobCaseMixin:
325+
"""Mixin for tests that exercise the ``/queue_job/runjob`` HTTP endpoint.
326+
327+
Combine with :class:`odoo.tests.common.HttpCase`. Example::
328+
329+
@tagged("-at_install", "post_install")
330+
class TestSomeJob(HttpJobCaseMixin, HttpCase):
331+
def test_something(self):
332+
delayable = self.env["queue.job"].with_delay()._test_job()
333+
record = self.run_http_job(delayable)
334+
self.assertEqual(record.state, "done")
335+
"""
336+
337+
def run_http_job(self, job_or_uuid):
338+
"""Hit ``/queue_job/runjob`` for the given job.
339+
340+
Commits the current transaction so the HTTP server's cursor can see
341+
the enqueued job, performs the request, then commits again to pull
342+
in state changes written by the request. Returns the reloaded
343+
``queue.job`` record.
344+
"""
345+
uuid = (
346+
job_or_uuid
347+
if isinstance(job_or_uuid, str)
348+
else job_or_uuid.db_record().uuid
349+
)
350+
self.env.cr.commit()
351+
response = self.url_open(
352+
f"/queue_job/runjob?db={self.env.cr.dbname}&job_uuid={uuid}"
353+
)
354+
response.raise_for_status()
355+
self.env.cr.commit()
356+
return self.env["queue.job"].search([("uuid", "=", uuid)])
357+
358+
324359
class JobCounter:
325360
def __init__(self, env):
326361
super().__init__()
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
2+
3+
from unittest import mock
4+
5+
from odoo import SUPERUSER_ID, http
6+
from odoo.tests.common import HttpCase, tagged
7+
8+
from ..controllers.main import RunJobController
9+
from ..job import Job
10+
from .common import HttpJobCaseMixin
11+
12+
13+
@tagged("-at_install", "post_install")
14+
class TestRunJobHttp(HttpJobCaseMixin, HttpCase):
15+
"""End-to-end tests for the ``/queue_job/runjob`` endpoint.
16+
17+
``runjob`` is declared ``auth="none"``, so ``_auth_method_none`` pins
18+
``transaction.default_env`` to an env with ``uid=None``. The controller
19+
must additionally repoint ``default_env`` to a superuser env, otherwise
20+
any flush during ``job.perform()`` that goes through
21+
``Transaction.flush() -> default_env.flush_all()`` recomputes stored
22+
fields with ``self.env.user`` as an empty recordset. See #922.
23+
"""
24+
25+
def _capture_default_env_uid_during(self, delayable):
26+
"""Run ``delayable`` via /queue_job/runjob and observe the transaction's
27+
``default_env.uid`` from inside ``job.perform()``.
28+
29+
Installs a spy around ``Job.perform`` that records
30+
``job.env.transaction.default_env.uid`` before delegating to the real
31+
implementation. HttpCase runs the HTTP server in the same process,
32+
so the class-level ``mock.patch`` takes effect on the server side too.
33+
34+
:param delayable: a delayed job (return value of
35+
``with_delay().method()``)
36+
:returns: ``(default_env_uid, queue_job_record)`` -- the uid observed
37+
during ``perform()`` and the reloaded ``queue.job`` record after
38+
the request completes.
39+
"""
40+
captured = {}
41+
original_perform = Job.perform
42+
43+
def spy(job):
44+
captured["default_env_uid"] = job.env.transaction.default_env.uid
45+
return original_perform(job)
46+
47+
with mock.patch.object(Job, "perform", spy):
48+
record = self.run_http_job(delayable)
49+
50+
return captured["default_env_uid"], record
51+
52+
def test_runjob_pins_default_env_to_superuser(self):
53+
"""``runjob`` must set ``transaction.default_env`` to a superuser env.
54+
55+
Verifies that during ``job.perform()``,
56+
``env.transaction.default_env.uid`` is ``SUPERUSER_ID``. This is the
57+
state that lets flushes triggered by the job recompute stored fields
58+
with a real ``self.env.user`` rather than an empty recordset.
59+
"""
60+
delayable = self.env["queue.job"].with_delay(
61+
description="check default_env uid (superuser)"
62+
)._test_job()
63+
64+
default_env_uid, record = self._capture_default_env_uid_during(delayable)
65+
66+
self.assertEqual(record.state, "done")
67+
self.assertEqual(default_env_uid, SUPERUSER_ID)
68+
69+
def test_runjob_without_updating_default_env_leaves_uid_none(self):
70+
"""Repointing ``default_env`` is the specific mechanism this relies on.
71+
72+
Substitutes an alternative ``runjob`` that only creates a local
73+
superuser env (via ``http.request.env(user=SUPERUSER_ID)``) without
74+
touching ``transaction.default_env``, and verifies that the observable
75+
``default_env.uid`` inside ``job.perform()`` is then ``None``. This
76+
isolates ``default_env`` repointing as the load-bearing behavior of
77+
``runjob``: any future refactor that drops it will trip this test.
78+
"""
79+
80+
def runjob_local_env_only(self_, db, job_uuid, **kw):
81+
"""Alternative ``runjob`` that swaps only the local ``env``.
82+
83+
Uses ``http.request.env(user=SUPERUSER_ID)`` to obtain a
84+
superuser env for controller bookkeeping (acquire, lock, commit
85+
state changes), but does not update ``request.env`` or
86+
``transaction.default_env``, which remain the ``uid=None`` env
87+
installed by ``_auth_method_none``.
88+
"""
89+
http.request.session.db = db
90+
env = http.request.env(user=SUPERUSER_ID)
91+
job = self_._acquire_job(env, job_uuid)
92+
if not job:
93+
return ""
94+
self_._runjob(env, job)
95+
return ""
96+
97+
delayable = self.env["queue.job"].with_delay(
98+
description="check default_env uid (local env only)"
99+
)._test_job()
100+
101+
with mock.patch.object(RunJobController, "runjob", runjob_local_env_only):
102+
default_env_uid, record = self._capture_default_env_uid_during(delayable)
103+
104+
self.assertEqual(record.state, "done")
105+
self.assertIsNone(default_env_uid)

0 commit comments

Comments
 (0)