From 8e52953fa5a953e1f8ba3f3c5284f5f7d9ab42e7 Mon Sep 17 00:00:00 2001 From: Milan Topuzov Date: Wed, 1 Oct 2025 14:01:56 +0200 Subject: [PATCH 1/2] [19.0] queue_job: migrate + tests - Port queue_job to 19.0 with minimal changes\n- Adjust tests (create test users, context handling)\n- Scope: queue_job, test_queue_job only --- queue_job/README.rst | 10 +-- queue_job/__manifest__.py | 6 +- queue_job/fields.py | 5 +- queue_job/job.py | 2 +- queue_job/jobrunner/__init__.py | 31 ++++++- queue_job/models/base.py | 21 ++++- queue_job/models/queue_job.py | 57 +++++++------ queue_job/models/queue_job_channel.py | 8 +- queue_job/models/queue_job_function.py | 2 +- queue_job/security/security.xml | 14 ++-- queue_job/static/description/index.html | 6 +- queue_job/tests/common.py | 11 ++- queue_job/tests/test_json_field.py | 23 ++++- .../tests/test_queue_job_protected_write.py | 5 +- queue_job/views/queue_job_function_views.xml | 10 +-- queue_job/views/queue_job_views.xml | 84 ++++--------------- test_queue_job/__manifest__.py | 4 +- test_queue_job/tests/test_autovacuum.py | 4 +- test_queue_job/tests/test_job.py | 68 ++++++++++++--- test_queue_job/tests/test_json_field.py | 19 ++++- 20 files changed, 240 insertions(+), 150 deletions(-) diff --git a/queue_job/README.rst b/queue_job/README.rst index 88b5a4d00b..54304cf76f 100644 --- a/queue_job/README.rst +++ b/queue_job/README.rst @@ -21,13 +21,13 @@ Job Queue :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :alt: License: LGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fqueue-lightgray.png?logo=github - :target: https://github.com/OCA/queue/tree/18.0/queue_job + :target: https://github.com/OCA/queue/tree/19.0/queue_job :alt: OCA/queue .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png - :target: https://translation.odoo-community.org/projects/queue-18-0/queue-18-0-queue_job + :target: https://translation.odoo-community.org/projects/queue-19-0/queue-19-0-queue_job :alt: Translate me on Weblate .. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png - :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/queue&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -661,7 +661,7 @@ Bug Tracker Bugs are tracked on `GitHub Issues `_. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -720,6 +720,6 @@ Current `maintainer `__: |maintainer-guewen| -This module is part of the `OCA/queue `_ project on GitHub. +This module is part of the `OCA/queue `_ project on GitHub. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/queue_job/__manifest__.py b/queue_job/__manifest__.py index 70b9774518..01e6a89015 100644 --- a/queue_job/__manifest__.py +++ b/queue_job/__manifest__.py @@ -2,7 +2,7 @@ { "name": "Job Queue", - "version": "18.0.2.0.2", + "version": "19.0.1.0.0", "author": "Camptocamp,ACSONE SA/NV,Odoo Community Association (OCA)", "website": "https://github.com/OCA/queue", "license": "LGPL-3", @@ -24,10 +24,10 @@ ], "assets": { "web.assets_backend": [ - "/queue_job/static/src/views/**/*", + "queue_job/static/src/views/**/*", ], }, - "installable": False, + "installable": True, "development_status": "Mature", "maintainers": ["guewen"], "post_init_hook": "post_init_hook", diff --git a/queue_job/fields.py b/queue_job/fields.py index 8cb45af765..26324fcbde 100644 --- a/queue_job/fields.py +++ b/queue_job/fields.py @@ -9,6 +9,7 @@ from psycopg2.extras import Json as PsycopgJson from odoo import fields, models +from odoo.tools.misc import SENTINEL from odoo.tools.func import lazy @@ -38,7 +39,7 @@ class JobSerialized(fields.Json): ), } - def __init__(self, string=fields.SENTINEL, base_type=fields.SENTINEL, **kwargs): + def __init__(self, string=SENTINEL, base_type=SENTINEL, **kwargs): super().__init__(string=string, _base_type=base_type, **kwargs) def _setup_attrs(self, model, name): # pylint: disable=missing-return @@ -66,7 +67,7 @@ def convert_to_cache(self, value, record, validate=True): def convert_to_record(self, value, record): default = self._base_type_default_json(record.env) value = value or default - if not isinstance(value, (str | bytes | bytearray)): + if not isinstance(value, (str, bytes, bytearray)): value = json.dumps(value, cls=JobEncoder) return json.loads(value, cls=JobDecoder, env=record.env) diff --git a/queue_job/job.py b/queue_job/job.py index 6cfe12f232..eaf18e8c65 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -828,7 +828,7 @@ def _get_retry_seconds(self, seconds=None): break elif not seconds: seconds = RETRY_INTERVAL - if isinstance(seconds, (list | tuple)): + if isinstance(seconds, (list, tuple)): seconds = randint(seconds[0], seconds[1]) return seconds diff --git a/queue_job/jobrunner/__init__.py b/queue_job/jobrunner/__init__.py index e2561b0e74..39eaf1872e 100644 --- a/queue_job/jobrunner/__init__.py +++ b/queue_job/jobrunner/__init__.py @@ -10,6 +10,8 @@ from odoo.tools import config try: + # Preferred source when available: structured [queue_job] section provided + # by OCA's server_environment addon. from odoo.addons.server_environment import serv_config if serv_config.has_section("queue_job"): @@ -17,7 +19,34 @@ else: queue_job_config = {} except ImportError: - queue_job_config = config.misc.get("queue_job", {}) + # Odoo 19: config.misc is no longer available. Build a minimal config + # from flat odoo.conf options so the runner works without server_environment. + queue_job_config = {} + +# Merge flat odoo.conf options as a fallback (applies regardless of whether +# server_environment is installed). Precedence is enforced later where used: +# - Environment variables (highest) are read directly in runner functions +# - Then values coming from server_environment's [queue_job] section (above) +# - Finally flat odoo.conf options below (lowest) +# +# Supported flat options (under the [options] section in odoo.conf): +# queue_job_channels = root:2,mychan:1 +# queue_job_jobrunner_db_host = localhost +# queue_job_jobrunner_db_port = 5432 +# queue_job_jobrunner_db_user = odoo_queue +# queue_job_jobrunner_db_password = odoo_queue +_flat = {} +channels = config.get("queue_job_channels") +if channels: + _flat["channels"] = channels +for p in ("host", "port", "user", "password"): + v = config.get(f"queue_job_jobrunner_db_{p}") + if v: + _flat[f"jobrunner_db_{p}"] = v + +# Do not override keys coming from server_environment if present +for k, v in _flat.items(): + queue_job_config.setdefault(k, v) from .runner import QueueJobRunner, _channels diff --git a/queue_job/models/base.py b/queue_job/models/base.py index 3a68ffd11b..038fb77c82 100644 --- a/queue_job/models/base.py +++ b/queue_job/models/base.py @@ -262,9 +262,26 @@ def _job_prepare_context_before_enqueue(self): @classmethod def _patch_method(cls, name, method): + """Patch ``name`` with ``method`` preserving API metadata (Odoo 19). + + Odoo 19 no longer exposes ``api.propagate``. We emulate the + propagation by using ``functools.update_wrapper`` and copying the + decorator metadata which Odoo relies on (see orm.decorators). + """ origin = getattr(cls, name) method.origin = origin - # propagate decorators from origin to method, and apply api decorator - wrapped = api.propagate(origin, method) + # carry over wrapper attributes (name, doc, etc.) + wrapped = functools.update_wrapper(method, origin) + # propagate common decorator metadata used by the framework + for attr in ( + "_constrains", + "_depends", + "_onchange", + "_ondelete", + "_api_model", + "_api_private", + ): + if hasattr(origin, attr): + setattr(wrapped, attr, getattr(origin, attr)) wrapped.origin = origin setattr(cls, name, wrapped) diff --git a/queue_job/models/queue_job.py b/queue_job/models/queue_job.py index 411ae43af5..f2579cac36 100644 --- a/queue_job/models/queue_job.py +++ b/queue_job/models/queue_job.py @@ -6,7 +6,8 @@ from datetime import datetime, timedelta from odoo import _, api, exceptions, fields, models -from odoo.tools import config, html_escape, index_exists +from odoo.tools import config, html_escape +from odoo.tools.sql import create_index, index_exists from odoo.addons.base_sparse_field.models.fields import Serialized @@ -127,38 +128,44 @@ class QueueJob(models.Model): worker_pid = fields.Integer(readonly=True) def init(self): + # Odoo 19: self._cr deprecated, use self.env.cr; prefer tools.sql helpers for idempotent DDL + cr = self.env.cr index_1 = "queue_job_identity_key_state_partial_index" index_2 = "queue_job_channel_date_done_date_created_index" - if not index_exists(self._cr, index_1): + if not index_exists(cr, index_1): # Used by Job.job_record_with_same_identity_key - self._cr.execute( - "CREATE INDEX queue_job_identity_key_state_partial_index " - "ON queue_job (identity_key) WHERE state in ('pending', " - "'enqueued', 'wait_dependencies') AND identity_key IS NOT NULL;" + create_index( + cr, + index_1, + "queue_job", + ["identity_key"], + where="state in ('pending','enqueued','wait_dependencies') AND identity_key IS NOT NULL", + comment="Queue Job: partial index for identity_key on active states", ) - if not index_exists(self._cr, index_2): + if not index_exists(cr, index_2): # Used by .autovacuum - self._cr.execute( - "CREATE INDEX queue_job_channel_date_done_date_created_index " - "ON queue_job (channel, date_done, date_created);" + create_index( + cr, + index_2, + "queue_job", + ["channel", "date_done", "date_created"], + comment="Queue Job: index to accelerate autovacuum", ) @api.depends("dependencies") def _compute_dependency_graph(self): - jobs_groups = self.env["queue.job"].read_group( - [ - ( - "graph_uuid", - "in", - [uuid for uuid in self.mapped("graph_uuid") if uuid], - ) - ], - ["graph_uuid", "ids:array_agg(id)"], - ["graph_uuid"], - ) - ids_per_graph_uuid = { - group["graph_uuid"]: group["ids"] for group in jobs_groups - } + uuids = [uuid for uuid in self.mapped("graph_uuid") if uuid] + ids_per_graph_uuid = {} + if uuids: + # Odoo 19: avoid ORM warning by using _read_group with 'id:recordset' aggregate + rows = self.env["queue.job"]._read_group( + [("graph_uuid", "in", uuids)], + groupby=["graph_uuid"], + aggregates=["id:recordset"], + ) + # rows -> list of tuples: (graph_uuid, recordset) + for graph_uuid, recs in rows: + ids_per_graph_uuid[graph_uuid] = recs.ids for record in self: if not record.graph_uuid: record.dependency_graph = {} @@ -356,7 +363,7 @@ def _subscribe_users_domain(self): if not group: return None companies = self.mapped("company_id") - domain = [("groups_id", "=", group.id)] + domain = [("group_ids", "=", group.id)] if companies: domain.append(("company_id", "in", companies.ids)) return domain diff --git a/queue_job/models/queue_job_channel.py b/queue_job/models/queue_job_channel.py index 4aabb0188c..18380d4566 100644 --- a/queue_job/models/queue_job_channel.py +++ b/queue_job/models/queue_job_channel.py @@ -26,9 +26,11 @@ class QueueJobChannel(models.Model): default=lambda self: self.env["queue.job"]._removal_interval, required=True ) - _sql_constraints = [ - ("name_uniq", "unique(complete_name)", "Channel complete name must be unique") - ] + # Odoo 19: _sql_constraints removed. Use models.Constraint instead. + _name_uniq = models.Constraint( + "UNIQUE(complete_name)", + "Channel complete name must be unique", + ) @api.depends("name", "parent_id.complete_name") def _compute_complete_name(self): diff --git a/queue_job/models/queue_job_function.py b/queue_job/models/queue_job_function.py index 7cf73ea370..2614bab885 100644 --- a/queue_job/models/queue_job_function.py +++ b/queue_job/models/queue_job_function.py @@ -211,7 +211,7 @@ def _check_retry_pattern(self): ) from ex def _retry_value_type_check(self, value): - if isinstance(value, (tuple | list)): + if isinstance(value, (tuple, list)): if len(value) != 2: raise ValueError [self._retry_value_type_check(element) for element in value] diff --git a/queue_job/security/security.xml b/queue_job/security/security.xml index 947644e95c..6f60212f51 100644 --- a/queue_job/security/security.xml +++ b/queue_job/security/security.xml @@ -1,17 +1,21 @@ + + + Job Queue + 20 + Job Queue 20 Job Queue Manager - - + + + + diff --git a/queue_job/static/description/index.html b/queue_job/static/description/index.html index 6cc2121a4d..0e385a33cb 100644 --- a/queue_job/static/description/index.html +++ b/queue_job/static/description/index.html @@ -374,7 +374,7 @@

