mirror of
https://github.com/bringout/oca-ocb-test.git
synced 2026-04-23 07:02:02 +02:00
Initial commit: Test packages
This commit is contained in:
commit
080accd21c
338 changed files with 32413 additions and 0 deletions
48
odoo-bringout-oca-ocb-test_base_automation/README.md
Normal file
48
odoo-bringout-oca-ocb-test_base_automation/README.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Test - Base Automation
|
||||
|
||||
This module contains tests related to base automation. Those are
|
||||
present in a separate module as it contains models used only to perform
|
||||
tests independently to functional aspects of other models.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_base_automation
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- base_automation
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Test - Base Automation
|
||||
- **Version**: 1.0
|
||||
- **Category**: Hidden
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_base_automation`.
|
||||
|
||||
## 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
|
||||
|
|
@ -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_base_automation Module - test_base_automation
|
||||
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.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Configuration
|
||||
|
||||
Refer to Odoo settings for test_base_automation. Configure related models, access rights, and options as needed.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Controllers
|
||||
|
||||
This module does not define custom HTTP controllers.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Dependencies
|
||||
|
||||
This addon depends on:
|
||||
|
||||
- [base_automation](../../odoo-bringout-oca-ocb-base_automation)
|
||||
4
odoo-bringout-oca-ocb-test_base_automation/doc/FAQ.md
Normal file
4
odoo-bringout-oca-ocb-test_base_automation/doc/FAQ.md
Normal 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_base_automation or install in UI.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Install
|
||||
|
||||
```bash
|
||||
pip install odoo-bringout-oca-ocb-test_base_automation"
|
||||
# or
|
||||
uv pip install odoo-bringout-oca-ocb-test_base_automation"
|
||||
```
|
||||
15
odoo-bringout-oca-ocb-test_base_automation/doc/MODELS.md
Normal file
15
odoo-bringout-oca-ocb-test_base_automation/doc/MODELS.md
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Models
|
||||
|
||||
Detected core models and extensions in test_base_automation.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class base_automation_lead_test
|
||||
class base_automation_line_test
|
||||
class base_automation_linked_test
|
||||
class base_automation_link_test
|
||||
```
|
||||
|
||||
Notes
|
||||
- Classes show model technical names; fields omitted for brevity.
|
||||
- Items listed under _inherit are extensions of existing models.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# Overview
|
||||
|
||||
Packaged Odoo addon: test_base_automation. Provides features documented in upstream Odoo 16 under this addon.
|
||||
|
||||
- Source: OCA/OCB 16.0, addon test_base_automation
|
||||
- License: LGPL-3
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Reports
|
||||
|
||||
This module does not define custom reports.
|
||||
34
odoo-bringout-oca-ocb-test_base_automation/doc/SECURITY.md
Normal file
34
odoo-bringout-oca-ocb-test_base_automation/doc/SECURITY.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Security
|
||||
|
||||
Access control and security definitions in test_base_automation.
|
||||
|
||||
## Access Control Lists (ACLs)
|
||||
|
||||
Model access permissions defined in:
|
||||
- **[ir.model.access.csv](../test_base_automation/security/ir.model.access.csv)**
|
||||
- 6 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_base_automation/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
|
||||
|
|
@ -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.
|
||||
7
odoo-bringout-oca-ocb-test_base_automation/doc/USAGE.md
Normal file
7
odoo-bringout-oca-ocb-test_base_automation/doc/USAGE.md
Normal 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_base_automation
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Wizards
|
||||
|
||||
This module does not include UI wizards.
|
||||
42
odoo-bringout-oca-ocb-test_base_automation/pyproject.toml
Normal file
42
odoo-bringout-oca-ocb-test_base_automation/pyproject.toml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-test_base_automation"
|
||||
version = "16.0.0"
|
||||
description = "Test - Base Automation - Base Automation Tests: Ensure Flow Robustness"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-base_automation>=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_base_automation"]
|
||||
|
||||
[tool.rye]
|
||||
managed = true
|
||||
dev-dependencies = [
|
||||
"pytest>=8.4.1",
|
||||
]
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Test - Base Automation',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'sequence': 9877,
|
||||
'summary': 'Base Automation Tests: Ensure Flow Robustness',
|
||||
'description': """This module contains tests related to base automation. Those are
|
||||
present in a separate module as it contains models used only to perform
|
||||
tests independently to functional aspects of other models.""",
|
||||
'depends': ['base_automation'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_base_automation
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from dateutil import relativedelta
|
||||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class LeadTest(models.Model):
|
||||
_name = "base.automation.lead.test"
|
||||
_description = "Automated Rule Test"
|
||||
|
||||
name = fields.Char(string='Subject', required=True)
|
||||
user_id = fields.Many2one('res.users', string='Responsible')
|
||||
state = fields.Selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'),
|
||||
('pending', 'Pending'), ('done', 'Closed')],
|
||||
string="Status", readonly=True, default='draft')
|
||||
active = fields.Boolean(default=True)
|
||||
partner_id = fields.Many2one('res.partner', string='Partner')
|
||||
date_action_last = fields.Datetime(string='Last Action', readonly=True)
|
||||
employee = fields.Boolean(compute='_compute_employee_deadline', store=True)
|
||||
line_ids = fields.One2many('base.automation.line.test', 'lead_id')
|
||||
|
||||
priority = fields.Boolean()
|
||||
deadline = fields.Boolean(compute='_compute_employee_deadline', store=True)
|
||||
is_assigned_to_admin = fields.Boolean(string='Assigned to admin user')
|
||||
|
||||
@api.depends('partner_id.employee', 'priority')
|
||||
def _compute_employee_deadline(self):
|
||||
# this method computes two fields on purpose; don't split it
|
||||
for record in self:
|
||||
record.employee = record.partner_id.employee
|
||||
if not record.priority:
|
||||
record.deadline = False
|
||||
else:
|
||||
record.deadline = record.create_date + relativedelta.relativedelta(days=3)
|
||||
|
||||
def write(self, vals):
|
||||
result = super().write(vals)
|
||||
# force recomputation of field 'deadline' via 'employee': the action
|
||||
# based on 'deadline' must be triggered
|
||||
self.mapped('employee')
|
||||
return result
|
||||
|
||||
|
||||
class LineTest(models.Model):
|
||||
_name = "base.automation.line.test"
|
||||
_description = "Automated Rule Line Test"
|
||||
|
||||
name = fields.Char()
|
||||
lead_id = fields.Many2one('base.automation.lead.test', ondelete='cascade')
|
||||
user_id = fields.Many2one('res.users')
|
||||
|
||||
|
||||
class ModelWithAccess(models.Model):
|
||||
_name = "base.automation.link.test"
|
||||
_description = "Automated Rule Link Test"
|
||||
|
||||
name = fields.Char()
|
||||
linked_id = fields.Many2one('base.automation.linked.test', ondelete='cascade')
|
||||
|
||||
|
||||
class ModelWithoutAccess(models.Model):
|
||||
_name = "base.automation.linked.test"
|
||||
_description = "Automated Rule Linked Test"
|
||||
|
||||
name = fields.Char()
|
||||
another_field = fields.Char()
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
_name = _description = 'test_base_automation.project'
|
||||
|
||||
name = fields.Char()
|
||||
task_ids = fields.One2many('test_base_automation.task', 'project_id')
|
||||
|
||||
|
||||
class Task(models.Model):
|
||||
_name = _description = 'test_base_automation.task'
|
||||
|
||||
name = fields.Char()
|
||||
parent_id = fields.Many2one('test_base_automation.task')
|
||||
project_id = fields.Many2one(
|
||||
'test_base_automation.project',
|
||||
compute='_compute_project_id', recursive=True, store=True, readonly=False,
|
||||
)
|
||||
|
||||
@api.depends('parent_id.project_id')
|
||||
def _compute_project_id(self):
|
||||
for task in self:
|
||||
if not task.project_id:
|
||||
task.project_id = task.parent_id.project_id
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_base_automation_lead_test,access_base_automation_lead_test,model_base_automation_lead_test,base.group_system,1,1,1,1
|
||||
access_base_automation_line_test,access_base_automation_line_test,model_base_automation_line_test,base.group_system,1,1,1,1
|
||||
access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,,1,1,1,1
|
||||
access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,,1,1,1,1
|
||||
access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,,1,1,1,1
|
||||
access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_flow
|
||||
|
|
@ -0,0 +1,462 @@
|
|||
# # -*- coding: utf-8 -*-
|
||||
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from unittest.mock import patch
|
||||
import sys
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
|
||||
from odoo.tests import common, tagged
|
||||
from odoo.exceptions import AccessError
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class BaseAutomationTest(TransactionCaseWithUserDemo):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseAutomationTest, self).setUp()
|
||||
self.user_root = self.env.ref('base.user_root')
|
||||
self.user_admin = self.env.ref('base.user_admin')
|
||||
|
||||
self.test_mail_template_automation = self.env['mail.template'].create({
|
||||
'name': 'Template Automation',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'body_html': """<div>Email automation</div>""",
|
||||
})
|
||||
|
||||
self.res_partner_1 = self.env['res.partner'].create({'name': 'My Partner'})
|
||||
self.env['base.automation'].create([
|
||||
{
|
||||
'name': 'Base Automation: test rule on create',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'state': 'code',
|
||||
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
|
||||
'trigger': 'on_create',
|
||||
'active': True,
|
||||
'filter_domain': "[('state', '=', 'draft')]",
|
||||
}, {
|
||||
'name': 'Base Automation: test rule on write',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'state': 'code',
|
||||
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
'filter_domain': "[('state', '=', 'done')]",
|
||||
'filter_pre_domain': "[('state', '=', 'open')]",
|
||||
}, {
|
||||
'name': 'Base Automation: test rule on recompute',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'state': 'code',
|
||||
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
'filter_domain': "[('employee', '=', True)]",
|
||||
}, {
|
||||
'name': 'Base Automation: test recursive rule',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'state': 'code',
|
||||
'code': """
|
||||
record = model.browse(env.context['active_id'])
|
||||
if 'partner_id' in env.context['old_values'][record.id]:
|
||||
record.write({'state': 'draft'})""",
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
}, {
|
||||
'name': 'Base Automation: test rule on secondary model',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_line_test').id,
|
||||
'state': 'code',
|
||||
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
|
||||
'trigger': 'on_create',
|
||||
'active': True,
|
||||
}, {
|
||||
'name': 'Base Automation: test rule on write check context',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'state': 'code',
|
||||
'code': """
|
||||
record = model.browse(env.context['active_id'])
|
||||
if 'user_id' in env.context['old_values'][record.id]:
|
||||
record.write({'is_assigned_to_admin': (record.user_id.id == 1)})""",
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
}, {
|
||||
'name': 'Base Automation: test rule with trigger',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__state').id)],
|
||||
'state': 'code',
|
||||
'code': """
|
||||
record = model.browse(env.context['active_id'])
|
||||
record['name'] = record.name + 'X'""",
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
}, {
|
||||
'name': 'Base Automation: test send an email',
|
||||
'mail_post_method': 'email',
|
||||
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
|
||||
'template_id': self.test_mail_template_automation.id,
|
||||
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__deadline').id)],
|
||||
'state': 'mail_post',
|
||||
'code': """
|
||||
record = model.browse(env.context['active_id'])
|
||||
record['name'] = record.name + 'X'""",
|
||||
'trigger': 'on_write',
|
||||
'active': True,
|
||||
'filter_domain': "[('deadline', '!=', False)]",
|
||||
'filter_pre_domain': "[('deadline', '=', False)]",
|
||||
}
|
||||
])
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.env['base.automation']._unregister_hook()
|
||||
|
||||
def create_lead(self, **kwargs):
|
||||
vals = {
|
||||
'name': "Lead Test",
|
||||
'user_id': self.user_root.id,
|
||||
}
|
||||
vals.update(kwargs)
|
||||
return self.env['base.automation.lead.test'].create(vals)
|
||||
|
||||
def test_00_check_to_state_open_pre(self):
|
||||
"""
|
||||
Check that a new record (with state = open) doesn't change its responsible
|
||||
when there is a precondition filter which check that the state is open.
|
||||
"""
|
||||
lead = self.create_lead(state='open')
|
||||
self.assertEqual(lead.state, 'open')
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
|
||||
|
||||
def test_01_check_to_state_draft_post(self):
|
||||
"""
|
||||
Check that a new record changes its responsible when there is a postcondition
|
||||
filter which check that the state is draft.
|
||||
"""
|
||||
lead = self.create_lead()
|
||||
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
|
||||
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on creation of Lead with state 'draft'.")
|
||||
|
||||
def test_02_check_from_draft_to_done_with_steps(self):
|
||||
"""
|
||||
A new record is created and goes from states 'open' to 'done' via the
|
||||
other states (open, pending and cancel). We have a rule with:
|
||||
- precondition: the record is in "open"
|
||||
- postcondition: that the record is "done".
|
||||
If the state goes from 'open' to 'done' the responsible is changed.
|
||||
If those two conditions aren't verified, the responsible remains the same.
|
||||
"""
|
||||
lead = self.create_lead(state='open')
|
||||
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
|
||||
# change state to pending and check that responsible has not changed
|
||||
lead.write({'state': 'pending'})
|
||||
self.assertEqual(lead.state, 'pending', "Lead state should be 'pending'")
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
|
||||
# change state to done and check that responsible has not changed
|
||||
lead.write({'state': 'done'})
|
||||
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.")
|
||||
|
||||
def test_03_check_from_draft_to_done_without_steps(self):
|
||||
"""
|
||||
A new record is created and goes from states 'open' to 'done' via the
|
||||
other states (open, pending and cancel). We have a rule with:
|
||||
- precondition: the record is in "open"
|
||||
- postcondition: that the record is "done".
|
||||
If the state goes from 'open' to 'done' the responsible is changed.
|
||||
If those two conditions aren't verified, the responsible remains the same.
|
||||
"""
|
||||
lead = self.create_lead(state='open')
|
||||
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
|
||||
# change state to done and check that responsible has changed
|
||||
lead.write({'state': 'done'})
|
||||
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
|
||||
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead with state from 'open' to 'done'.")
|
||||
|
||||
def test_10_recomputed_field(self):
|
||||
"""
|
||||
Check that a rule is executed whenever a field is recomputed after a
|
||||
change on another model.
|
||||
"""
|
||||
partner = self.res_partner_1
|
||||
partner.write({'employee': False})
|
||||
lead = self.create_lead(state='open', partner_id=partner.id)
|
||||
self.assertFalse(lead.employee, "Customer field should updated to False")
|
||||
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
|
||||
# change partner, recompute on lead should trigger the rule
|
||||
partner.write({'employee': True})
|
||||
self.env.flush_all()
|
||||
self.assertTrue(lead.employee, "Customer field should updated to True")
|
||||
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead when Customer becomes True.")
|
||||
|
||||
def test_11_recomputed_field(self):
|
||||
"""
|
||||
Check that a rule is executed whenever a field is recomputed and the
|
||||
context contains the target field
|
||||
"""
|
||||
partner = self.res_partner_1
|
||||
lead = self.create_lead(state='draft', partner_id=partner.id)
|
||||
self.assertFalse(lead.deadline, 'There should not be a deadline defined')
|
||||
# change priority and user; this triggers deadline recomputation, and
|
||||
# the server action should set the boolean field to True
|
||||
lead.write({'priority': True, 'user_id': self.user_root.id})
|
||||
self.assertTrue(lead.deadline, 'Deadline should be defined')
|
||||
self.assertTrue(lead.is_assigned_to_admin, 'Lead should be assigned to admin')
|
||||
|
||||
def test_11b_recomputed_field(self):
|
||||
mail_automation = self.env['base.automation'].search([('name', '=', 'Base Automation: test send an email')])
|
||||
send_mail_count = 0
|
||||
|
||||
def _patched_get_actions(*args, **kwargs):
|
||||
obj = args[0]
|
||||
if '__action_done' not in obj._context:
|
||||
obj = obj.with_context(__action_done={})
|
||||
return mail_automation.with_env(obj.env)
|
||||
|
||||
def _patched_send_mail(*args, **kwargs):
|
||||
nonlocal send_mail_count
|
||||
send_mail_count += 1
|
||||
|
||||
patchers = [
|
||||
patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._get_actions', _patched_get_actions),
|
||||
patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail),
|
||||
]
|
||||
|
||||
self.startPatcher(patchers[0])
|
||||
|
||||
lead = self.create_lead()
|
||||
self.assertFalse(lead.priority)
|
||||
self.assertFalse(lead.deadline)
|
||||
|
||||
self.startPatcher(patchers[1])
|
||||
|
||||
lead.write({'priority': True})
|
||||
|
||||
self.assertTrue(lead.priority)
|
||||
self.assertTrue(lead.deadline)
|
||||
|
||||
|
||||
self.assertEqual(send_mail_count, 1)
|
||||
|
||||
def test_12_recursive(self):
|
||||
""" Check that a rule is executed recursively by a secondary change. """
|
||||
lead = self.create_lead(state='open')
|
||||
self.assertEqual(lead.state, 'open')
|
||||
self.assertEqual(lead.user_id, self.user_root)
|
||||
# change partner; this should trigger the rule that modifies the state
|
||||
partner = self.res_partner_1
|
||||
lead.write({'partner_id': partner.id})
|
||||
self.assertEqual(lead.state, 'draft')
|
||||
|
||||
def test_20_direct_line(self):
|
||||
"""
|
||||
Check that a rule is executed after creating a line record.
|
||||
"""
|
||||
line = self.env['base.automation.line.test'].create({'name': "Line"})
|
||||
self.assertEqual(line.user_id, self.user_demo)
|
||||
|
||||
def test_20_indirect_line(self):
|
||||
"""
|
||||
Check that creating a lead with a line executes rules on both records.
|
||||
"""
|
||||
lead = self.create_lead(line_ids=[(0, 0, {'name': "Line"})])
|
||||
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
|
||||
self.assertEqual(lead.user_id, self.user_demo, "Responsible should change on creation of Lead test line.")
|
||||
self.assertEqual(len(lead.line_ids), 1, "New test line is not created")
|
||||
self.assertEqual(lead.line_ids.user_id, self.user_demo, "Responsible should be change on creation of Lead test line.")
|
||||
|
||||
def test_21_trigger_fields(self):
|
||||
"""
|
||||
Check that the rule with trigger is executed only once per pertinent update.
|
||||
"""
|
||||
lead = self.create_lead(name="X")
|
||||
lead.priority = True
|
||||
partner1 = self.res_partner_1
|
||||
lead.partner_id = partner1.id
|
||||
self.assertEqual(lead.name, 'X', "No update until now.")
|
||||
|
||||
lead.state = 'open'
|
||||
self.assertEqual(lead.name, 'XX', "One update should have happened.")
|
||||
lead.state = 'done'
|
||||
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
|
||||
lead.state = 'done'
|
||||
self.assertEqual(lead.name, 'XXX', "No update should have happened.")
|
||||
lead.state = 'cancel'
|
||||
self.assertEqual(lead.name, 'XXXX', "One update should have happened.")
|
||||
|
||||
# change the rule to trigger on partner_id
|
||||
rule = self.env['base.automation'].search([('name', '=', 'Base Automation: test rule with trigger')])
|
||||
rule.write({'trigger_field_ids': [(6, 0, [self.env.ref('test_base_automation.field_base_automation_lead_test__partner_id').id])]})
|
||||
|
||||
partner2 = self.env['res.partner'].create({'name': 'A new partner'})
|
||||
lead.name = 'X'
|
||||
lead.state = 'open'
|
||||
self.assertEqual(lead.name, 'X', "No update should have happened.")
|
||||
lead.partner_id = partner2
|
||||
self.assertEqual(lead.name, 'XX', "One update should have happened.")
|
||||
lead.partner_id = partner2
|
||||
self.assertEqual(lead.name, 'XX', "No update should have happened.")
|
||||
lead.partner_id = partner1
|
||||
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
|
||||
|
||||
def test_30_modelwithoutaccess(self):
|
||||
"""
|
||||
Ensure a domain on a M2O without user access doesn't fail.
|
||||
We create a base automation with a filter on a model the user haven't access to
|
||||
- create a group
|
||||
- restrict acl to this group and set only admin in it
|
||||
- create base.automation with a filter
|
||||
- create a record in the restricted model in admin
|
||||
- create a record in the non restricted model in demo
|
||||
"""
|
||||
Model = self.env['base.automation.link.test']
|
||||
Comodel = self.env['base.automation.linked.test']
|
||||
|
||||
access = self.env.ref("test_base_automation.access_base_automation_linked_test")
|
||||
access.group_id = self.env['res.groups'].create({
|
||||
'name': "Access to base.automation.linked.test",
|
||||
"users": [(6, 0, [self.user_admin.id,])]
|
||||
})
|
||||
|
||||
# sanity check: user demo has no access to the comodel of 'linked_id'
|
||||
with self.assertRaises(AccessError):
|
||||
Comodel.with_user(self.user_demo).check_access_rights('read')
|
||||
|
||||
# check base automation with filter that performs Comodel.search()
|
||||
self.env['base.automation'].create({
|
||||
'name': 'test no access',
|
||||
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
|
||||
'trigger': 'on_create_or_write',
|
||||
'filter_pre_domain': "[('linked_id.another_field', '=', 'something')]",
|
||||
'state': 'code',
|
||||
'active': True,
|
||||
'code': "action = [rec.name for rec in records]"
|
||||
})
|
||||
Comodel.create([
|
||||
{'name': 'a first record', 'another_field': 'something'},
|
||||
{'name': 'another record', 'another_field': 'something different'},
|
||||
])
|
||||
rec1 = Model.create({'name': 'a record'})
|
||||
rec1.write({'name': 'a first record'})
|
||||
rec2 = Model.with_user(self.user_demo).create({'name': 'another record'})
|
||||
rec2.write({'name': 'another value'})
|
||||
|
||||
# check base automation with filter that performs Comodel.name_search()
|
||||
self.env['base.automation'].create({
|
||||
'name': 'test no name access',
|
||||
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
|
||||
'trigger': 'on_create_or_write',
|
||||
'filter_pre_domain': "[('linked_id', '=', 'whatever')]",
|
||||
'state': 'code',
|
||||
'active': True,
|
||||
'code': "action = [rec.name for rec in records]"
|
||||
})
|
||||
rec3 = Model.create({'name': 'a random record'})
|
||||
rec3.write({'name': 'a first record'})
|
||||
rec4 = Model.with_user(self.user_demo).create({'name': 'again another record'})
|
||||
rec4.write({'name': 'another value'})
|
||||
|
||||
|
||||
@common.tagged('post_install', '-at_install')
|
||||
class TestCompute(common.TransactionCase):
|
||||
def test_inversion(self):
|
||||
""" If a stored field B depends on A, an update to the trigger for A
|
||||
should trigger the recomputaton of A, then B.
|
||||
|
||||
However if a search() is performed during the computation of A
|
||||
??? and _order is affected ??? a flush will be triggered, forcing the
|
||||
computation of B, based on the previous A.
|
||||
|
||||
This happens if a rule has has a non-empty filter_pre_domain, even if
|
||||
it's an empty list (``'[]'`` as opposed to ``False``).
|
||||
"""
|
||||
company1 = self.env['res.partner'].create({
|
||||
'name': "Gorofy",
|
||||
'is_company': True,
|
||||
})
|
||||
company2 = self.env['res.partner'].create({
|
||||
'name': "Awiclo",
|
||||
'is_company': True
|
||||
})
|
||||
r = self.env['res.partner'].create({
|
||||
'name': 'Bob',
|
||||
'is_company': False,
|
||||
'parent_id': company1.id
|
||||
})
|
||||
self.assertEqual(r.display_name, 'Gorofy, Bob')
|
||||
r.parent_id = company2
|
||||
self.assertEqual(r.display_name, 'Awiclo, Bob')
|
||||
|
||||
self.env['base.automation'].create({
|
||||
'name': "test rule",
|
||||
'filter_pre_domain': False,
|
||||
'trigger': 'on_create_or_write',
|
||||
'state': 'code', # no-op action
|
||||
'model_id': self.env.ref('base.model_res_partner').id,
|
||||
})
|
||||
r.parent_id = company1
|
||||
self.assertEqual(r.display_name, 'Gorofy, Bob')
|
||||
|
||||
self.env['base.automation'].create({
|
||||
'name': "test rule",
|
||||
'filter_pre_domain': '[]',
|
||||
'trigger': 'on_create_or_write',
|
||||
'state': 'code', # no-op action
|
||||
'model_id': self.env.ref('base.model_res_partner').id,
|
||||
})
|
||||
r.parent_id = company2
|
||||
self.assertEqual(r.display_name, 'Awiclo, Bob')
|
||||
|
||||
def test_recursion(self):
|
||||
project = self.env['test_base_automation.project'].create({})
|
||||
|
||||
# this action is executed every time a task is assigned to project
|
||||
self.env['base.automation'].create({
|
||||
'name': 'dummy',
|
||||
'model_id': self.env['ir.model']._get_id('test_base_automation.task'),
|
||||
'state': 'code',
|
||||
'trigger': 'on_create_or_write',
|
||||
'filter_domain': repr([('project_id', '=', project.id)]),
|
||||
})
|
||||
|
||||
# create one task in project with 10 subtasks; all the subtasks are
|
||||
# automatically assigned to project, too
|
||||
task = self.env['test_base_automation.task'].create({'project_id': project.id})
|
||||
subtasks = task.create([{'parent_id': task.id} for _ in range(10)])
|
||||
subtasks.flush_model()
|
||||
|
||||
# This test checks what happens when a stored recursive computed field
|
||||
# is marked to compute on many records, and automated actions are
|
||||
# triggered depending on that field. In this case, we trigger the
|
||||
# recomputation of 'project_id' on 'subtasks' by deleting their parent
|
||||
# task.
|
||||
#
|
||||
# An issue occurs when the domain of automated actions is evaluated by
|
||||
# method search(), because the latter flushes the fields to search on,
|
||||
# which are also the ones being recomputed. Combined with the fact
|
||||
# that recursive fields are not computed in batch, this leads to a huge
|
||||
# amount of recursive calls between the automated action and flush().
|
||||
#
|
||||
# The execution of task.unlink() looks like this:
|
||||
# - mark 'project_id' to compute on subtasks
|
||||
# - delete task
|
||||
# - flush()
|
||||
# - recompute 'project_id' on subtask1
|
||||
# - call compute on subtask1
|
||||
# - in action, search([('id', 'in', subtask1.ids), ('project_id', '=', pid)])
|
||||
# - flush(['id', 'project_id'])
|
||||
# - recompute 'project_id' on subtask2
|
||||
# - call compute on subtask2
|
||||
# - in action search([('id', 'in', subtask2.ids), ('project_id', '=', pid)])
|
||||
# - flush(['id', 'project_id'])
|
||||
# - recompute 'project_id' on subtask3
|
||||
# - call compute on subtask3
|
||||
# - in action, search([('id', 'in', subtask3.ids), ('project_id', '=', pid)])
|
||||
# - flush(['id', 'project_id'])
|
||||
# - recompute 'project_id' on subtask4
|
||||
# ...
|
||||
limit = sys.getrecursionlimit()
|
||||
try:
|
||||
sys.setrecursionlimit(100)
|
||||
task.unlink()
|
||||
finally:
|
||||
sys.setrecursionlimit(limit)
|
||||
Loading…
Add table
Add a link
Reference in a new issue