Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

View file

@ -0,0 +1,46 @@
# Queue Job Tests
Odoo addon: test_queue_job
## Installation
```bash
pip install odoo-bringout-oca-queue-test_queue_job
```
## Dependencies
This addon depends on:
- queue_job
## Manifest Information
- **Name**: Queue Job Tests
- **Version**: 16.0.2.3.0
- **Category**: Generic Modules
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/queue](https://github.com/OCA/queue) branch 16.0, addon `test_queue_job`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Test_queue_job Module - test_queue_job
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for test_queue_job. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [queue_job](../../odoo-bringout-oca-queue-queue_job)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon test_queue_job or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-queue-test_queue_job"
# or
uv pip install odoo-bringout-oca-queue-test_queue_job"
```

View file

@ -0,0 +1,15 @@
# Models
Detected core models and extensions in test_queue_job.
```mermaid
classDiagram
class test_queue_channel
class test_queue_job
class test_related_action
class queue_job
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: test_queue_job. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon test_queue_job
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,34 @@
# Security
Access control and security definitions in test_queue_job.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../test_queue_job/security/ir.model.access.csv)**
- 3 model access rules
## Record Rules
Row-level security rules defined in:
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../test_queue_job/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon test_queue_job
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-queue-test_queue_job"
version = "16.0.0"
description = "Queue Job Tests - Odoo addon"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-queue-queue_job>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["test_queue_job"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,70 @@
.. image:: https://img.shields.io/badge/licence-LGPL--3-blue.svg
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
==============
Test Job Queue
==============
This addon is not meant to be used. It extends the Odoo Models
in order to run automated tests on the jobs queue.
The basic tests are integrated with the ``queue_job`` addon,
but for the tests that need several job methods are done in
this module to avoid to pollute the Models.
Installation
============
Nothing particular.
Usage
=====
This module only contains Python tests.
Known issues / Roadmap
======================
Bug Tracker
===========
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/queue/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smashing it by providing a detailed and welcomed feedback.
Credits
=======
Images
------
* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.
Contributors
------------
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Stéphane Bidoul <stephane.bidoul@acsone.eu>
* Matthieu Dietrich <matthieu.dietrich@camptocamp.com>
* Jos De Graeve <Jos.DeGraeve@apertoso.be>
* David Lefever <dl@taktik.be>
* Laurent Mignon <laurent.mignon@acsone.eu>
* Laetitia Gangloff <laetitia.gangloff@acsone.eu>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,18 @@
# Copyright 2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
{
"name": "Queue Job Tests",
"version": "16.0.2.3.0",
"author": "Camptocamp,Odoo Community Association (OCA)",
"license": "LGPL-3",
"category": "Generic Modules",
"depends": ["queue_job"],
"website": "https://github.com/OCA/queue",
"data": [
"data/queue_job_channel_data.xml",
"data/queue_job_function_data.xml",
"security/ir.model.access.csv",
],
"installable": True,
}

View file

@ -0,0 +1,10 @@
<odoo noupdate="1">
<record model="queue.job.channel" id="channel_sub">
<field name="name">sub</field>
<field name="parent_id" ref="queue_job.channel_root" />
</record>
<record model="queue.job.channel" id="channel_subsub">
<field name="name">subsub</field>
<field name="parent_id" ref="channel_sub" />
</record>
</odoo>

View file

@ -0,0 +1,65 @@
<odoo noupdate="1">
<record id="job_function_test_queue_job_testing_method" model="queue.job.function">
<field name="model_id" ref="test_queue_job.model_test_queue_job" />
<field name="method">testing_method</field>
<field name="related_action" eval='{"func_name": "testing_related_method"}' />
</record>
<record
id="job_function_test_queue_job_job_with_retry_pattern"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_queue_job" />
<field name="method">job_with_retry_pattern</field>
<field name="retry_pattern" eval="{1: 60, 2: 180, 3: 10, 5: 300}" />
</record>
<record
id="job_function_test_queue_job_job_with_retry_pattern__no_zero"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_queue_job" />
<field name="method">job_with_retry_pattern__no_zero</field>
<field name="retry_pattern" eval="{3: 180}" />
</record>
<record
id="job_function_test_queue_channel_job_sub_channel"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_queue_channel" />
<field name="method">job_sub_channel</field>
<field name="channel_id" ref="channel_subsub" />
</record>
<record id="job_function_test_queue_channel_job_a" model="queue.job.function">
<field name="model_id" ref="test_queue_job.model_test_queue_channel" />
<field name="method">job_a</field>
</record>
<record
id="job_function_test_related_action_testing_related_action__return_none"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_related_action" />
<field name="method">testing_related_action__return_none</field>
<field name="related_action" eval='{"enable": False}' />
</record>
<record
id="job_function_test_related_action_testing_related_action__kwargs"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_related_action" />
<field name="method">testing_related_action__kwargs</field>
<field
name="related_action"
eval='{"func_name": "testing_related_method", "kwargs": {"b": 4}}'
/>
</record>
<record
id="job_function_test_related_action_testing_related_action__store"
model="queue.job.function"
>
<field name="model_id" ref="test_queue_job.model_test_related_action" />
<field name="method">testing_related_action__store</field>
<field
name="related_action"
eval='{"func_name": "testing_related__url", "kwargs": {"url": "https://en.wikipedia.org/wiki/{subject}"}}'
/>
</record>
</odoo>

View file

@ -0,0 +1,93 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr "Dodatni podaci"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr "Kreirao"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr "Kreirano"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr "Prikazani naziv"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr "ID"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr "Zadnje mijenjano"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr "Zadnji ažurirao"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr "Zadnje ažurirano"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr "Naziv:"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr "Queue posao"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr "Test model za queue.channel"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr "Test model za queue.job"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr "Test model za povezane akcije"

View file

@ -0,0 +1,96 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2022-11-04 14:44+0000\n"
"Last-Translator: Maria Sparenberg <maria.sparenberg@gmx.net>\n"
"Language-Team: none\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.14.1\n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr "Erstellt von"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr "Erstellt am"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr "Anzeigename"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr "ID"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr "Zuletzt geändert am"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr "Zuletzt aktualisiert von"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr "Zuletzt aktualisiert am"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr "Bezeichnung"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr "Job einreihen"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr ""

View file

@ -0,0 +1,96 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-10-15 18:36+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr "Información Adicional"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr "Creado por"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr "Creado el"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr "Mostrar Nombre"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr "ID(identificación)"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr "Última Modificación el"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr "Actualizado por Última vez por"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr "Última Actualización el"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr "Nombre"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr "Cola de Trabajo"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr "Modelo de prueba para queue.channel"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr "Modelo de prueba para queue.job"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr "Modelo de prueba para acciones relacionadas"

View file

@ -0,0 +1,96 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-01-11 09:34+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.17\n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr "Informazioni aggiuntive"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr "Creato il"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr "ID"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr "Ultima modifica il"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr "Nome"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr "Lavoro in coda"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr "Prova modello per queue.channel"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr "Prova modello per queue.job"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr "Prova modello per azioni collegate"

View file

@ -0,0 +1,93 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr ""
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr ""

View file

@ -0,0 +1,96 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * test_queue_job
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 12.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2019-08-31 09:23+0000\n"
"Last-Translator: 黎伟杰 <674416404@qq.com>\n"
"Language-Team: none\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.8\n"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_queue_job__additional_info
msgid "Additional Info"
msgstr ""
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_uid
msgid "Created by"
msgstr "创建者"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__create_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__create_date
msgid "Created on"
msgstr "创建时间"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__display_name
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__display_name
msgid "Display Name"
msgstr "显示名称"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__id
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__id
msgid "ID"
msgstr "ID"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job____last_update
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action____last_update
msgid "Last Modified on"
msgstr "最后修改时间"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_uid
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_uid
msgid "Last Updated by"
msgstr "最后更新者"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_channel__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__write_date
#: model:ir.model.fields,field_description:test_queue_job.field_test_related_action__write_date
msgid "Last Updated on"
msgstr "最后更新时间"
#. module: test_queue_job
#: model:ir.model.fields,field_description:test_queue_job.field_test_queue_job__name
msgid "Name"
msgstr "名称"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_queue_job
msgid "Queue Job"
msgstr "队列作业"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_channel
msgid "Test model for queue.channel"
msgstr "测试模型 queue.channel"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_queue_job
msgid "Test model for queue.job"
msgstr "测试模型queue.job"
#. module: test_queue_job
#: model:ir.model,name:test_queue_job.model_test_related_action
msgid "Test model for related actions"
msgstr "测试模型的相关动作"

View file

@ -0,0 +1 @@
from . import test_models

View file

@ -0,0 +1,175 @@
# Copyright 2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
from odoo import api, fields, models
from odoo.addons.queue_job.delay import chain
from odoo.addons.queue_job.exception import RetryableJobError
from odoo.addons.queue_job.job import identity_exact
class QueueJob(models.Model):
_inherit = "queue.job"
additional_info = fields.Char()
def testing_related_method(self, **kwargs):
return self, kwargs
def testing_related__none(self, **kwargs):
return None
def testing_related__url(self, **kwargs):
assert "url" in kwargs, "url required"
subject = self.args[0]
return {
"type": "ir.actions.act_url",
"target": "new",
"url": kwargs["url"].format(subject=subject),
}
class ModelTestQueueJob(models.Model):
_name = "test.queue.job"
_description = "Test model for queue.job"
name = fields.Char()
# to test the context is serialized/deserialized properly
@api.model
def _job_prepare_context_before_enqueue_keys(self):
return ("tz", "lang", "allowed_company_ids")
def testing_method(self, *args, **kwargs):
"""Method used for tests
Return always the arguments and keyword arguments received
"""
if kwargs.get("raise_retry"):
raise RetryableJobError("Must be retried later")
if kwargs.get("return_context"):
return self.env.context
return args, kwargs
def create_ir_logging(self, message, level="info"):
return self.env["ir.logging"].create(
{
"name": "test_queue_job",
"type": "server",
"dbname": self.env.cr.dbname,
"message": message,
"path": "job",
"func": "create_ir_logging",
"line": 1,
}
)
def no_description(self):
return
def job_with_retry_pattern(self):
return
def job_with_retry_pattern__no_zero(self):
return
def mapped(self, func):
return super().mapped(func)
def job_alter_mutable(self, mutable_arg, mutable_kwarg=None):
mutable_arg.append(2)
mutable_kwarg["b"] = 2
return mutable_arg, mutable_kwarg
def delay_me(self, arg, kwarg=None):
return arg, kwarg
def delay_me_options_job_options(self):
return {
"identity_key": "my_job_identity",
}
def delay_me_options(self):
return "ok"
def delay_me_context_key(self):
return "ok"
def _register_hook(self):
self._patch_method("delay_me", self._patch_job_auto_delay("delay_me"))
self._patch_method(
"delay_me_options", self._patch_job_auto_delay("delay_me_options")
)
self._patch_method(
"delay_me_context_key",
self._patch_job_auto_delay(
"delay_me_context_key", context_key="auto_delay_delay_me_context_key"
),
)
return super()._register_hook()
def _job_store_values(self, job):
value = "JUST_TESTING"
if job.state == "failed":
value += "_BUT_FAILED"
return {"additional_info": value}
def button_that_uses_with_delay(self):
self.with_delay(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
).testing_method(1, foo=2)
def button_that_uses_delayable_chain(self):
delayables = chain(
self.delayable(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
).testing_method(1, foo=2),
self.delayable().testing_method("x", foo="y"),
self.delayable().no_description(),
)
delayables.delay()
class ModelTestQueueChannel(models.Model):
_name = "test.queue.channel"
_description = "Test model for queue.channel"
def job_a(self):
return
def job_b(self):
return
def job_sub_channel(self):
return
class ModelTestRelatedAction(models.Model):
_name = "test.related.action"
_description = "Test model for related actions"
def testing_related_action__no(self):
return
def testing_related_action__return_none(self):
return
def testing_related_action__kwargs(self):
return
def testing_related_action__store(self):
return

View file

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_test_queue_job,access_test_queue_job,model_test_queue_job,,1,1,1,1
access_test_queue_channel,access_test_queue_channel,model_test_queue_channel,,1,1,1,1
access_test_related_action,access_test_related_action,model_test_related_action,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_test_queue_job access_test_queue_job model_test_queue_job 1 1 1 1
3 access_test_queue_channel access_test_queue_channel model_test_queue_channel 1 1 1 1
4 access_test_related_action access_test_related_action model_test_related_action 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -0,0 +1,9 @@
from . import test_autovacuum
from . import test_delayable
from . import test_dependencies
from . import test_job
from . import test_job_auto_delay
from . import test_job_channels
from . import test_job_function
from . import test_related_actions
from . import test_delay_mocks

View file

@ -0,0 +1,22 @@
# Copyright 2016-2019 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
from odoo.tests import common
from odoo.addons.queue_job.job import Job
class JobCommonCase(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.queue_job = cls.env["queue.job"]
cls.user = cls.env["res.users"]
cls.method = cls.env["test.queue.job"].testing_method
def _create_job(self):
test_job = Job(self.method)
test_job.store()
stored = Job.db_record_from_uuid(self.env, test_job.uuid)
self.assertEqual(len(stored), 1)
return stored

View file

@ -0,0 +1,58 @@
# Copyright 2019 Versada UAB
# License LGPL-3 or later (https://www.gnu.org/licenses/lgpl).
from datetime import datetime, timedelta
from .common import JobCommonCase
class TestQueueJobAutovacuumCronJob(JobCommonCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.cron_job = cls.env.ref("queue_job.ir_cron_autovacuum_queue_jobs")
def test_old_jobs_are_deleted_by_cron_job(self):
"""Old jobs are deleted by the autovacuum cron job."""
date_done = datetime.now() - timedelta(
days=self.queue_job._removal_interval + 1
)
stored = self._create_job()
stored.write({"date_done": date_done})
self.cron_job.method_direct_trigger()
self.assertFalse(stored.exists())
def test_autovacuum(self):
# test default removal interval
stored = self._create_job()
date_done = datetime.now() - timedelta(days=29)
stored.write({"date_done": date_done})
self.env["queue.job"].autovacuum()
self.assertEqual(len(self.env["queue.job"].search([])), 1)
date_done = datetime.now() - timedelta(days=31)
stored.write({"date_done": date_done})
self.env["queue.job"].autovacuum()
self.assertEqual(len(self.env["queue.job"].search([])), 0)
def test_autovacuum_multi_channel(self):
root_channel = self.env.ref("queue_job.channel_root")
channel_60days = self.env["queue.job.channel"].create(
{"name": "60days", "removal_interval": 60, "parent_id": root_channel.id}
)
date_done = datetime.now() - timedelta(days=31)
job_root = self._create_job()
job_root.write({"date_done": date_done})
job_60days = self._create_job()
job_60days.write(
{"channel": channel_60days.complete_name, "date_done": date_done}
)
self.assertEqual(len(self.env["queue.job"].search([])), 2)
self.env["queue.job"].autovacuum()
self.assertEqual(len(self.env["queue.job"].search([])), 1)
date_done = datetime.now() - timedelta(days=61)
job_60days.write({"date_done": date_done})
self.env["queue.job"].autovacuum()
self.assertEqual(len(self.env["queue.job"].search([])), 0)

View file

@ -0,0 +1,373 @@
# Copyright 2021 Guewen Baconnier
# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import os
from unittest import mock
import odoo.tests.common as common
from odoo.tools import mute_logger
from odoo.addons.queue_job.delay import Delayable
from odoo.addons.queue_job.job import identity_exact
from odoo.addons.queue_job.tests.common import mock_with_delay, trap_jobs
class TestDelayMocks(common.TransactionCase):
def test_trap_jobs_on_with_delay_model(self):
with trap_jobs() as trap:
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
trap.assert_jobs_count(1, only=self.env["test.queue.job"].testing_method)
trap.assert_enqueued_job(
self.env["test.queue.job"].testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
def test_trap_jobs_on_with_delay_recordset(self):
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
recordset.button_that_uses_with_delay()
trap.assert_jobs_count(1)
trap.assert_jobs_count(1, only=recordset.testing_method)
trap.assert_enqueued_job(
recordset.testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
def test_trap_jobs_on_with_delay_recordset_no_properties(self):
"""Verify that trap_jobs() can omit properties"""
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
recordset.button_that_uses_with_delay()
trap.assert_enqueued_job(
recordset.testing_method,
args=(1,),
kwargs={"foo": 2},
)
def test_trap_jobs_on_with_delay_recordset_partial_properties(self):
"""Verify that trap_jobs() can check partially properties"""
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
recordset.button_that_uses_with_delay()
trap.assert_enqueued_job(
recordset.testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
description="Test",
eta=15,
),
)
def test_trap_with_identity_key(self):
with trap_jobs() as trap:
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
trap.assert_jobs_count(1, only=self.env["test.queue.job"].testing_method)
trap.assert_enqueued_job(
self.env["test.queue.job"].testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
# Should not enqueue again
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
trap.perform_enqueued_jobs()
# Should no longer be enqueued
trap.assert_jobs_count(0)
# Can now requeue
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
def test_trap_jobs_on_with_delay_assert_model_count_mismatch(self):
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
with self.assertRaises(AssertionError, msg="0 != 1"):
trap.assert_jobs_count(1, only=recordset.testing_method)
def test_trap_jobs_on_with_delay_assert_recordset_count_mismatch(self):
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
recordset.button_that_uses_with_delay()
trap.assert_jobs_count(1)
with self.assertRaises(AssertionError, msg="0 != 1"):
trap.assert_jobs_count(
1, only=self.env["test.queue.job"].testing_method
)
def test_trap_jobs_on_with_delay_assert_model_enqueued_mismatch(self):
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
recordset.button_that_uses_with_delay()
trap.assert_jobs_count(1)
message = (
r"Job <test\.queue.job\(\)>\.testing_method\(1, foo=2\) with "
r"properties \(channel=root\.test, description=Test, eta=15, "
"identity_key=<function identity_exact at 0x[0-9a-fA-F]+>, "
"max_retries=1, priority=15\\) was not enqueued\\.\n"
"Actual enqueued jobs:\n"
r" \* <test.queue.job\(%s,\)>.testing_method\(1, foo=2\) with properties "
r"\(priority=15, max_retries=1, eta=15, description=Test, channel=root.test, "
r"identity_key=<function identity_exact at 0x[0-9a-fA-F]+>\)"
) % (recordset.id,)
with self.assertRaisesRegex(AssertionError, message):
trap.assert_enqueued_job(
self.env["test.queue.job"].testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
def test_trap_jobs_on_with_delay_assert_recordset_enqueued_mismatch(self):
recordset = self.env["test.queue.job"].create({"name": "test"})
with trap_jobs() as trap:
self.env["test.queue.job"].button_that_uses_with_delay()
trap.assert_jobs_count(1)
message = (
r"Job <test\.queue.job\(%s,\)>\.testing_method\(1, foo=2\) with "
r"properties \(channel=root\.test, description=Test, eta=15, "
"identity_key=<function identity_exact at 0x[0-9a-fA-F]+>, "
"max_retries=1, priority=15\\) was not enqueued\\.\n"
"Actual enqueued jobs:\n"
r" \* <test.queue.job\(\)>.testing_method\(1, foo=2\) with properties "
r"\(priority=15, max_retries=1, eta=15, description=Test, channel=root.test, "
r"identity_key=<function identity_exact at 0x[0-9a-fA-F]+>\)"
) % (recordset.id,)
with self.assertRaisesRegex(AssertionError, message):
trap.assert_enqueued_job(
recordset.testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
def test_trap_jobs_on_graph(self):
with trap_jobs() as trap:
self.env["test.queue.job"].button_that_uses_delayable_chain()
trap.assert_jobs_count(3)
trap.assert_jobs_count(2, only=self.env["test.queue.job"].testing_method)
trap.assert_jobs_count(1, only=self.env["test.queue.job"].no_description)
trap.assert_enqueued_job(
self.env["test.queue.job"].testing_method,
args=(1,),
kwargs={"foo": 2},
properties=dict(
channel="root.test",
description="Test",
eta=15,
identity_key=identity_exact,
max_retries=1,
priority=15,
),
)
trap.assert_enqueued_job(
self.env["test.queue.job"].testing_method,
args=("x",),
kwargs={"foo": "y"},
)
trap.assert_enqueued_job(
self.env["test.queue.job"].no_description,
)
trap.perform_enqueued_jobs()
def test_trap_jobs_perform(self):
with trap_jobs() as trap:
model = self.env["test.queue.job"]
model.with_delay(priority=1).create_ir_logging(
"test_trap_jobs_perform single"
)
node = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 1")
node2 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 2")
node3 = Delayable(model).create_ir_logging("test_trap_jobs_perform graph 3")
node2.on_done(node3)
node3.on_done(node)
node2.delay()
# jobs are not executed
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 0)
trap.assert_jobs_count(4)
# perform the jobs
trap.perform_enqueued_jobs()
trap.assert_jobs_count(0)
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 4)
# check if they are executed in order
self.assertEqual(logs[0].message, "test_trap_jobs_perform single")
self.assertEqual(logs[1].message, "test_trap_jobs_perform graph 2")
self.assertEqual(logs[2].message, "test_trap_jobs_perform graph 3")
self.assertEqual(logs[3].message, "test_trap_jobs_perform graph 1")
def test_mock_with_delay(self):
with mock_with_delay() as (delayable_cls, delayable):
self.env["test.queue.job"].button_that_uses_with_delay()
self.assertEqual(delayable_cls.call_count, 1)
# arguments passed in 'with_delay()'
delay_args, delay_kwargs = delayable_cls.call_args
self.assertEqual(delay_args, (self.env["test.queue.job"],))
self.assertDictEqual(
delay_kwargs,
{
"channel": "root.test",
"description": "Test",
"eta": 15,
"identity_key": identity_exact,
"max_retries": 1,
"priority": 15,
},
)
# check what's passed to the job method 'testing_method'
self.assertEqual(delayable.testing_method.call_count, 1)
delay_args, delay_kwargs = delayable.testing_method.call_args
self.assertEqual(delay_args, (1,))
self.assertDictEqual(delay_kwargs, {"foo": 2})
@mute_logger("odoo.addons.queue_job.utils")
@mock.patch.dict(os.environ, {"QUEUE_JOB__NO_DELAY": "1"})
def test_delay_graph_direct_exec_env_var(self):
node = Delayable(self.env["test.queue.job"]).create_ir_logging(
"test_delay_graph_direct_exec 1"
)
node2 = Delayable(self.env["test.queue.job"]).create_ir_logging(
"test_delay_graph_direct_exec 2"
)
node2.on_done(node)
node2.delay()
# jobs are executed directly
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 2)
# check if they are executed in order
self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2")
self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1")
@mute_logger("odoo.addons.queue_job.utils")
def test_delay_graph_direct_exec_context_key(self):
node = Delayable(
self.env["test.queue.job"].with_context(queue_job__no_delay=True)
).create_ir_logging("test_delay_graph_direct_exec 1")
node2 = Delayable(self.env["test.queue.job"]).create_ir_logging(
"test_delay_graph_direct_exec 2"
)
node2.on_done(node)
node2.delay()
# jobs are executed directly
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 2)
# check if they are executed in order
self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 2")
self.assertEqual(logs[1].message, "test_delay_graph_direct_exec 1")
@mute_logger("odoo.addons.queue_job.utils")
@mock.patch.dict(os.environ, {"QUEUE_JOB__NO_DELAY": "1"})
def test_delay_with_delay_direct_exec_env_var(self):
model = self.env["test.queue.job"]
model.with_delay().create_ir_logging("test_delay_graph_direct_exec 1")
# jobs are executed directly
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 1)
self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1")
@mute_logger("odoo.addons.queue_job.utils")
def test_delay_with_delay_direct_exec_context_key(self):
model = self.env["test.queue.job"].with_context(queue_job__no_delay=True)
model.with_delay().create_ir_logging("test_delay_graph_direct_exec 1")
# jobs are executed directly
logs = self.env["ir.logging"].search(
[
("name", "=", "test_queue_job"),
("func", "=", "create_ir_logging"),
],
order="id asc",
)
self.assertEqual(len(logs), 1)
self.assertEqual(logs[0].message, "test_delay_graph_direct_exec 1")

View file

@ -0,0 +1,304 @@
# copyright 2019 Camptocamp
# Copyright 2019 Guewen Baconnier
# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import odoo.tests.common as common
from odoo.addons.queue_job.delay import (
Delayable,
DelayableChain,
DelayableGroup,
chain,
group,
)
class TestDelayable(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.queue_job = cls.env["queue.job"]
cls.test_model = cls.env["test.queue.job"]
cls.method = cls.env["test.queue.job"].testing_method
def job_node(self, id_):
return Delayable(self.test_model).testing_method(id_)
def assert_generated_job(self, *nodes):
for node in nodes:
self.assertTrue(node._generated_job)
job = node._generated_job
self.assertTrue(job.db_record().id)
def assert_depends_on(self, delayable, parent_delayables):
self.assertEqual(
delayable._generated_job._depends_on,
{parent._generated_job for parent in parent_delayables},
)
def assert_reverse_depends_on(self, delayable, child_delayables):
self.assertEqual(
set(delayable._generated_job._reverse_depends_on),
{child._generated_job for child in child_delayables},
)
def assert_dependencies(self, nodes):
reverse_dependencies = {}
for child, parents in nodes.items():
self.assert_depends_on(child, parents)
for parent in parents:
reverse_dependencies.setdefault(parent, set()).add(child)
for parent, children in reverse_dependencies.items():
self.assert_reverse_depends_on(parent, children)
def test_delayable_delay_single(self):
node = self.job_node(1)
node.delay()
self.assert_generated_job(node)
def test_delayable_delay_on_done(self):
node = self.job_node(1)
node2 = self.job_node(2)
node.on_done(node2).delay()
self.assert_generated_job(node, node2)
self.assert_dependencies({node: {}, node2: {node}})
def test_delayable_delay_done_multi(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node.on_done(node2, node3).delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies({node: {}, node2: {node}, node3: {node}})
def test_delayable_delay_group(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
DelayableGroup(node, node2, node3).delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies({node: {}, node2: {}, node3: {}})
def test_group_function(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
group(node, node2, node3).delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies({node: {}, node2: {}, node3: {}})
def test_delayable_delay_job_after_group(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
DelayableGroup(node, node2).on_done(node3).delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies({node: {}, node2: {}, node3: {node, node2}})
def test_delayable_delay_group_after_group(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
g1 = DelayableGroup(node, node2)
g2 = DelayableGroup(node3, node4)
g1.on_done(g2).delay()
self.assert_generated_job(node, node2, node3, node4)
self.assert_dependencies(
{
node: {},
node2: {},
node3: {node, node2},
node4: {node, node2},
}
)
def test_delayable_delay_implicit_group_after_group(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
g1 = DelayableGroup(node, node2).on_done(node3, node4)
g1.delay()
self.assert_generated_job(node, node2, node3, node4)
self.assert_dependencies(
{
node: {},
node2: {},
node3: {node, node2},
node4: {node, node2},
}
)
def test_delayable_delay_group_after_group_after_group(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
g1 = DelayableGroup(node)
g2 = DelayableGroup(node2)
g3 = DelayableGroup(node3)
g4 = DelayableGroup(node4)
g1.on_done(g2.on_done(g3.on_done(g4))).delay()
self.assert_generated_job(node, node2, node3, node4)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node2},
node4: {node3},
}
)
def test_delayable_diamond(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
g1 = DelayableGroup(node2, node3)
g1.on_done(node4)
node.on_done(g1)
node.delay()
self.assert_generated_job(node, node2, node3, node4)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node},
node4: {node2, node3},
}
)
def test_delayable_chain(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
c1 = DelayableChain(node, node2, node3)
c1.delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node2},
}
)
def test_chain_function(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
c1 = chain(node, node2, node3)
c1.delay()
self.assert_generated_job(node, node2, node3)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node2},
}
)
def test_delayable_chain_after_job(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
c1 = DelayableChain(node2, node3, node4)
node.on_done(c1)
node.delay()
self.assert_generated_job(node, node2, node3, node4)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node2},
node4: {node3},
}
)
def test_delayable_chain_after_chain(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
node5 = self.job_node(5)
node6 = self.job_node(6)
chain1 = DelayableChain(node, node2, node3)
chain2 = DelayableChain(node4, node5, node6)
chain1.on_done(chain2)
chain1.delay()
self.assert_generated_job(node, node2, node3, node4, node5, node6)
self.assert_dependencies(
{
node: {},
node2: {node},
node3: {node2},
node4: {node3},
node5: {node4},
node6: {node5},
}
)
def test_delayable_group_of_chain(self):
node = self.job_node(1)
node2 = self.job_node(2)
node3 = self.job_node(3)
node4 = self.job_node(4)
node5 = self.job_node(5)
node6 = self.job_node(6)
node7 = self.job_node(7)
node8 = self.job_node(8)
chain1 = DelayableChain(node, node2)
chain2 = DelayableChain(node3, node4)
chain3 = DelayableChain(node5, node6)
chain4 = DelayableChain(node7, node8)
g1 = DelayableGroup(chain1, chain2).on_done(chain3, chain4)
g1.delay()
self.assert_generated_job(
node,
node2,
node3,
node4,
node5,
node6,
node7,
node8,
)
self.assert_dependencies(
{
node: {},
node3: {},
node2: {node},
node4: {node3},
node5: {node4, node2},
node7: {node4, node2},
node6: {node5},
node8: {node7},
}
)
def test_log_not_delayed(self):
logger_name = "odoo.addons.queue_job"
with self.assertLogs(logger_name, level="WARN") as test:
# When a Delayable never gets a delay() call,
# when the GC collects it and calls __del__, a warning
# will be displayed. We cannot test this is a scenario
# using the GC as it isn't predictable. Call __del__
# directly
node = self.job_node(1)
node.__del__()
expected = (
"WARNING:odoo.addons.queue_job.delay:Delayable "
"Delayable(test.queue.job().testing_method((1,), {}))"
" was prepared but never delayed"
)
self.assertEqual(test.output, [expected])
def test_delay_job_already_exists(self):
node = self.job_node(1)
node2 = self.job_node(2)
node2.delay()
node.on_done(node2).delay()
self.assert_generated_job(node, node2)
self.assert_dependencies({node: {}, node2: {node}})

View file

@ -0,0 +1,289 @@
# Copyright 2019 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
import odoo.tests.common as common
from odoo.addons.queue_job.delay import DelayableGraph, chain, group
from odoo.addons.queue_job.job import PENDING, WAIT_DEPENDENCIES, Job
class TestJobDependencies(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.queue_job = cls.env["queue.job"]
cls.method = cls.env["test.queue.job"].testing_method
def test_depends_store(self):
job_root = Job(self.method)
job_lvl1_a = Job(self.method)
job_lvl1_a.add_depends({job_root})
job_lvl1_b = Job(self.method)
job_lvl1_b.add_depends({job_root})
job_lvl2_a = Job(self.method)
job_lvl2_a.add_depends({job_lvl1_a})
# Jobs must be stored after the dependencies are set up.
# (Or if not, a new store must be called on the parent)
job_root.store()
job_lvl1_a.store()
job_lvl1_b.store()
job_lvl2_a.store()
# test properties
self.assertFalse(job_root.depends_on)
self.assertEqual(job_lvl1_a.depends_on, {job_root})
self.assertEqual(job_lvl1_b.depends_on, {job_root})
self.assertEqual(job_lvl2_a.depends_on, {job_lvl1_a})
self.assertEqual(job_root.reverse_depends_on, {job_lvl1_a, job_lvl1_b})
self.assertEqual(job_lvl1_a.reverse_depends_on, {job_lvl2_a})
self.assertFalse(job_lvl1_b.reverse_depends_on)
self.assertFalse(job_lvl2_a.reverse_depends_on)
# test DB state
self.assertEqual(job_root.db_record().dependencies["depends_on"], [])
self.assertEqual(
sorted(job_root.db_record().dependencies["reverse_depends_on"]),
sorted([job_lvl1_a.uuid, job_lvl1_b.uuid]),
)
self.assertEqual(
job_lvl1_a.db_record().dependencies["depends_on"], [job_root.uuid]
)
self.assertEqual(
job_lvl1_a.db_record().dependencies["reverse_depends_on"], [job_lvl2_a.uuid]
)
self.assertEqual(
job_lvl1_b.db_record().dependencies["depends_on"], [job_root.uuid]
)
self.assertEqual(job_lvl1_b.db_record().dependencies["reverse_depends_on"], [])
self.assertEqual(
job_lvl2_a.db_record().dependencies["depends_on"], [job_lvl1_a.uuid]
)
self.assertEqual(job_lvl2_a.db_record().dependencies["reverse_depends_on"], [])
def test_depends_store_after(self):
job_root = Job(self.method)
job_root.store()
job_a = Job(self.method)
job_a.add_depends({job_root})
job_a.store()
# as the reverse dependency has been added after the root job has been
# stored, it is not reflected in DB
self.assertEqual(job_root.db_record().dependencies["reverse_depends_on"], [])
# a new store will write it
job_root.store()
self.assertEqual(
job_root.db_record().dependencies["reverse_depends_on"], [job_a.uuid]
)
def test_depends_load(self):
job_root = Job(self.method)
job_a = Job(self.method)
job_a.add_depends({job_root})
job_root.store()
job_a.store()
read_job_root = Job.load(self.env, job_root.uuid)
self.assertEqual(read_job_root.reverse_depends_on, {job_a})
read_job_a = Job.load(self.env, job_a.uuid)
self.assertEqual(read_job_a.depends_on, {job_root})
def test_depends_enqueue_waiting_single(self):
job_root = Job(self.method)
job_a = Job(self.method)
job_a.add_depends({job_root})
DelayableGraph._ensure_same_graph_uuid([job_root, job_a])
job_root.store()
job_a.store()
self.assertEqual(job_a.state, WAIT_DEPENDENCIES)
# these are the steps run by RunJobController
job_root.perform()
job_root.set_done()
job_root.store()
self.env.flush_all()
job_root.enqueue_waiting()
# Will be picked up by the jobrunner.
# Warning: as the state has been changed in memory but
# not in the job_a instance, here, we re-read it.
# In practice, it won't be an issue for the jobrunner.
self.assertEqual(Job.load(self.env, job_a.uuid).state, PENDING)
def test_dependency_graph(self):
job_root = Job(self.method)
job_lvl1_a = Job(self.method)
job_lvl1_a.add_depends({job_root})
job_lvl1_b = Job(self.method)
job_lvl1_b.add_depends({job_root})
job_lvl2_a = Job(self.method)
job_lvl2_a.add_depends({job_lvl1_a})
DelayableGraph._ensure_same_graph_uuid(
[
job_root,
job_lvl1_a,
job_lvl1_b,
job_lvl2_a,
]
)
job_2_root = Job(self.method)
job_2_child = Job(self.method)
job_2_child.add_depends({job_2_root})
DelayableGraph._ensure_same_graph_uuid([job_2_root, job_2_child])
# Jobs must be stored after the dependencies are set up.
# (Or if not, a new store must be called on the parent)
job_root.store()
job_lvl1_a.store()
job_lvl1_b.store()
job_lvl2_a.store()
job_2_root.store()
job_2_child.store()
record_root = job_root.db_record()
record_lvl1_a = job_lvl1_a.db_record()
record_lvl1_b = job_lvl1_b.db_record()
record_lvl2_a = job_lvl2_a.db_record()
record_2_root = job_2_root.db_record()
record_2_child = job_2_child.db_record()
expected_nodes = [
{
"id": record_root.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
{
"id": record_lvl1_a.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
{
"id": record_lvl1_b.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
{
"id": record_lvl2_a.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
]
expected_edges = sorted(
[
[record_root.id, record_lvl1_a.id],
[record_lvl1_a.id, record_lvl2_a.id],
[record_root.id, record_lvl1_b.id],
]
)
records = [record_root, record_lvl1_a, record_lvl1_b, record_lvl2_a]
for record in records:
self.assertEqual(
sorted(record.dependency_graph["nodes"], key=lambda d: d["id"]),
expected_nodes,
)
self.assertEqual(sorted(record.dependency_graph["edges"]), expected_edges)
expected_nodes = [
{
"id": record_2_root.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
{
"id": record_2_child.id,
"title": "<strong>Method used for tests</strong><br/>"
"test.queue.job().testing_method()",
"color": "#D2E5FF",
"border": "#2B7CE9",
"shadow": True,
},
]
expected_edges = sorted([[record_2_root.id, record_2_child.id]])
for record in [record_2_root, record_2_child]:
self.assertEqual(
sorted(record.dependency_graph["nodes"], key=lambda d: d["id"]),
expected_nodes,
)
self.assertEqual(sorted(record.dependency_graph["edges"]), expected_edges)
def test_no_dependency_graph_single_job(self):
"""A single job has no graph"""
job = self.env["test.queue.job"].with_delay().testing_method()
self.assertEqual(job.db_record().dependency_graph, {})
self.assertIsNone(job.graph_uuid)
def test_depends_graph_uuid(self):
"""All jobs in a graph share the same graph uuid"""
model = self.env["test.queue.job"]
delayable1 = model.delayable().testing_method(1)
delayable2 = model.delayable().testing_method(2)
delayable3 = model.delayable().testing_method(3)
delayable4 = model.delayable().testing_method(4)
group1 = group(delayable1, delayable2)
group2 = group(delayable3, delayable4)
chain_root = chain(group1, group2)
chain_root.delay()
jobs = [
delayable._generated_job
for delayable in [delayable1, delayable2, delayable3, delayable4]
]
self.assertTrue(jobs[0].graph_uuid)
self.assertEqual(len({j.graph_uuid for j in jobs}), 1)
for job in jobs:
self.assertEqual(job.graph_uuid, job.db_record().graph_uuid)
def test_depends_graph_uuid_group(self):
"""All jobs in a group share the same graph uuid"""
g = group(
self.env["test.queue.job"].delayable().testing_method(),
self.env["test.queue.job"].delayable().testing_method(),
)
g.delay()
jobs = [delayable._generated_job for delayable in g._delayables]
self.assertTrue(jobs[0].graph_uuid)
self.assertTrue(jobs[1].graph_uuid)
self.assertEqual(jobs[0].graph_uuid, jobs[1].graph_uuid)

View file

@ -0,0 +1,796 @@
# Copyright 2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import hashlib
from datetime import datetime, timedelta
from unittest import mock
import odoo.tests.common as common
from odoo.addons.queue_job import identity_exact
from odoo.addons.queue_job.delay import DelayableGraph
from odoo.addons.queue_job.exception import (
FailedJobError,
NoSuchJobError,
RetryableJobError,
)
from odoo.addons.queue_job.job import (
CANCELLED,
DONE,
ENQUEUED,
FAILED,
PENDING,
RETRY_INTERVAL,
STARTED,
WAIT_DEPENDENCIES,
Job,
)
from .common import JobCommonCase
class TestJobsOnTestingMethod(JobCommonCase):
"""Test Job"""
def test_new_job(self):
"""
Create a job
"""
test_job = Job(self.method)
self.assertEqual(test_job.func.__func__, self.method.__func__)
def test_eta(self):
"""When an `eta` is datetime, it uses it"""
now = datetime.now()
method = self.env["res.users"].mapped
job_a = Job(method, eta=now)
self.assertEqual(job_a.eta, now)
def test_eta_integer(self):
"""When an `eta` is an integer, it adds n seconds up to now"""
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
job_a = Job(self.method, eta=60)
self.assertEqual(job_a.eta, datetime(2015, 3, 15, 16, 42, 0))
def test_eta_timedelta(self):
"""When an `eta` is a timedelta, it adds it up to now"""
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
delta = timedelta(hours=3)
job_a = Job(self.method, eta=delta)
self.assertEqual(job_a.eta, datetime(2015, 3, 15, 19, 41, 0))
def test_perform_args(self):
test_job = Job(self.method, args=("o", "k"), kwargs={"c": "!"})
result = test_job.perform()
self.assertEqual(result, (("o", "k"), {"c": "!"}))
def test_retryable_error(self):
test_job = Job(self.method, kwargs={"raise_retry": True}, max_retries=3)
self.assertEqual(test_job.retry, 0)
with self.assertRaises(RetryableJobError):
test_job.perform()
self.assertEqual(test_job.retry, 1)
with self.assertRaises(RetryableJobError):
test_job.perform()
self.assertEqual(test_job.retry, 2)
with self.assertRaises(FailedJobError):
test_job.perform()
self.assertEqual(test_job.retry, 3)
def test_infinite_retryable_error(self):
test_job = Job(self.method, kwargs={"raise_retry": True}, max_retries=0)
self.assertEqual(test_job.retry, 0)
with self.assertRaises(RetryableJobError):
test_job.perform()
self.assertEqual(test_job.retry, 1)
def test_on_instance_method(self):
class A:
def method(self):
pass
with self.assertRaises(TypeError):
Job(A.method)
def test_on_model_method(self):
job_ = Job(self.env["test.queue.job"].testing_method)
self.assertEqual(job_.model_name, "test.queue.job")
self.assertEqual(job_.method_name, "testing_method")
def test_invalid_function(self):
with self.assertRaises(TypeError):
Job(1)
def test_set_pending(self):
job_a = Job(self.method)
job_a.set_pending(result="test")
self.assertEqual(job_a.state, PENDING)
self.assertFalse(job_a.date_enqueued)
self.assertFalse(job_a.date_started)
self.assertEqual(job_a.retry, 0)
self.assertEqual(job_a.result, "test")
def test_set_enqueued(self):
job_a = Job(self.method)
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
job_a.set_enqueued()
self.assertEqual(job_a.state, ENQUEUED)
self.assertEqual(job_a.date_enqueued, datetime(2015, 3, 15, 16, 41, 0))
self.assertFalse(job_a.date_started)
def test_set_started(self):
job_a = Job(self.method)
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
job_a.set_started()
self.assertEqual(job_a.state, STARTED)
self.assertEqual(job_a.date_started, datetime(2015, 3, 15, 16, 41, 0))
def test_worker_pid(self):
"""When a job is started, it gets the PID of the worker that starts it"""
method = self.env["res.users"].mapped
job_a = Job(method)
self.assertFalse(job_a.worker_pid)
with mock.patch("os.getpid", autospec=True) as mock_getpid:
mock_getpid.return_value = 99999
job_a.set_started()
self.assertEqual(job_a.worker_pid, 99999)
# reset the pid
job_a.set_pending()
self.assertFalse(job_a.worker_pid)
def test_set_done(self):
job_a = Job(self.method)
job_a.date_started = datetime(2015, 3, 15, 16, 40, 0)
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
job_a.set_done(result="test")
self.assertEqual(job_a.state, DONE)
self.assertEqual(job_a.result, "test")
self.assertEqual(job_a.date_done, datetime(2015, 3, 15, 16, 41, 0))
self.assertEqual(job_a.exec_time, 60.0)
self.assertFalse(job_a.exc_info)
def test_set_failed(self):
job_a = Job(self.method)
job_a.set_failed(
exc_info="failed test",
exc_name="FailedTest",
exc_message="Sadly this job failed",
)
self.assertEqual(job_a.state, FAILED)
self.assertEqual(job_a.exc_info, "failed test")
self.assertEqual(job_a.exc_name, "FailedTest")
self.assertEqual(job_a.exc_message, "Sadly this job failed")
def test_postpone(self):
job_a = Job(self.method)
datetime_path = "odoo.addons.queue_job.job.datetime"
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 3, 15, 16, 41, 0)
job_a.postpone(result="test", seconds=60)
self.assertEqual(job_a.eta, datetime(2015, 3, 15, 16, 42, 0))
self.assertEqual(job_a.result, "test")
self.assertFalse(job_a.exc_info)
def test_company_simple(self):
company = self.env.ref("base.main_company")
eta = datetime.now() + timedelta(hours=5)
test_job = Job(
self.env["test.queue.job"].with_company(company).testing_method,
args=("o", "k"),
kwargs={"return_context": 1},
priority=15,
eta=eta,
description="My description",
)
test_job.worker_pid = 99999 # normally set on "set_start"
test_job.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(test_job.func.__func__, job_read.func.__func__)
result_ctx = job_read.func(*tuple(test_job.args), **test_job.kwargs)
self.assertEqual(result_ctx.get("allowed_company_ids"), company.ids)
def test_company_complex(self):
company1 = self.env.ref("base.main_company")
company2 = company1.create({"name": "Queue job company"})
companies = company1 | company2
self.env.user.write({"company_ids": [(6, False, companies.ids)]})
# Ensure the main company still the first
self.assertEqual(self.env.user.company_id, company1)
eta = datetime.now() + timedelta(hours=5)
test_job = Job(
self.env["test.queue.job"].with_company(company2).testing_method,
args=("o", "k"),
kwargs={"return_context": 1},
priority=15,
eta=eta,
description="My description",
)
test_job.worker_pid = 99999 # normally set on "set_start"
test_job.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(test_job.func.__func__, job_read.func.__func__)
result_ctx = job_read.func(*tuple(test_job.args), **test_job.kwargs)
self.assertEqual(result_ctx.get("allowed_company_ids"), company2.ids)
def test_store(self):
test_job = Job(self.method)
test_job.store()
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
self.assertEqual(len(stored), 1)
def test_store_extra_data(self):
test_job = Job(self.method)
test_job.store()
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
self.assertEqual(stored.additional_info, "JUST_TESTING")
test_job.set_failed(exc_info="failed test", exc_name="FailedTest")
test_job.store()
stored.invalidate_recordset()
self.assertEqual(stored.additional_info, "JUST_TESTING_BUT_FAILED")
def test_read(self):
eta = datetime.now() + timedelta(hours=5)
test_job = Job(
self.method,
args=("o", "k"),
kwargs={"c": "!"},
priority=15,
eta=eta,
description="My description",
)
test_job.worker_pid = 99999 # normally set on "set_start"
test_job.company_id = self.env.ref("base.main_company").id
test_job.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(test_job.uuid, job_read.uuid)
self.assertEqual(test_job.model_name, job_read.model_name)
self.assertEqual(test_job.func.__func__, job_read.func.__func__)
self.assertEqual(test_job.args, job_read.args)
self.assertEqual(test_job.kwargs, job_read.kwargs)
self.assertEqual(test_job.method_name, job_read.method_name)
self.assertEqual(test_job.description, job_read.description)
self.assertEqual(test_job.state, job_read.state)
self.assertEqual(test_job.priority, job_read.priority)
self.assertEqual(test_job.exc_info, job_read.exc_info)
self.assertEqual(test_job.result, job_read.result)
self.assertEqual(test_job.user_id, job_read.user_id)
self.assertEqual(test_job.company_id, job_read.company_id)
self.assertEqual(test_job.worker_pid, 99999)
delta = timedelta(seconds=1) # DB does not keep milliseconds
self.assertAlmostEqual(
test_job.date_created, job_read.date_created, delta=delta
)
self.assertAlmostEqual(
test_job.date_started, job_read.date_started, delta=delta
)
self.assertAlmostEqual(
test_job.date_enqueued, job_read.date_enqueued, delta=delta
)
self.assertAlmostEqual(test_job.date_done, job_read.date_done, delta=delta)
self.assertAlmostEqual(test_job.eta, job_read.eta, delta=delta)
test_date = datetime(2015, 3, 15, 21, 7, 0)
job_read.date_enqueued = test_date
job_read.date_started = test_date
job_read.date_done = test_date
job_read.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertAlmostEqual(job_read.date_started, test_date, delta=delta)
self.assertAlmostEqual(job_read.date_enqueued, test_date, delta=delta)
self.assertAlmostEqual(job_read.date_done, test_date, delta=delta)
self.assertAlmostEqual(job_read.exec_time, 0.0)
def test_job_unlinked(self):
test_job = Job(self.method, args=("o", "k"), kwargs={"c": "!"})
test_job.store()
stored = self.queue_job.search([("uuid", "=", test_job.uuid)])
stored.unlink()
with self.assertRaises(NoSuchJobError):
Job.load(self.env, test_job.uuid)
def test_unicode(self):
test_job = Job(
self.method,
args=("öô¿‽", "ñě"),
kwargs={"c": "ßø"},
priority=15,
description="My dé^Wdescription",
)
test_job.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(test_job.args, job_read.args)
self.assertEqual(job_read.args, ("öô¿‽", "ñě"))
self.assertEqual(test_job.kwargs, job_read.kwargs)
self.assertEqual(job_read.kwargs, {"c": "ßø"})
self.assertEqual(test_job.description, job_read.description)
self.assertEqual(job_read.description, "My dé^Wdescription")
def test_accented_bytestring(self):
test_job = Job(
self.method,
args=("öô¿‽", "ñě"),
kwargs={"c": "ßø"},
priority=15,
description="My dé^Wdescription",
)
test_job.store()
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(job_read.args, ("öô¿‽", "ñě"))
self.assertEqual(job_read.kwargs, {"c": "ßø"})
self.assertEqual(job_read.description, "My dé^Wdescription")
def test_job_delay(self):
self.cr.execute("delete from queue_job")
job_ = self.env["test.queue.job"].with_delay().testing_method()
stored = self.queue_job.search([])
self.assertEqual(len(stored), 1)
self.assertEqual(stored.uuid, job_.uuid, "Incorrect returned Job UUID")
def test_job_delay_model_method(self):
self.cr.execute("delete from queue_job")
delayable = self.env["test.queue.job"].with_delay()
job_instance = delayable.testing_method("a", k=1)
self.assertTrue(job_instance)
result = job_instance.perform()
self.assertEqual(result, (("a",), {"k": 1}))
def test_job_identity_key_str(self):
id_key = "e294e8444453b09d59bdb6efbfec1323"
test_job_1 = Job(
self.method,
priority=15,
description="Test I am the first one",
identity_key=id_key,
)
test_job_1.store()
job1 = Job.load(self.env, test_job_1.uuid)
self.assertEqual(job1.identity_key, id_key)
def test_job_identity_key_func_exact(self):
hasher = hashlib.sha1()
hasher.update(b"test.queue.job")
hasher.update(b"testing_method")
hasher.update(str(sorted([])).encode("utf-8"))
hasher.update(str((1, "foo")).encode("utf-8"))
hasher.update(str(sorted({"bar": "baz"}.items())).encode("utf-8"))
expected_key = hasher.hexdigest()
test_job_1 = Job(
self.method,
args=[1, "foo"],
kwargs={"bar": "baz"},
identity_key=identity_exact,
)
self.assertEqual(test_job_1.identity_key, expected_key)
test_job_1.store()
job1 = Job.load(self.env, test_job_1.uuid)
self.assertEqual(job1.identity_key, expected_key)
class TestJobs(JobCommonCase):
"""Test jobs on other methods or with different job configuration"""
def test_description(self):
"""If no description is given to the job, it
should be computed from the function
"""
# if a docstring is defined for the function
# it's used as description
job_a = Job(self.env["test.queue.job"].testing_method)
self.assertEqual(job_a.description, "Method used for tests")
# if no docstring, the description is computed
job_b = Job(self.env["test.queue.job"].no_description)
self.assertEqual(job_b.description, "test.queue.job.no_description")
# case when we explicitly specify the description
description = "My description"
job_a = Job(self.env["test.queue.job"].testing_method, description=description)
self.assertEqual(job_a.description, description)
def test_retry_pattern(self):
"""When we specify a retry pattern, the eta must follow it"""
datetime_path = "odoo.addons.queue_job.job.datetime"
method = self.env["test.queue.job"].job_with_retry_pattern
with mock.patch(datetime_path, autospec=True) as mock_datetime:
mock_datetime.now.return_value = datetime(2015, 6, 1, 15, 10, 0)
test_job = Job(method, max_retries=0)
test_job.retry += 1
test_job.postpone(self.env)
self.assertEqual(test_job.retry, 1)
self.assertEqual(test_job.eta, datetime(2015, 6, 1, 15, 11, 0))
test_job.retry += 1
test_job.postpone(self.env)
self.assertEqual(test_job.retry, 2)
self.assertEqual(test_job.eta, datetime(2015, 6, 1, 15, 13, 0))
test_job.retry += 1
test_job.postpone(self.env)
self.assertEqual(test_job.retry, 3)
self.assertEqual(test_job.eta, datetime(2015, 6, 1, 15, 10, 10))
test_job.retry += 1
test_job.postpone(self.env)
self.assertEqual(test_job.retry, 4)
self.assertEqual(test_job.eta, datetime(2015, 6, 1, 15, 10, 10))
test_job.retry += 1
test_job.postpone(self.env)
self.assertEqual(test_job.retry, 5)
self.assertEqual(test_job.eta, datetime(2015, 6, 1, 15, 15, 0))
def test_retry_pattern_no_zero(self):
"""When we specify a retry pattern without 0, uses RETRY_INTERVAL"""
method = self.env["test.queue.job"].job_with_retry_pattern__no_zero
test_job = Job(method, max_retries=0)
test_job.retry += 1
self.assertEqual(test_job.retry, 1)
self.assertEqual(test_job._get_retry_seconds(), RETRY_INTERVAL)
test_job.retry += 1
self.assertEqual(test_job.retry, 2)
self.assertEqual(test_job._get_retry_seconds(), RETRY_INTERVAL)
test_job.retry += 1
self.assertEqual(test_job.retry, 3)
self.assertEqual(test_job._get_retry_seconds(), 180)
test_job.retry += 1
self.assertEqual(test_job.retry, 4)
self.assertEqual(test_job._get_retry_seconds(), 180)
def test_job_delay_model_method_multi(self):
rec1 = self.env["test.queue.job"].create({"name": "test1"})
rec2 = self.env["test.queue.job"].create({"name": "test2"})
recs = rec1 + rec2
job_instance = recs.with_delay().mapped("name")
self.assertTrue(job_instance)
self.assertEqual(job_instance.args, ("name",))
self.assertEqual(job_instance.recordset, recs)
self.assertEqual(job_instance.model_name, "test.queue.job")
self.assertEqual(job_instance.method_name, "mapped")
self.assertEqual(["test1", "test2"], job_instance.perform())
def test_job_identity_key_no_duplicate(self):
"""If a job with same identity key in queue do not add a new one"""
id_key = "e294e8444453b09d59bdb6efbfec1323"
rec1 = self.env["test.queue.job"].create({"name": "test1"})
job_1 = rec1.with_delay(identity_key=id_key).mapped("name")
self.assertTrue(job_1)
job_2 = rec1.with_delay(identity_key=id_key).mapped("name")
self.assertEqual(job_2.uuid, job_1.uuid)
def test_job_with_mutable_arguments(self):
"""Job with mutable arguments do not mutate on perform()"""
delayable = self.env["test.queue.job"].with_delay()
job_instance = delayable.job_alter_mutable([1], mutable_kwarg={"a": 1})
self.assertTrue(job_instance)
result = job_instance.perform()
self.assertEqual(result, ([1, 2], {"a": 1, "b": 2}))
job_instance.set_done()
# at this point, the 'args' and 'kwargs' of the job instance
# might have been modified, but they must never be modified in
# the queue_job table after their creation, so a new 'load' will
# get the initial values.
job_instance.store()
# jobs are always loaded before being performed, so we simulate
# this behavior here to check if we have the correct initial arguments
job_instance = Job.load(self.env, job_instance.uuid)
self.assertEqual(([1],), job_instance.args)
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")
self.env = self.env(user=demo_user)
delayable = self.env["test.queue.job"].with_delay()
test_job = delayable.testing_method()
stored = test_job.db_record()
job_instance = Job.load(self.env, stored.uuid)
self.assertFalse(job_instance.recordset.env.su)
self.assertTrue(job_instance.user_id, demo_user)
def test_store_env_su_sudo(self):
demo_user = self.env.ref("base.user_demo")
self.env = self.env(user=demo_user)
delayable = self.env["test.queue.job"].sudo().with_delay()
test_job = delayable.testing_method()
stored = test_job.db_record()
job_instance = Job.load(self.env, stored.uuid)
self.assertTrue(job_instance.recordset.env.su)
self.assertTrue(job_instance.user_id, demo_user)
class TestJobModel(JobCommonCase):
def test_job_change_state(self):
stored = self._create_job()
stored._change_job_state(DONE, result="test")
self.assertEqual(stored.state, DONE)
self.assertEqual(stored.result, "test")
stored._change_job_state(PENDING, result="test2")
self.assertEqual(stored.state, PENDING)
self.assertEqual(stored.result, "test2")
with self.assertRaises(ValueError):
# only PENDING and DONE supported
stored._change_job_state(STARTED)
def test_button_done(self):
stored = self._create_job()
stored.button_done()
self.assertEqual(stored.state, DONE)
self.assertEqual(
stored.result, "Manually set to done by %s" % self.env.user.name
)
def test_button_done_enqueue_waiting_dependencies(self):
job_root = Job(self.env["test.queue.job"].testing_method)
job_child = Job(self.env["test.queue.job"].testing_method)
job_child.add_depends({job_root})
DelayableGraph._ensure_same_graph_uuid([job_root, job_child])
job_root.store()
job_child.store()
self.assertEqual(job_child.state, WAIT_DEPENDENCIES)
record_root = job_root.db_record()
record_child = job_child.db_record()
# Trigger button done
record_root.button_done()
# Check the state
self.assertEqual(record_root.state, DONE)
self.assertEqual(record_child.state, PENDING)
def test_button_cancel_dependencies(self):
job_root = Job(self.env["test.queue.job"].testing_method)
job_child = Job(self.env["test.queue.job"].testing_method)
job_child.add_depends({job_root})
DelayableGraph._ensure_same_graph_uuid([job_root, job_child])
job_root.store()
job_child.store()
self.assertEqual(job_child.state, WAIT_DEPENDENCIES)
record_root = job_root.db_record()
record_child = job_child.db_record()
# Trigger button cancelled
record_root.button_cancelled()
# Check the state
self.assertEqual(record_root.state, CANCELLED)
self.assertEqual(record_child.state, CANCELLED)
def test_requeue(self):
stored = self._create_job()
stored.write({"state": "failed"})
stored.requeue()
self.assertEqual(stored.state, PENDING)
def test_requeue_wait_dependencies_not_touched(self):
job_root = Job(self.env["test.queue.job"].testing_method)
job_child = Job(self.env["test.queue.job"].testing_method)
job_child.add_depends({job_root})
job_root.store()
job_child.store()
DelayableGraph._ensure_same_graph_uuid([job_root, job_child])
record_root = job_root.db_record()
record_child = job_child.db_record()
self.assertEqual(record_root.state, PENDING)
self.assertEqual(record_child.state, WAIT_DEPENDENCIES)
record_root.write({"state": "failed"})
(record_root + record_child).requeue()
self.assertEqual(record_root.state, PENDING)
self.assertEqual(record_child.state, WAIT_DEPENDENCIES)
def test_message_when_write_fail(self):
stored = self._create_job()
stored.write({"state": "failed"})
self.assertEqual(stored.state, FAILED)
messages = stored.message_ids
self.assertEqual(len(messages), 1)
def test_follower_when_write_fail(self):
"""Check that inactive users doesn't are not followers even if
they are linked to an active partner"""
group = self.env.ref("queue_job.group_queue_job_manager")
vals = {
"name": "xx",
"login": "xx",
"groups_id": [(6, 0, [group.id])],
"active": False,
}
inactiveusr = self.user.create(vals)
inactiveusr.partner_id.active = True
self.assertFalse(inactiveusr in group.users)
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))
def test_wizard_requeue(self):
stored = self._create_job()
stored.write({"state": "failed"})
model = self.env["queue.requeue.job"]
model = model.with_context(active_model="queue.job", active_ids=stored.ids)
model.create({}).requeue()
self.assertEqual(stored.state, PENDING)
def test_context_uuid(self):
delayable = self.env["test.queue.job"].with_delay()
test_job = delayable.testing_method(return_context=True)
result = test_job.perform()
key_present = "job_uuid" in result
self.assertTrue(key_present)
self.assertEqual(result["job_uuid"], test_job._uuid)
def test_override_channel(self):
delayable = self.env["test.queue.job"].with_delay(channel="root.sub.sub")
test_job = delayable.testing_method(return_context=True)
self.assertEqual("root.sub.sub", test_job.channel)
def test_job_change_user_id(self):
demo_user = self.env.ref("base.user_demo")
stored = self._create_job()
stored.user_id = demo_user
self.assertEqual(stored.records.env.uid, demo_user.id)
class TestJobStorageMultiCompany(common.TransactionCase):
"""Test storage of jobs"""
def setUp(self):
super().setUp()
self.queue_job = self.env["queue.job"]
grp_queue_job_manager = self.ref("queue_job.group_queue_job_manager")
User = self.env["res.users"]
Company = self.env["res.company"]
Partner = self.env["res.partner"]
main_company = self.env.ref("base.main_company")
self.partner_user = Partner.create(
{"name": "Simple User", "email": "simple.user@example.com"}
)
self.simple_user = User.create(
{
"partner_id": self.partner_user.id,
"company_ids": [(4, main_company.id)],
"login": "simple_user",
"name": "simple user",
"groups_id": [],
}
)
self.other_partner_a = Partner.create(
{"name": "My Company a", "is_company": True, "email": "test@tes.ttest"}
)
self.other_company_a = Company.create(
{
"name": "My Company a",
"partner_id": self.other_partner_a.id,
"currency_id": self.ref("base.EUR"),
}
)
self.other_user_a = User.create(
{
"partner_id": self.other_partner_a.id,
"company_id": self.other_company_a.id,
"company_ids": [(4, self.other_company_a.id)],
"login": "my_login a",
"name": "my user A",
"groups_id": [(4, grp_queue_job_manager)],
}
)
self.other_partner_b = Partner.create(
{"name": "My Company b", "is_company": True, "email": "test@tes.ttest"}
)
self.other_company_b = Company.create(
{
"name": "My Company b",
"partner_id": self.other_partner_b.id,
"currency_id": self.ref("base.EUR"),
}
)
self.other_user_b = User.create(
{
"partner_id": self.other_partner_b.id,
"company_id": self.other_company_b.id,
"company_ids": [(4, self.other_company_b.id)],
"login": "my_login_b",
"name": "my user B",
"groups_id": [(4, grp_queue_job_manager)],
}
)
def _create_job(self, env):
self.cr.execute("delete from queue_job")
env["test.queue.job"].with_delay().testing_method()
stored = self.queue_job.search([])
self.assertEqual(len(stored), 1)
return stored
def test_job_default_company_id(self):
"""the default company is the one from the current user_id"""
stored = self._create_job(self.env)
self.assertEqual(
stored.company_id.id,
self.ref("base.main_company"),
"Incorrect default company_id",
)
env = self.env(user=self.other_user_b.id)
stored = self._create_job(env)
self.assertEqual(
stored.company_id.id,
self.other_company_b.id,
"Incorrect default company_id",
)
def test_job_no_company_id(self):
"""if we put an empty company_id in the context
jobs are created without company_id
"""
env = self.env(context={"company_id": None})
stored = self._create_job(env)
self.assertFalse(stored.company_id, "Company_id should be empty")
def test_job_specific_company_id(self):
"""If a company_id specified in the context
it's used by default for the job creation"""
env = self.env(context={"company_id": self.other_company_a.id})
stored = self._create_job(env)
self.assertEqual(
stored.company_id.id, self.other_company_a.id, "Incorrect company_id"
)
def test_job_subscription(self):
# if the job is created without company_id, all members of
# queue_job.group_queue_job_manager must be followers
User = self.env["res.users"]
no_company_context = dict(self.env.context, company_id=None)
no_company_env = self.env(user=self.simple_user, context=no_company_context)
stored = self._create_job(no_company_env)
stored._message_post_on_failure()
users = (
User.search(
[("groups_id", "=", self.ref("queue_job.group_queue_job_manager"))]
)
+ stored.user_id
)
self.assertEqual(len(stored.message_follower_ids), len(users))
expected_partners = [u.partner_id for u in users]
self.assertSetEqual(
set(stored.message_follower_ids.mapped("partner_id")),
set(expected_partners),
)
followers_id = stored.message_follower_ids.mapped("partner_id.id")
self.assertIn(self.other_partner_a.id, followers_id)
self.assertIn(self.other_partner_b.id, followers_id)
# jobs created for a specific company_id are followed only by
# company's members
company_a_context = dict(self.env.context, company_id=self.other_company_a.id)
company_a_env = self.env(user=self.simple_user, context=company_a_context)
stored = self._create_job(company_a_env)
stored.with_user(self.other_user_a.id)
stored._message_post_on_failure()
# 2 because simple_user (creator of job) + self.other_partner_a
self.assertEqual(len(stored.message_follower_ids), 2)
users = self.simple_user + self.other_user_a
expected_partners = [u.partner_id for u in users]
self.assertSetEqual(
set(stored.message_follower_ids.mapped("partner_id")),
set(expected_partners),
)
followers_id = stored.message_follower_ids.mapped("partner_id.id")
self.assertIn(self.other_partner_a.id, followers_id)
self.assertNotIn(self.other_partner_b.id, followers_id)

View file

@ -0,0 +1,59 @@
# Copyright 2020 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
from odoo.tests.common import tagged
from odoo.addons.queue_job.job import Job
from .common import JobCommonCase
@tagged("post_install", "-at_install")
class TestJobAutoDelay(JobCommonCase):
"""Test auto delay of jobs"""
def test_auto_delay(self):
"""method decorated by @job_auto_delay is automatically delayed"""
result = self.env["test.queue.job"].delay_me(1, kwarg=2)
self.assertTrue(isinstance(result, Job))
self.assertEqual(result.args, (1,))
self.assertEqual(result.kwargs, {"kwarg": 2})
def test_auto_delay_options(self):
"""method automatically delayed une <method>_job_options arguments"""
result = self.env["test.queue.job"].delay_me_options()
self.assertTrue(isinstance(result, Job))
self.assertEqual(result.identity_key, "my_job_identity")
def test_auto_delay_inside_job(self):
"""when a delayed job is processed, it must not delay itself"""
job_ = self.env["test.queue.job"].delay_me(1, kwarg=2)
self.assertTrue(job_.perform(), (1, 2))
def test_auto_delay_force_sync(self):
"""method forced to run synchronously"""
with self.assertLogs(level="WARNING") as log_catcher:
result = (
self.env["test.queue.job"]
.with_context(_job_force_sync=True)
.delay_me(1, kwarg=2)
)
self.assertEqual(
len(log_catcher.output), 1, "Exactly one warning should be logged"
)
self.assertIn(" ctx key found. NO JOB scheduled. ", log_catcher.output[0])
self.assertTrue(result, (1, 2))
def test_auto_delay_context_key_set(self):
"""patched with context_key delays only if context keys is set"""
result = (
self.env["test.queue.job"]
.with_context(auto_delay_delay_me_context_key=True)
.delay_me_context_key()
)
self.assertTrue(isinstance(result, Job))
def test_auto_delay_context_key_unset(self):
"""patched with context_key do not delay if context keys is not set"""
result = self.env["test.queue.job"].delay_me_context_key()
self.assertEqual(result, "ok")

View file

@ -0,0 +1,93 @@
# Copyright 2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import odoo.tests.common as common
from odoo import exceptions
from odoo.addons.queue_job.job import Job
class TestJobChannels(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.function_model = cls.env["queue.job.function"]
cls.channel_model = cls.env["queue.job.channel"]
cls.test_model = cls.env["test.queue.channel"]
cls.root_channel = cls.env.ref("queue_job.channel_root")
def test_channel_complete_name(self):
channel = self.channel_model.create(
{"name": "number", "parent_id": self.root_channel.id}
)
subchannel = self.channel_model.create(
{"name": "five", "parent_id": channel.id}
)
self.assertEqual(channel.complete_name, "root.number")
self.assertEqual(subchannel.complete_name, "root.number.five")
def test_channel_tree(self):
with self.assertRaises(exceptions.ValidationError):
self.channel_model.create({"name": "sub"})
def test_channel_root(self):
with self.assertRaises(exceptions.UserError):
self.root_channel.unlink()
with self.assertRaises(exceptions.UserError):
self.root_channel.name = "leaf"
def test_channel_on_job(self):
method = self.env["test.queue.channel"].job_a
path_a = self.env["queue.job.function"].job_function_name(
"test.queue.channel", "job_a"
)
job_func = self.function_model.search([("name", "=", path_a)])
self.assertEqual(job_func.channel, "root")
test_job = Job(method)
test_job.store()
stored = test_job.db_record()
self.assertEqual(stored.channel, "root")
job_read = Job.load(self.env, test_job.uuid)
self.assertEqual(job_read.channel, "root")
sub_channel = self.env.ref("test_queue_job.channel_sub")
job_func.channel_id = sub_channel
test_job = Job(method)
test_job.store()
stored = test_job.db_record()
self.assertEqual(stored.channel, "root.sub")
# it's also possible to override the channel
test_job = Job(method, channel="root.sub")
test_job.store()
stored = test_job.db_record()
self.assertEqual(stored.channel, test_job.channel)
def test_default_channel_no_xml(self):
"""Channel on job is root if there is no queue.job.function record"""
test_job = Job(self.env["res.users"].browse)
test_job.store()
stored = test_job.db_record()
self.assertEqual(stored.channel, "root")
def test_set_channel_from_record(self):
func_name = self.env["queue.job.function"].job_function_name(
"test.queue.channel", "job_sub_channel"
)
job_func = self.function_model.search([("name", "=", func_name)])
self.assertEqual(job_func.channel, "root.sub.subsub")
channel = job_func.channel_id
self.assertEqual(channel.name, "subsub")
self.assertEqual(channel.parent_id.name, "sub")
self.assertEqual(channel.parent_id.parent_id.name, "root")
self.assertEqual(job_func.channel, "root.sub.subsub")
def test_default_removal_interval(self):
channel = self.channel_model.create(
{"name": "number", "parent_id": self.root_channel.id}
)
self.assertEqual(channel.removal_interval, 30)

View file

@ -0,0 +1,35 @@
import odoo.tests.common as common
from odoo import exceptions
class TestJobFunction(common.TransactionCase):
def setUp(self):
super().setUp()
self.test_function_model = self.env.ref(
"queue_job.job_function_queue_job__test_job"
)
def test_check_retry_pattern_randomized_case(self):
randomized_pattern = "{1: (10, 20), 2: (20, 40)}"
self.test_function_model.edit_retry_pattern = randomized_pattern
self.assertEqual(
self.test_function_model.edit_retry_pattern, randomized_pattern
)
def test_check_retry_pattern_fixed_case(self):
fixed_pattern = "{1: 10, 2: 20}"
self.test_function_model.edit_retry_pattern = fixed_pattern
self.assertEqual(self.test_function_model.edit_retry_pattern, fixed_pattern)
def test_check_retry_pattern_invalid_cases(self):
invalid_time_value_pattern = "{1: a, 2: 20}"
with self.assertRaises(exceptions.UserError):
self.test_function_model.edit_retry_pattern = invalid_time_value_pattern
invalid_retry_count_pattern = "{a: 10, 2: 20}"
with self.assertRaises(exceptions.UserError):
self.test_function_model.edit_retry_pattern = invalid_retry_count_pattern
invalid_randomized_pattern = "{1: (1, 2, 3), 2: 20}"
with self.assertRaises(exceptions.UserError):
self.test_function_model.edit_retry_pattern = invalid_randomized_pattern

View file

@ -0,0 +1,32 @@
# copyright 2022 Guewen Baconnier
# license lgpl-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import json
from odoo.tests import common
# pylint: disable=odoo-addons-relative-import
# we are testing, we want to test as if we were an external consumer of the API
from odoo.addons.queue_job.fields import JobEncoder
class TestJsonField(common.TransactionCase):
# TODO: when migrating to 16.0, adapt the 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")
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")
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)[
"test.queue.job"
]
value_json = json.dumps(test_model, cls=JobEncoder)
self.assertEqual(json.loads(value_json)["context"], user_context)

View file

@ -0,0 +1,114 @@
# Copyright 2014-2016 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import odoo.tests.common as common
from odoo import exceptions
class TestRelatedAction(common.TransactionCase):
"""Test Related Actions"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.model = cls.env["test.related.action"]
cls.record = cls.model.create({})
cls.records = cls.record + cls.model.create({})
def test_attributes(self):
"""Job with related action check if action returns correctly"""
job_ = self.record.with_delay().testing_related_action__kwargs()
act_job, act_kwargs = job_.related_action()
self.assertEqual(act_job, job_.db_record())
self.assertEqual(act_kwargs, {"b": 4})
def test_decorator_empty(self):
"""Job with decorator without value disable the default action
The ``related_action`` configuration is: ``{"enable": False}``
"""
# default action returns None
job_ = self.record.with_delay().testing_related_action__return_none()
self.assertIsNone(job_.related_action())
def test_model_no_action(self):
"""Model shows an error when no action exist"""
job_ = self.record.with_delay().testing_related_action__return_none()
with self.assertRaises(exceptions.UserError):
# db_record is the 'job.queue' record on which we click on the
# button to open the related action
job_.db_record().open_related_action()
def test_default_no_record(self):
"""Default related action called when no decorator is set
When called on no record.
The ``related_action`` configuration is: ``{}``
"""
job_ = self.model.with_delay().testing_related_action__no()
expected = None
self.assertEqual(job_.related_action(), expected)
def test_model_default_no_record(self):
"""Model shows an error when using the default action and we have no
record linke to the job"""
job_ = self.model.with_delay().testing_related_action__no()
with self.assertRaises(exceptions.UserError):
# db_record is the 'job.queue' record on which we click on the
# button to open the related action
job_.db_record().open_related_action()
def test_default_one_record(self):
"""Default related action called when no decorator is set
When called on one record.
The ``related_action`` configuration is: ``{}``
"""
job_ = self.record.with_delay().testing_related_action__no()
expected = {
"name": "Related Record",
"res_id": self.record.id,
"res_model": self.record._name,
"type": "ir.actions.act_window",
"view_mode": "form",
}
self.assertEqual(job_.related_action(), expected)
def test_default_several_record(self):
"""Default related action called when no decorator is set
When called on several record.
The ``related_action`` configuration is: ``{}``
"""
job_ = self.records.with_delay().testing_related_action__no()
expected = {
"name": "Related Records",
"domain": [("id", "in", self.records.ids)],
"res_model": self.record._name,
"type": "ir.actions.act_window",
"view_mode": "tree,form",
}
self.assertEqual(job_.related_action(), expected)
def test_decorator(self):
"""Call the related action on the model
The function is::
The ``related_action`` configuration is::
{
"func_name": "testing_related__url",
"kwargs": {"url": "https://en.wikipedia.org/wiki/{subject}"}
}
"""
job_ = self.record.with_delay().testing_related_action__store("Discworld")
expected = {
"type": "ir.actions.act_url",
"target": "new",
"url": "https://en.wikipedia.org/wiki/Discworld",
}
self.assertEqual(job_.related_action(), expected)