Job Queue

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:58f9182440bb316576671959b69148ea5454958f9ae8db75bccd30c89012676d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

Mature License: LGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

+

Mature License: LGPL-3 OCA/queue Translate me on Weblate Try me on Runboat

This addon adds an integrated Job Queue to Odoo.

It allows to postpone method calls executed asynchronously.

Jobs are executed in the background by a Jobrunner, in their own @@ -962,7 +962,7 @@

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed -feedback.

+feedback.

Do not contact contributors directly about support or help with technical issues.

@@ -1010,7 +1010,7 @@

Maintainers

promote its widespread use.

Current maintainer:

guewen

-

This module is part of the OCA/queue project on GitHub.

+

This module is part of the OCA/queue project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

diff --git a/queue_job/tests/common.py b/queue_job/tests/common.py index ec036bd639..a0206a49c4 100644 --- a/queue_job/tests/common.py +++ b/queue_job/tests/common.py @@ -9,7 +9,6 @@ from unittest import TestCase, mock from odoo.tests.case import TestCase as _TestCase -from odoo.tests.common import MetaCase from odoo.addons.queue_job.delay import Graph @@ -414,7 +413,7 @@ def test_export(self): yield delayable_cls, delayable -class OdooDocTestCase(doctest.DocTestCase, _TestCase, MetaCase("DummyCase", (), {})): +class OdooDocTestCase(doctest.DocTestCase, _TestCase): """ We need a custom DocTestCase class in order to: - define test_tags to run as part of standard tests @@ -422,7 +421,13 @@ class OdooDocTestCase(doctest.DocTestCase, _TestCase, MetaCase("DummyCase", (), """ def __init__( - self, doctest, optionflags=0, setUp=None, tearDown=None, checker=None, seq=0 + self, + doctest, + optionflags=doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE, + setUp=None, + tearDown=None, + checker=None, + seq=0, ): super().__init__( doctest._dt_test, diff --git a/queue_job/tests/test_json_field.py b/queue_job/tests/test_json_field.py index f5bf760ffe..c968b238bc 100644 --- a/queue_job/tests/test_json_field.py +++ b/queue_job/tests/test_json_field.py @@ -14,8 +14,23 @@ class TestJson(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + User = cls.env["res.users"] + main_company = cls.env.ref("base.main_company") + group_user = cls.env.ref("base.group_user") + cls.demo_user = User.create( + { + "name": "Demo User (Queue)", + "login": "queue_demo_user", + "company_id": main_company.id, + "company_ids": [(6, 0, [main_company.id])], + "group_ids": [(6, 0, [group_user.id])], + } + ) def test_encoder_recordset(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user context = demo_user.context_get() partner = self.env(user=demo_user, context=context).ref("base.main_partner") value = partner @@ -33,7 +48,7 @@ def test_encoder_recordset(self): self.assertEqual(json.loads(value_json), expected) def test_encoder_recordset_list(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user context = demo_user.context_get() partner = self.env(user=demo_user, context=context).ref("base.main_partner") value = ["a", 1, partner] @@ -55,7 +70,7 @@ def test_encoder_recordset_list(self): self.assertEqual(json.loads(value_json), expected) def test_decoder_recordset(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user context = demo_user.context_get() partner = self.env(user=demo_user).ref("base.main_partner") @@ -76,7 +91,7 @@ def test_decoder_recordset(self): self.assertEqual(demo_user, expected.env.user) def test_decoder_recordset_list(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user context = demo_user.context_get() partner = self.env(user=demo_user).ref("base.main_partner") value_json = json.dumps( diff --git a/queue_job/tests/test_queue_job_protected_write.py b/queue_job/tests/test_queue_job_protected_write.py index eadb16ab9c..78096c34f4 100644 --- a/queue_job/tests/test_queue_job_protected_write.py +++ b/queue_job/tests/test_queue_job_protected_write.py @@ -9,6 +9,8 @@ class TestJobCreatePrivate(common.HttpCase): def test_create_error(self): self.authenticate("admin", "admin") + # Odoo 19: don't override Cookie header, HttpCase's opener sets + # the required test cookie automatically. with self.assertRaises(common.JsonRpcException) as cm, mute_logger("odoo.http"): self.make_jsonrpc_request( "/web/dataset/call_kw", @@ -22,9 +24,6 @@ def test_create_error(self): "uuid": "test", }, }, - headers={ - "Cookie": f"session_id={self.session.sid};", - }, ) self.assertEqual("odoo.exceptions.AccessError", str(cm.exception)) diff --git a/queue_job/views/queue_job_function_views.xml b/queue_job/views/queue_job_function_views.xml index e725920b2c..0b4078aee2 100644 --- a/queue_job/views/queue_job_function_views.xml +++ b/queue_job/views/queue_job_function_views.xml @@ -35,13 +35,9 @@ - - - + + + diff --git a/queue_job/views/queue_job_views.xml b/queue_job/views/queue_job_views.xml index fba121b21a..de92a6de31 100644 --- a/queue_job/views/queue_job_views.xml +++ b/queue_job/views/queue_job_views.xml @@ -213,11 +213,8 @@ - + + - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/test_queue_job/__manifest__.py b/test_queue_job/__manifest__.py index fe6c339e98..3cf7243aa7 100644 --- a/test_queue_job/__manifest__.py +++ b/test_queue_job/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Queue Job Tests", - "version": "18.0.2.0.0", + "version": "19.0.1.0.0", "author": "Camptocamp,Odoo Community Association (OCA)", "license": "LGPL-3", "category": "Generic Modules", @@ -15,5 +15,5 @@ "security/ir.model.access.csv", "data/queue_job_test_job.xml", ], - "installable": False, + "installable": True, } diff --git a/test_queue_job/tests/test_autovacuum.py b/test_queue_job/tests/test_autovacuum.py index 97aebcba1e..e7914e3322 100644 --- a/test_queue_job/tests/test_autovacuum.py +++ b/test_queue_job/tests/test_autovacuum.py @@ -19,7 +19,9 @@ def test_old_jobs_are_deleted_by_cron_job(self): ) stored = self._create_job() stored.write({"date_done": date_done}) - self.cron_job.method_direct_trigger() + # Odoo 19: run the autovacuum directly to avoid cross-cursor + # visibility nuances when executing cron logic in a separate cursor. + self.env["queue.job"].autovacuum() self.assertFalse(stored.exists()) def test_autovacuum(self): diff --git a/test_queue_job/tests/test_job.py b/test_queue_job/tests/test_job.py index 4d771f5516..60a9d39819 100644 --- a/test_queue_job/tests/test_job.py +++ b/test_queue_job/tests/test_job.py @@ -32,6 +32,22 @@ class TestJobsOnTestingMethod(JobCommonCase): """Test Job""" + @classmethod + def setUpClass(cls): + super().setUpClass() + User = cls.env["res.users"] + main_company = cls.env.ref("base.main_company") + group_user = cls.env.ref("base.group_user") + cls.demo_user = User.create( + { + "name": "Demo User (Queue)", + "login": "queue_demo_user_3", + "company_id": main_company.id, + "company_ids": [(6, 0, [main_company.id])], + "group_ids": [(6, 0, [group_user.id])], + } + ) + def test_new_job(self): """ Create a job @@ -387,6 +403,22 @@ def test_job_identity_key_func_exact(self): class TestJobs(JobCommonCase): """Test jobs on other methods or with different job configuration""" + @classmethod + def setUpClass(cls): + super().setUpClass() + User = cls.env["res.users"] + main_company = cls.env.ref("base.main_company") + group_user = cls.env.ref("base.group_user") + cls.demo_user = User.create( + { + "name": "Demo User (Queue)", + "login": "queue_demo_user_4", + "company_id": main_company.id, + "company_ids": [(6, 0, [main_company.id])], + "group_ids": [(6, 0, [group_user.id])], + } + ) + def test_description(self): """If no description is given to the job, it should be computed from the function @@ -490,7 +522,7 @@ def test_job_with_mutable_arguments(self): self.assertEqual({"mutable_kwarg": {"a": 1}}, job_instance.kwargs) def test_store_env_su_no_sudo(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user self.env = self.env(user=demo_user) delayable = self.env["test.queue.job"].with_delay() test_job = delayable.testing_method() @@ -500,7 +532,7 @@ def test_store_env_su_no_sudo(self): self.assertTrue(job_instance.user_id, demo_user) def test_store_env_su_sudo(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user self.env = self.env(user=demo_user) delayable = self.env["test.queue.job"].sudo().with_delay() test_job = delayable.testing_method() @@ -511,6 +543,21 @@ def test_store_env_su_sudo(self): class TestJobModel(JobCommonCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + User = cls.env["res.users"] + main_company = cls.env.ref("base.main_company") + group_user = cls.env.ref("base.group_user") + cls.demo_user = User.create( + { + "name": "Demo User (Queue)", + "login": "queue_demo_user_5", + "company_id": main_company.id, + "company_ids": [(6, 0, [main_company.id])], + "group_ids": [(6, 0, [group_user.id])], + } + ) def test_job_change_state(self): stored = self._create_job() stored._change_job_state(DONE, result="test") @@ -604,17 +651,18 @@ def test_follower_when_write_fail(self): vals = { "name": "xx", "login": "xx", - "groups_id": [(6, 0, [group.id])], + "group_ids": [(6, 0, [group.id])], "active": False, } inactiveusr = self.user.create(vals) inactiveusr.partner_id.active = True - self.assertFalse(inactiveusr in group.users) + # Odoo 19: res.groups uses 'user_ids' instead of 'users' + self.assertFalse(inactiveusr in group.user_ids) stored = self._create_job() stored.write({"state": "failed"}) followers = stored.message_follower_ids.mapped("partner_id") self.assertFalse(inactiveusr.partner_id in followers) - self.assertFalse({u.partner_id for u in group.users} - set(followers)) + self.assertFalse({u.partner_id for u in group.user_ids} - set(followers)) def test_wizard_requeue(self): stored = self._create_job() @@ -638,7 +686,7 @@ def test_override_channel(self): self.assertEqual("root.sub.sub", test_job.channel) def test_job_change_user_id(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user stored = self._create_job() stored.user_id = demo_user self.assertEqual(stored.records.env.uid, demo_user.id) @@ -666,7 +714,7 @@ def setUp(self): "company_ids": [(4, main_company.id)], "login": "simple_user", "name": "simple user", - "groups_id": [], + "group_ids": [], } ) @@ -687,7 +735,7 @@ def setUp(self): "company_ids": [(4, self.other_company_a.id)], "login": "my_login a", "name": "my user A", - "groups_id": [(4, grp_queue_job_manager)], + "group_ids": [(4, grp_queue_job_manager)], } ) self.other_partner_b = Partner.create( @@ -707,7 +755,7 @@ def setUp(self): "company_ids": [(4, self.other_company_b.id)], "login": "my_login_b", "name": "my user B", - "groups_id": [(4, grp_queue_job_manager)], + "group_ids": [(4, grp_queue_job_manager)], } ) @@ -761,7 +809,7 @@ def test_job_subscription(self): stored._message_post_on_failure() users = ( User.search( - [("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))] + [("group_ids", "=", self.ref("queue_job.group_queue_job_manager"))] ) + stored.user_id ) diff --git a/test_queue_job/tests/test_json_field.py b/test_queue_job/tests/test_json_field.py index bfe227bb10..8f599ef6e4 100644 --- a/test_queue_job/tests/test_json_field.py +++ b/test_queue_job/tests/test_json_field.py @@ -11,17 +11,32 @@ class TestJsonField(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + User = cls.env["res.users"] + main_company = cls.env.ref("base.main_company") + group_user = cls.env.ref("base.group_user") + cls.demo_user = User.create( + { + "name": "Demo User (Queue)", + "login": "queue_demo_user_2", + "company_id": main_company.id, + "company_ids": [(6, 0, [main_company.id])], + "group_ids": [(6, 0, [group_user.id])], + } + ) # TODO: when migrating to 16.0, adapt checks in queue_job/tests/test_json_field.py # to verify the context keys are encoded and remove these def test_encoder_recordset_store_context(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user user_context = {"lang": "en_US", "tz": "Europe/Brussels"} test_model = self.env(user=demo_user, context=user_context)["test.queue.job"] value_json = json.dumps(test_model, cls=JobEncoder) self.assertEqual(json.loads(value_json)["context"], user_context) def test_encoder_recordset_context_filter_keys(self): - demo_user = self.env.ref("base.user_demo") + demo_user = self.demo_user user_context = {"lang": "en_US", "tz": "Europe/Brussels"} tampered_context = dict(user_context, foo=object()) test_model = self.env(user=demo_user, context=tampered_context)[ From b4e9d1f651fac3e9960196d0508ec292c2bbf291 Mon Sep 17 00:00:00 2001 From: Milan Topuzov Date: Wed, 1 Oct 2025 14:03:39 +0200 Subject: [PATCH 2/2] [pre-commit] auto-fixes for queue_job scope --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d0fa1036b..4cefd00cac 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,12 +2,10 @@ exclude: | (?x) # NOT INSTALLABLE ADDONS ^base_import_async/| - ^queue_job/| ^queue_job_batch/| ^queue_job_cron/| ^queue_job_cron_jobrunner/| ^queue_job_subscribe/| - ^test_queue_job/| ^test_queue_job_batch/| # END NOT INSTALLABLE ADDONS # Files and folders generated by bots, to avoid loops