diff --git a/odoo-bringout-oca-ocb-test_base_automation/README.md b/odoo-bringout-oca-ocb-test_base_automation/README.md
index 8ed964a..3b81d0f 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/README.md
+++ b/odoo-bringout-oca-ocb-test_base_automation/README.md
@@ -12,37 +12,14 @@ 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`.
+- Repository: https://github.com/OCA/OCB
+- Branch: 19.0
+- Path: addons/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
+This package preserves the original LGPL-3 license.
diff --git a/odoo-bringout-oca-ocb-test_base_automation/pyproject.toml b/odoo-bringout-oca-ocb-test_base_automation/pyproject.toml
index 0026791..7375130 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/pyproject.toml
+++ b/odoo-bringout-oca-ocb-test_base_automation/pyproject.toml
@@ -1,12 +1,14 @@
[project]
name = "odoo-bringout-oca-ocb-test_base_automation"
version = "16.0.0"
-description = "Test - Base Automation - Base Automation Tests: Ensure Flow Robustness"
+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",
+ "odoo-bringout-oca-ocb-base_automation>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@@ -16,7 +18,7 @@ classifiers = [
"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.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/__manifest__.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/__manifest__.py
index aa06d31..550aad3 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/__manifest__.py
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/__manifest__.py
@@ -14,6 +14,12 @@ tests independently to functional aspects of other models.""",
'data': [
'security/ir.model.access.csv',
],
+ 'assets': {
+ 'web.assets_tests': [
+ 'test_base_automation/static/tests/**/*',
+ ],
+ },
'installable': True,
+ 'author': 'Odoo S.A.',
'license': 'LGPL-3',
}
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/models/test_base_automation.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/models/test_base_automation.py
index 8a58328..f664540 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/models/test_base_automation.py
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/models/test_base_automation.py
@@ -5,8 +5,8 @@ from dateutil import relativedelta
from odoo import fields, models, api
-class LeadTest(models.Model):
- _name = "base.automation.lead.test"
+class BaseAutomationLeadTest(models.Model):
+ _name = 'base.automation.lead.test'
_description = "Automated Rule Test"
name = fields.Char(string='Subject', required=True)
@@ -15,8 +15,9 @@ class LeadTest(models.Model):
('pending', 'Pending'), ('done', 'Closed')],
string="Status", readonly=True, default='draft')
active = fields.Boolean(default=True)
+ tag_ids = fields.Many2many('test_base_automation.tag')
partner_id = fields.Many2one('res.partner', string='Partner')
- date_action_last = fields.Datetime(string='Last Action', readonly=True)
+ date_automation_last = fields.Datetime(string='Last Automation', readonly=True)
employee = fields.Boolean(compute='_compute_employee_deadline', store=True)
line_ids = fields.One2many('base.automation.line.test', 'lead_id')
@@ -24,12 +25,26 @@ class LeadTest(models.Model):
deadline = fields.Boolean(compute='_compute_employee_deadline', store=True)
is_assigned_to_admin = fields.Boolean(string='Assigned to admin user')
+ stage_id = fields.Many2one(
+ 'test_base_automation.stage', string='Stage',
+ compute='_compute_stage_id', readonly=False, store=True)
+
+ @api.depends('state')
+ def _compute_stage_id(self):
+ Test_Base_AutomationStage = self.env['test_base_automation.stage']
+ for task in self:
+ if not task.stage_id and task.state == 'draft':
+ task.stage_id = (
+ Test_Base_AutomationStage.search([('name', 'ilike', 'new')], limit=1)
+ or Test_Base_AutomationStage.create({'name': 'New'})
+ )
+
@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:
+ if not record.priority or not record.create_date:
record.deadline = False
else:
record.deadline = record.create_date + relativedelta.relativedelta(days=3)
@@ -42,8 +57,15 @@ class LeadTest(models.Model):
return result
-class LineTest(models.Model):
- _name = "base.automation.line.test"
+class BaseAutomationLeadThreadTest(models.Model):
+ _name = 'base.automation.lead.thread.test'
+ _description = "Automated Rule Test With Thread"
+ _inherit = ['base.automation.lead.test', 'mail.thread']
+
+ user_id = fields.Many2one("res.users")
+
+class BaseAutomationLineTest(models.Model):
+ _name = 'base.automation.line.test'
_description = "Automated Rule Line Test"
name = fields.Char()
@@ -51,31 +73,37 @@ class LineTest(models.Model):
user_id = fields.Many2one('res.users')
-class ModelWithAccess(models.Model):
- _name = "base.automation.link.test"
+class BaseAutomationLinkTest(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"
+class BaseAutomationLinkedTest(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'
+class Test_Base_AutomationProject(models.Model):
+ _name = 'test_base_automation.project'
+ _description = 'test_base_automation.project'
name = fields.Char()
task_ids = fields.One2many('test_base_automation.task', 'project_id')
+ stage_id = fields.Many2one('test_base_automation.stage')
+ tag_ids = fields.Many2many('test_base_automation.tag')
+ priority = fields.Selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], default='1')
+ user_ids = fields.Many2many('res.users')
-class Task(models.Model):
- _name = _description = 'test_base_automation.task'
+class Test_Base_AutomationTask(models.Model):
+ _name = 'test_base_automation.task'
+ _description = 'test_base_automation.task'
name = fields.Char()
parent_id = fields.Many2one('test_base_automation.task')
@@ -83,9 +111,72 @@ class Task(models.Model):
'test_base_automation.project',
compute='_compute_project_id', recursive=True, store=True, readonly=False,
)
+ allocated_hours = fields.Float()
+ trigger_hours = fields.Float("Save time to trigger effective hours")
+ remaining_hours = fields.Float("Time Remaining", compute='_compute_remaining_hours', store=True, readonly=True, help="Number of allocated hours minus the number of hours spent.")
+ effective_hours = fields.Float("Time Spent", compute='_compute_effective_hours', compute_sudo=True, store=True)
@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
+
+ @api.depends('trigger_hours')
+ def _compute_effective_hours(self):
+ for task in self:
+ task.effective_hours = task.trigger_hours
+
+ @api.depends('effective_hours')
+ def _compute_remaining_hours(self):
+ for task in self:
+ if not task.allocated_hours:
+ task.remaining_hours = 0.0
+ else:
+ task.remaining_hours = task.allocated_hours - task.effective_hours
+
+
+class Test_Base_AutomationStage(models.Model):
+ _name = 'test_base_automation.stage'
+ _description = 'test_base_automation.stage'
+ name = fields.Char()
+
+
+class Test_Base_AutomationTag(models.Model):
+ _name = 'test_base_automation.tag'
+ _description = 'test_base_automation.tag'
+ name = fields.Char()
+
+
+# pylint: disable=E0102
+class BaseAutomationLeadThreadTest(models.Model): # noqa: F811
+ _name = 'base.automation.lead.thread.test'
+ _inherit = ["base.automation.lead.test", "mail.thread"]
+ _description = "Threaded Lead Test"
+
+
+class BaseAutomationModelWithRecnameChar(models.Model):
+ _name = 'base.automation.model.with.recname.char'
+ _description = "Model with Char as _rec_name"
+ _rec_name = "description"
+ description = fields.Char()
+ user_id = fields.Many2one('res.users', string='Responsible')
+
+
+class BaseAutomationModelWithRecnameM2o(models.Model):
+ _name = 'base.automation.model.with.recname.m2o'
+ _description = "Model with Many2one as _rec_name and name_create"
+ _rec_name = "user_id"
+ user_id = fields.Many2one("base.automation.model.with.recname.char", string='Responsible')
+
+ @api.model
+ def name_create(self, name):
+ name = name.strip()
+ user = self.env["base.automation.model.with.recname.char"].search([('description', '=ilike', name)], limit=1)
+ if user:
+ user_id = user.id
+ else:
+ user_id, _user_name = self.env["base.automation.model.with.recname.char"].name_create(name)
+
+ record = self.create({'user_id': user_id})
+ return record.id, record.display_name
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/security/ir.model.access.csv b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/security/ir.model.access.csv
index 24605b6..002716a 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/security/ir.model.access.csv
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/security/ir.model.access.csv
@@ -1,7 +1,12 @@
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_lead_thread_test,access_base_automation_lead_thread_test,model_base_automation_lead_thread_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
+access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,base.group_user,1,1,1,1
+access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,base.group_user,1,1,1,1
+access_base_automation_model_with_recname_char,access_base_automation_model_with_recname_char,model_base_automation_model_with_recname_char,base.group_user,1,1,1,1
+access_base_automation_model_with_recname_m2o,access_base_automation_model_with_recname_m2o,model_base_automation_model_with_recname_m2o,base.group_user,1,1,1,1
+access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,base.group_user,1,1,1,1
+access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,base.group_user,1,1,1,1
+access_test_base_automation_stage,access_test_base_automation_stage,model_test_base_automation_stage,base.group_user,1,1,1,1
+access_test_base_automation_tag,access_test_base_automation_tag,model_test_base_automation_tag,base.group_user,1,1,1,1
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/static/tests/tour/base_automation_tour.js b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/static/tests/tour/base_automation_tour.js
new file mode 100644
index 0000000..0832011
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/static/tests/tour/base_automation_tour.js
@@ -0,0 +1,671 @@
+import { registry } from "@web/core/registry";
+import { stepUtils } from "@web_tour/tour_utils";
+
+function assertEqual(actual, expected) {
+ if (actual !== expected) {
+ throw new Error(`Assert failed: expected: ${expected} ; got: ${actual}`);
+ }
+}
+
+registry.category("web_tour.tours").add("test_base_automation", {
+ steps: () => [
+ stepUtils.showAppsMenuItem(),
+ {
+ content: "Create new rule",
+ trigger: ".o_control_panel button.o-kanban-button-new",
+ run: "click",
+ },
+ {
+ content: "Enter rule name",
+ trigger: ".o_form_renderer .oe_title .o_input",
+ run: "edit Test rule",
+ },
+ {
+ content: "Select model",
+ trigger: '.o_form_renderer .o_group div[name="model_id"] input',
+ run: "edit res.partner",
+ },
+ {
+ trigger: ".dropdown-menu:contains(Contact)",
+ },
+ {
+ content: "Select model contact",
+ trigger: ".dropdown-menu li a:contains(Contact):not(:has(.fa-spin))",
+ run: "click",
+ },
+ {
+ content: "Open select",
+ trigger: ".o_form_renderer #trigger_0",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_item:contains(On create and edit)",
+ run: "click",
+ },
+ {
+ content: "Add new action",
+ trigger: '.o_form_renderer div[name="action_server_ids"] button',
+ run: "click",
+ },
+ {
+ content: "Set new action to update the record",
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
+ run: "click",
+ },
+ {
+ content: "Focus on the 'update_path' field",
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
+ run: "click",
+ },
+ {
+ content: "Input field name",
+ trigger: ".o_model_field_selector_popover .o_model_field_selector_popover_search input",
+ run: "edit Job Position",
+ },
+ {
+ content: "Select field",
+ trigger:
+ '.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="function"] button',
+ run: "click",
+ },
+ {
+ content: "Open update select",
+ trigger:
+ '.modal .modal-content .o_form_renderer div[name="value"] textarea',
+ run: "edit Test",
+ },
+ {
+ content: "Open update select",
+ trigger: ".modal .modal-content .o_form_button_save",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(.modal))",
+ },
+ ...stepUtils.saveForm(),
+ ],
+});
+
+registry.category("web_tour.tours").add("test_base_automation_on_tag_added", {
+ steps: () => [
+ stepUtils.showAppsMenuItem(),
+ {
+ trigger: ".o_control_panel button.o-kanban-button-new",
+ run: "click",
+ },
+ {
+ trigger: ".o_form_renderer .oe_title .o_input",
+ run: "edit Test rule",
+ },
+ {
+ trigger: '.o_form_renderer .o_group div[name="model_id"] input',
+ run: "edit test_base_automation.project",
+ },
+ {
+ trigger:
+ ".dropdown-menu li a:contains(test_base_automation.project):not(:has(.fa-spin))",
+ run: "click",
+ },
+ {
+ content: "Open select",
+ trigger: ".o_form_renderer #trigger_0",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_menu",
+ run() {
+ const options = [...this.anchor.querySelectorAll(".o_select_menu_item")].map(
+ (el) => el.textContent
+ );
+
+ assertEqual(
+ JSON.stringify(options),
+ JSON.stringify([
+ "Stage is set to",
+ "User is set",
+ "Tag is added",
+ "Priority is set to",
+ "Based on date field",
+ "After creation",
+ "After last update",
+ "On create",
+ "On create and edit",
+ "On deletion",
+ "On UI change",
+ "On webhook"
+ ])
+ );
+ },
+ },
+ {
+ trigger: ".o_select_menu_item:contains(Tag is added)",
+ run: "click",
+ },
+ {
+ trigger: '.o_form_renderer div[name="trg_field_ref"] input',
+ run: "edit test",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(test):not(:has(.fa-spin))",
+ run: "click",
+ },
+ {
+ trigger: '.o_form_renderer div[name="action_server_ids"] button',
+ run: "click",
+ },
+ {
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
+ run: "click",
+ },
+ {
+ content: "Focus on the 'update_path' field",
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
+ run: "click",
+ },
+ {
+ content: "Input field name",
+ trigger:
+ ".o_model_field_selector_popover .o_model_field_selector_popover_search input",
+ run: "edit Name",
+ },
+ {
+ content: "Select field",
+ trigger:
+ '.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="name"] button',
+ run: "click",
+ },
+ {
+ trigger:
+ '.modal .modal-content .o_form_renderer div[name="value"] textarea',
+ run: "edit Test",
+ },
+ {
+ trigger: ".modal .modal-content .o_form_button_save",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(.modal))",
+ },
+ {
+ trigger: '.o_form_renderer div[name="action_server_ids"] button',
+ run: "click",
+ },
+ {
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='state'] span[value*='object_write']",
+ run: "click",
+ },
+ {
+ content: "Focus on the 'update_path' field",
+ trigger:
+ ".modal .modal-content .o_form_renderer [name='update_path'] .o_model_field_selector",
+ run: "click",
+ },
+ {
+ content: "Input field name",
+ trigger:
+ ".o_model_field_selector_popover .o_model_field_selector_popover_search input",
+ run: "edit Priority",
+ },
+ {
+ content: "Select field",
+ trigger:
+ '.o_model_field_selector_popover .o_model_field_selector_popover_page li[data-name="priority"] button',
+ run: "click",
+ },
+ {
+ trigger:
+ '.modal .modal-content .o_form_renderer div[name="selection_value"] input',
+ run: "edit High",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(High):not(:has(.fa-spin))",
+ run: "click",
+ },
+ {
+ trigger: ".modal .modal-content .o_form_button_save",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(.modal-content))",
+ },
+ ...stepUtils.saveForm(),
+ {
+ trigger: ".breadcrumb .o_back_button a",
+ run: "click",
+ },
+ {
+ trigger: ".o_base_automation_kanban_view .o_kanban_record",
+ run() {
+ assertEqual(
+ this.anchor.querySelector(".o_automation_base_info").textContent,
+ "Test ruletest_base_automation.projectTag is addedtest"
+ );
+ assertEqual(
+ this.anchor.querySelector(".o_automation_actions").textContent,
+ "Update test_base_automation.projectUpdate test_base_automation.project"
+ );
+ },
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_open_automation_from_grouped_kanban", {
+ steps: () => [
+ {
+ trigger: ".o_kanban_header:contains(test tag)",
+ run: "hover && click .o_kanban_view .o_group_config button.dropdown-toggle",
+ },
+ {
+ trigger: ".dropdown-menu .o_column_automations",
+ run: "click",
+ },
+ {
+ trigger: ".o_base_automation_kanban_view .o_control_panel button.o-kanban-button-new",
+ run: "click",
+ },
+ {
+ trigger: ".o_form_view",
+ run() {
+ assertEqual(
+ this.anchor.querySelector(".o_field_widget[name='trigger'] input").value,
+ "Tag is added"
+ );
+ assertEqual(
+ this.anchor.querySelector(".o_field_widget[name='trg_field_ref'] input").value,
+ "test tag"
+ );
+ },
+ },
+ {
+ trigger: ".o_form_view .o_field_widget[name='name'] input",
+ run: "edit From Tour",
+ },
+ ...stepUtils.saveForm(),
+ ],
+});
+
+registry.category("web_tour.tours").add("test_kanban_automation_view_stage_trigger", {
+ steps: () => [
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ {
+ trigger: ".o_kanban_record .fs-2:contains(Test Stage)",
+ },
+ {
+ trigger: ".o_kanban_record .o_tag:contains(Stage value)",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_kanban_automation_view_time_trigger", {
+ steps: () => [
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ {
+ trigger: ".o_automation_base_info > div > div > span:nth-child(1):contains(1)",
+ },
+ {
+ trigger: ".o_automation_base_info .text-lowercase:contains(hours)",
+ },
+ {
+ trigger: `.o_kanban_record .o_tag:contains("Last Automation (Automated Rule Test)")`,
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_kanban_automation_view_time_updated_trigger", {
+ steps: () => [
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ {
+ trigger: ".o_automation_base_info > div > div > span:nth-child(1):contains(1)",
+ async run() {
+ const lowercaseTexts = document.querySelectorAll(
+ ".o_automation_base_info .text-lowercase"
+ );
+ assertEqual(lowercaseTexts.length, 2);
+ assertEqual(lowercaseTexts[0].innerText, "hours");
+ assertEqual(lowercaseTexts[1].innerText, "after last update");
+ },
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_kanban_automation_view_create_action", {
+ steps: () => [
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ {
+ trigger: "div[name='action_server_ids']:contains(Create Contact with name NameX)",
+ async run() {
+ assertEqual(document.querySelectorAll(".fa.fa-plus-square").length, 1);
+ },
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_resize_kanban", {
+ steps: () => [
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ {
+ trigger:
+ ".o_automation_actions:contains(Set Active To False Set Active To False Set Active To False)",
+ async run() {
+ document.body.style.setProperty("width", "500px");
+ window.dispatchEvent(new Event("resize"));
+ },
+ },
+ {
+ trigger: ".o_automation_actions:contains(Set Active To False 2 actions)",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_form_view_resequence_actions", {
+ steps: () => [
+ {
+ trigger:
+ ".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_renderer",
+ async run() {
+ assertEqual(
+ this.anchor.innerText,
+ "Update Active 0\nUpdate Active 1\nUpdate Active 2"
+ );
+ },
+ },
+ {
+ trigger:
+ ".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_record:nth-child(3)",
+ run: "drag_and_drop(.o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_record:nth-child(1))",
+ },
+ ...stepUtils.saveForm(),
+ {
+ trigger:
+ ".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_renderer",
+ async run() {
+ assertEqual(
+ this.anchor.innerText,
+ "Update Active 2\nUpdate Active 0\nUpdate Active 1"
+ );
+ },
+ },
+ {
+ trigger:
+ ".o_form_renderer .o_field_widget[name='action_server_ids'] .o_kanban_view .o_cp_buttons button",
+ run: "click",
+ },
+ {
+ trigger: ".modal-content .o_form_renderer",
+ run() {
+ const allFields = this.anchor.querySelectorAll(".o_field_widget[name]");
+ assertEqual(
+ Array.from(allFields)
+ .map((el) => el.getAttribute("name"))
+ .includes("model_id"),
+ false
+ );
+ },
+ },
+ {
+ trigger: ".modal-content .o_form_renderer [name='state'] span[value*='followers']",
+ run: "click",
+ },
+ {
+ trigger:
+ ".modal-content .o_form_renderer [name='state'] span.active[value*='followers']",
+ },
+ {
+ trigger: ".modal-content .o_form_button_cancel",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(.modal-content))",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_form_view_model_id", {
+ steps: () => [
+ {
+ trigger: ".o_field_widget[name='model_id'] input",
+ run: "edit base.automation.line.test",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(Automated Rule Line Test)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name='trigger'] input",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_menu",
+ run() {
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
+ .map((el) => el.textContent)
+ .join(", "),
+ "Values Updated, Timing Conditions, Custom, External"
+ );
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_item"))
+ .map((el) => el.textContent)
+ .join(", "),
+ "User is set, Based on date field, After creation, After last update, On create, On create and edit, On deletion, On UI change, On webhook"
+ );
+ }
+ },
+ {
+ trigger: ".o_field_widget[name='model_id'] input",
+ run: "edit test_base_automation.project",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(test_base_automation.project)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name='trigger'] input",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_menu",
+ run() {
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
+ .map((el) => el.textContent)
+ .join(", "),
+ "Values Updated, Timing Conditions, Custom, External"
+ );
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_item"))
+ .map((el) => el.textContent)
+ .join(", "),
+ "Stage is set to, User is set, Tag is added, Priority is set to, Based on date field, After creation, After last update, On create, On create and edit, On deletion, On UI change, On webhook"
+ );
+ }
+ },
+ {
+ trigger: ".o_form_button_cancel",
+ run: "click",
+ },
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_form_view_custom_reference_field", {
+ steps: () => [
+ {
+ trigger: ".o_field_widget[name='model_id'] input",
+ run: "edit test_base_automation.project",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(test_base_automation.project)",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(.o_field_widget[name='trg_field_ref']))",
+ },
+ {
+ content: "Open select",
+ trigger: ".o_form_renderer #trigger_0",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_item:contains(Stage is set to)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name='trg_field_ref'] input",
+ run: "fill test",
+ },
+ {
+ trigger:
+ ".o_field_widget[name='trg_field_ref'] .o-autocomplete--dropdown-menu:not(:has(a .fa-spin)",
+ run() {
+ assertEqual(this.anchor.innerText, "test stage\nSearch more...");
+ },
+ },
+ {
+ content: "Open select",
+ trigger: ".o_form_renderer #trigger_0",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_item:contains(Tag is added)",
+ run: "click",
+ },
+ {
+ trigger:
+ ".o_field_widget[name='trg_field_ref'] :not(:has(.o-autocomplete--dropdown-menu))",
+ },
+ {
+ trigger: ".o_field_widget[name='trg_field_ref'] input",
+ run: "fill test",
+ },
+ {
+ trigger:
+ ".o_field_widget[name='trg_field_ref'] .o-autocomplete--dropdown-menu:not(:has(a .fa-spin)",
+ run() {
+ assertEqual(this.anchor.innerText, "test tag\nSearch more...");
+ },
+ },
+ {
+ trigger: ".o_form_button_cancel",
+ run: "click",
+ },
+ {
+ trigger: ".o_base_automation_kanban_view",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("test_form_view_mail_triggers", {
+ steps: () => [
+ {
+ trigger: ".o_field_widget[name='model_id'] input",
+ run: "edit base.automation.lead.test",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(Automated Rule Test)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name='trigger'] input",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_menu",
+ run() {
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_group"))
+ .map((el) => el.textContent)
+ .join(", "),
+ "Values Updated, Timing Conditions, Custom, External"
+ );
+ },
+ },
+ {
+ trigger: ".o_field_widget[name='model_id'] input",
+ run: "edit base.automation.lead.thread.test",
+ },
+ {
+ trigger: ".dropdown-menu li a:contains(Threaded Lead Test)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name='trigger'] input",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_menu",
+ run() {
+ assertEqual(
+ Array.from(this.anchor.querySelectorAll(".o_select_menu_group "))
+ .map((el) => el.textContent)
+ .join(", "),
+ "Values Updated, Email Events, Timing Conditions, Custom, External"
+ );
+ }
+ },
+ {
+ trigger: "button.o_form_button_cancel",
+ run: "click",
+ },
+ {
+ trigger: "body:not(:has(button.o_form_button_cancel)",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("base_automation.on_change_rule_creation", {
+ url: "/odoo/action-base_automation.base_automation_act",
+ steps: () => [
+ {
+ trigger: ".o-kanban-button-new",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name=name] input",
+ run: "edit Test rule",
+ },
+ {
+ trigger: ".o_field_widget[name=model_id] input",
+ run: "edit ir.ui.view",
+ },
+ {
+ trigger: ".ui-menu-item > a:text(View)",
+ run: "click",
+ },
+ {
+ content: "Open select",
+ trigger: ".o_form_renderer #trigger_0",
+ run: "click",
+ },
+ {
+ trigger: ".o_select_menu_item:contains(On UI change)",
+ run: "click",
+ },
+ {
+ trigger: ".o_field_widget[name=on_change_field_ids] input",
+ run: "edit Active",
+ },
+ {
+ trigger: ".ui-menu-item > a:text(Active)",
+ run: "click",
+ },
+ ...stepUtils.saveForm(),
+ ],
+});
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/__init__.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/__init__.py
index b96c2ec..a85f5d0 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/__init__.py
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/__init__.py
@@ -2,3 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_flow
+from . import test_server_actions
+from . import test_tour
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_flow.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_flow.py
index e62f850..ff7e047 100644
--- a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_flow.py
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_flow.py
@@ -1,112 +1,63 @@
# # -*- coding: utf-8 -*-
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from unittest.mock import patch
+import datetime
+import json
import sys
+from freezegun import freeze_time
+from unittest.mock import patch
+from odoo import Command
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
-from odoo.tests import common, tagged
-from odoo.exceptions import AccessError
+from odoo.exceptions import AccessError, ValidationError
+from odoo.tests import Form, common, tagged, WhitespaceInsensitive
+from odoo.tools import mute_logger
+
+
+def create_automation(self, **kwargs):
+ """
+ Create a transient automation with the given data and actions
+ The created automation is cleaned up at the end of the calling test
+ """
+ vals = {'name': 'Automation'}
+ vals.update(kwargs)
+ actions_data = vals.pop('_actions', [])
+ if not isinstance(actions_data, list):
+ actions_data = [actions_data]
+ automation_id = self.env['base.automation'].create(vals)
+ action_ids = self.env['ir.actions.server'].create(
+ [
+ {
+ 'name': 'Action',
+ 'base_automation_id': automation_id.id,
+ 'model_id': automation_id.model_id.id,
+ 'usage': 'base_automation',
+ **action,
+ }
+ for action in actions_data
+ ]
+ )
+ action_ids.flush_recordset()
+ automation_id.write({'action_server_ids': [Command.set(action_ids.ids)]})
+ self.addCleanup(automation_id.unlink)
+ return automation_id
@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([
+ self.lead_model = self.env.ref('test_base_automation.model_base_automation_lead_test')
+ self.project_model = self.env.ref('test_base_automation.model_test_base_automation_project')
+ self.test_mail_template_automation = self.env['mail.template'].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)]",
+ 'name': 'Template Automation',
+ 'model_id': self.env['ir.model']._get_id("base.automation.lead.thread.test"),
+ 'body_html': """<div>Email automation</div>""",
}
- ])
-
- def tearDown(self):
- super().tearDown()
- self.env['base.automation']._unregister_hook()
+ )
+ self.res_partner_1 = self.env['res.partner'].create({'name': 'My Partner'})
def create_lead(self, **kwargs):
vals = {
@@ -114,164 +65,170 @@ record['name'] = record.name + 'X'""",
'user_id': self.user_root.id,
}
vals.update(kwargs)
- return self.env['base.automation.lead.test'].create(vals)
+ lead = self.env['base.automation.lead.test'].create(vals)
+ self.addCleanup(lead.unlink)
+ return lead
- def test_00_check_to_state_open_pre(self):
+ def create_line(self, **kwargs):
+ vals = {
+ 'name': 'Line Test',
+ 'user_id': self.user_root.id,
+ }
+ vals.update(kwargs)
+ line = self.env['base.automation.line.test'].create(vals)
+ self.addCleanup(line.unlink)
+ return line
+
+ def create_project(self, **kwargs):
+ vals = {'name': 'Project Test'}
+ vals.update(kwargs)
+ project = self.env['test_base_automation.project'].create(vals)
+ self.addCleanup(project.unlink)
+ return project
+
+ def create_stage(self, **kwargs):
+ vals = {'name': 'Stage Test'}
+ vals.update(kwargs)
+ stage = self.env['test_base_automation.stage'].create(vals)
+ self.addCleanup(stage.unlink)
+ return stage
+
+ def create_tag(self, **kwargs):
+ vals = {'name': 'Tag Test'}
+ vals.update(kwargs)
+ tag = self.env['test_base_automation.tag'].create(vals)
+ self.addCleanup(tag.unlink)
+ return tag
+
+ def test_000_on_create_or_write(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.
+ Test case: on save, simple case
+ - trigger: on_create_or_write
"""
- lead = self.create_lead(state='open')
+ # --- Without the automation ---
+ lead = self.create_lead()
+ self.assertEqual(lead.state, 'draft')
+ self.assertEqual(lead.user_id, self.user_root)
+
+ # --- With the automation ---
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+
+ # Write a lead should trigger the automation
+ lead.write({'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'.")
+ self.assertEqual(lead.user_id, self.user_demo)
- def test_01_check_to_state_draft_post(self):
+ # Create a lead should trigger the automation
+ lead2 = self.create_lead()
+ self.assertEqual(lead2.state, 'draft')
+ self.assertEqual(lead2.user_id, self.user_demo)
+
+ def test_001_on_create_or_write(self):
"""
- Check that a new record changes its responsible when there is a postcondition
- filter which check that the state is draft.
+ Test case: on save, with filter_domain
+ - trigger: on_create_or_write
+ - apply when: 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'.")
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'draft')]",
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
- 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. """
+ # Create a lead with state=open should not trigger the automation
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})
+
+ # Write a lead to state=draft should trigger the automation
+ lead.write({'state': 'draft'})
self.assertEqual(lead.state, 'draft')
+ self.assertEqual(lead.user_id, self.user_demo)
- 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)
+ # Create a lead with state=draft should trigger the automation
+ lead_2 = self.create_lead()
+ self.assertEqual(lead_2.state, 'draft')
+ self.assertEqual(lead_2.user_id, self.user_demo)
- def test_20_indirect_line(self):
+ def test_002_on_create_or_write(self):
"""
- Check that creating a lead with a line executes rules on both records.
+ Test case: on save, with filter_pre_domain and filter_domain
+ - trigger: on_create_or_write
+ - before update filter: state is 'open'
+ - apply when: state is 'done'
"""
- 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.")
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_pre_domain="[('state', '=', 'open')]",
+ filter_domain="[('state', '=', 'done')]",
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+
+ # Create a lead with state=open should not trigger the automation
+ lead = self.create_lead(state='open')
+ self.assertEqual(lead.state, 'open')
+ self.assertEqual(lead.user_id, self.user_root)
+
+ # Write a lead to state=pending THEN to state=done should not trigger the automation
+ lead.write({'state': 'pending'})
+ self.assertEqual(lead.state, 'pending')
+ self.assertEqual(lead.user_id, self.user_root)
+ lead.write({'state': 'done'})
+ self.assertEqual(lead.state, 'done')
+ self.assertEqual(lead.user_id, self.user_root)
+
+ # Write a lead from state=open to state=done should trigger the automation
+ lead.write({'state': 'open'})
+ self.assertEqual(lead.state, 'open')
+ self.assertEqual(lead.user_id, self.user_root)
+ lead.write({'state': 'done'})
+ self.assertEqual(lead.state, 'done')
+ self.assertEqual(lead.user_id, self.user_demo)
+
+ # Create a lead with state=open then write it to state=done should trigger the automation
+ lead_2 = self.create_lead(state='open')
+ self.assertEqual(lead_2.state, 'open')
+ self.assertEqual(lead_2.user_id, self.user_root)
+ lead_2.write({'state': 'done'})
+ self.assertEqual(lead_2.state, 'done')
+ self.assertEqual(lead_2.user_id, self.user_demo)
+
+ # Create a lead with state=done should trigger the automation,
+ # as verifying the filter_pre_domain does not make sense on create
+ lead_3 = self.create_lead(state='done')
+ self.assertEqual(lead_3.state, 'done')
+ self.assertEqual(lead_3.user_id, self.user_demo)
+
+ def test_003_on_create_or_write(self):
+ """ Check that the on_create_or_write trigger works as expected with trigger fields. """
+ lead_state_field = self.env.ref('test_base_automation.field_base_automation_lead_test__state')
+ automation = create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ trigger_field_ids=[Command.link(lead_state_field.id)],
+ _actions={
+ 'state': 'code',
+ 'code': """
+if env.context.get('old_values', None): # on write only
+ record = model.browse(env.context['active_id'])
+ record['name'] = record.name + 'X'""",
+ },
+ )
- 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
+ lead.partner_id = partner1
self.assertEqual(lead.name, 'X', "No update until now.")
lead.state = 'open'
@@ -284,8 +241,8 @@ record['name'] = record.name + 'X'""",
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])]})
+ lead_partner_id_field = self.env.ref('test_base_automation.field_base_automation_lead_test__partner_id')
+ automation.write({'trigger_field_ids': [Command.set([lead_partner_id_field.id])]})
partner2 = self.env['res.partner'].create({'name': 'A new partner'})
lead.name = 'X'
@@ -297,8 +254,265 @@ record['name'] = record.name + 'X'""",
self.assertEqual(lead.name, 'XX', "No update should have happened.")
lead.partner_id = partner1
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
+ lead.partner_id = partner1
+ self.assertEqual(lead.name, 'XXX', "No update should have happened.")
- def test_30_modelwithoutaccess(self):
+ def test_010_recompute(self):
+ """
+ Test case: automation is applied whenever a field is recomputed
+ after a change on another model.
+ - trigger: on_create_or_write
+ - apply when: employee is True
+ """
+ partner = self.res_partner_1
+ partner.write({'employee': False})
+
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('employee', '=', True)]",
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+
+ lead = self.create_lead(partner_id=partner.id)
+ self.assertEqual(lead.partner_id, partner)
+ self.assertEqual(lead.employee, False)
+ self.assertEqual(lead.user_id, self.user_root)
+
+ # change partner, recompute on lead should trigger the rule
+ partner.write({'employee': True})
+ self.env.flush_all() # ensures the recomputation is done
+ self.assertEqual(lead.partner_id, partner)
+ self.assertEqual(lead.employee, True)
+ self.assertEqual(lead.user_id, self.user_demo)
+
+ def test_011_recompute(self):
+ """
+ Test case: automation is applied whenever a field is recomputed.
+ The context contains the target field.
+ - trigger: on_create_or_write
+ """
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ _actions={
+ 'state': 'code',
+ 'code': """
+if env.context.get('old_values', None): # on write
+ if 'user_id' in env.context['old_values'][record.id]:
+ record.write({'is_assigned_to_admin': (record.user_id.id == 1)})""",
+ },
+ )
+
+ partner = self.res_partner_1
+ lead = self.create_lead(state='draft', partner_id=partner.id)
+ self.assertEqual(lead.deadline, False)
+ self.assertEqual(lead.is_assigned_to_admin, False)
+
+ # change priority and user; this triggers deadline recomputation, and
+ # the server action should set is_assigned_to_admin field to True
+ lead.write({'priority': True, 'user_id': self.user_root.id})
+ self.assertNotEqual(lead.deadline, False)
+ self.assertEqual(lead.is_assigned_to_admin, True)
+
+ def test_012_recompute(self):
+ """
+ Test case: automation is applied whenever a field is recomputed.
+ - trigger: on_create_or_write
+ - if updating fields: [deadline]
+ """
+ active_field = self.env.ref("test_base_automation.field_base_automation_lead_test__active")
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ trigger_field_ids=[Command.link(active_field.id)],
+ _actions={
+ 'state': 'code',
+ 'code': """
+if not env.context.get('old_values', None): # on create
+ record.write({'state': 'open'})
+else:
+ record.write({'priority': not record.priority})""",
+ },
+ )
+
+ lead = self.create_lead(state='draft', priority=False)
+ self.assertEqual(lead.state, 'open') # the rule has set the state to open on create
+ self.assertEqual(lead.priority, False)
+
+ # change state; the rule should not be triggered
+ lead.write({'state': 'pending'})
+ self.assertEqual(lead.state, 'pending')
+ self.assertEqual(lead.priority, False)
+
+ # change active; the rule should be triggered
+ lead.write({'active': False})
+ self.assertEqual(lead.state, 'pending')
+ self.assertEqual(lead.priority, True)
+
+ # change active again; the rule should still be triggered
+ lead.write({'active': True})
+ self.assertEqual(lead.state, 'pending')
+ self.assertEqual(lead.priority, False)
+
+ def test_013_recompute(self):
+ """
+ Test case: automation is applied whenever a field is recomputed
+ - trigger: on_create_or_write
+ - if updating fields: [deadline]
+ - before update filter: deadline is not set
+ - apply when: deadline is set
+ """
+ deadline_field = self.env.ref("test_base_automation.field_base_automation_lead_test__deadline")
+ create_automation(
+ self,
+ model_id=self.env['ir.model']._get_id('base.automation.lead.thread.test'),
+ trigger='on_create_or_write',
+ trigger_field_ids=[Command.link(deadline_field.id)],
+ filter_pre_domain="[('deadline', '=', False)]",
+ filter_domain="[('deadline', '!=', False)]",
+ _actions={
+ 'state': 'mail_post',
+ 'mail_post_method': 'email',
+ 'template_id': self.test_mail_template_automation.id,
+ },
+ )
+
+ send_mail_count = 0
+
+ def _patched_send_mail(*args, **kwargs):
+ nonlocal send_mail_count
+ send_mail_count += 1
+
+ patcher = patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail)
+ self.startPatcher(patcher)
+
+ lead = self.env['base.automation.lead.thread.test'].create({
+ 'name': "Lead Test",
+ 'user_id': self.user_root.id,
+ })
+ self.addCleanup(lead.unlink)
+ self.assertEqual(lead.priority, False)
+ self.assertEqual(lead.deadline, False)
+ self.assertEqual(send_mail_count, 0)
+
+ lead.write({'priority': True})
+ self.assertEqual(lead.priority, True)
+ self.assertNotEqual(lead.deadline, False)
+ self.assertEqual(send_mail_count, 1)
+
+ def test_020_recursive(self):
+ """ Check that a rule is executed recursively by a secondary change. """
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ _actions={
+ 'state': 'code',
+ 'code': """
+if env.context.get('old_values', None): # on write
+ if 'partner_id' in env.context['old_values'][record.id]:
+ record.write({'state': 'draft'})""",
+ },
+ )
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'draft')]",
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+
+ 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
+ # and then the rule that modifies the user
+ partner = self.res_partner_1
+ lead.write({'partner_id': partner.id})
+ self.assertEqual(lead.state, 'draft')
+ self.assertEqual(lead.user_id, self.user_demo)
+
+ def test_021_recursive(self):
+ """ Check what it does with a recursive infinite loop """
+ automations = [
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'draft')]",
+ _actions={'state': 'code', 'code': "record.write({'state': 'pending'})"},
+ ),
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'pending')]",
+ _actions={'state': 'code', 'code': "record.write({'state': 'open'})"},
+ ),
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'open')]",
+ _actions={'state': 'code', 'code': "record.write({'state': 'done'})"},
+ ),
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ filter_domain="[('state', '=', 'done')]",
+ _actions={'state': 'code', 'code': "record.write({'state': 'draft'})"},
+ ),
+ ]
+
+ def _patch(*args, **kwargs):
+ self.assertEqual(args[0], automations.pop(0))
+
+ patcher = patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._process', _patch)
+ self.startPatcher(patcher)
+
+ lead = self.create_lead(state='draft')
+ self.assertEqual(lead.state, 'draft')
+ self.assertEqual(len(automations), 0) # all automations have been processed # CHECK if proper assertion ?
+
+ def test_030_submodel(self):
+ """ Check that a rule on a submodel is executed when the parent is modified. """
+ # --- Without the automations ---
+ line = self.create_line()
+ self.assertEqual(line.user_id, self.user_root)
+
+ lead = self.create_lead(line_ids=[(0, 0, {'name': 'Line', 'user_id': self.user_root.id})])
+ self.assertEqual(lead.user_id, self.user_root)
+ self.assertEqual(lead.line_ids.user_id, self.user_root)
+
+ # --- With the automations ---
+ comodel = self.env.ref('test_base_automation.model_base_automation_line_test')
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+ create_automation(
+ self,
+ model_id=comodel.id,
+ trigger='on_create_or_write',
+ _actions={'state': 'code', 'code': "record.write({'user_id': %s})" % (self.user_demo.id)},
+ )
+
+ line = self.create_line(user_id=self.user_root.id)
+ self.assertEqual(line.user_id, self.user_demo) # rule on secondary model
+
+ lead = self.create_lead(line_ids=[(0, 0, {'name': 'Line', 'user_id': self.user_root.id})])
+ self.assertEqual(lead.user_id, self.user_demo) # rule on primary model
+ self.assertEqual(lead.line_ids.user_id, self.user_demo) # rule on secondary model
+
+ def test_040_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
@@ -309,28 +523,26 @@ record['name'] = record.name + 'X'""",
- create a record in the non restricted model in demo
"""
Model = self.env['base.automation.link.test']
+ model_id = self.env.ref('test_base_automation.model_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({
+ comodel_access = self.env.ref('test_base_automation.access_base_automation_linked_test')
+ comodel_access.group_id = self.env['res.groups'].create({
'name': "Access to base.automation.linked.test",
- "users": [(6, 0, [self.user_admin.id,])]
+ "user_ids": [Command.link(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')
+ Comodel.with_user(self.user_demo).check_access('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]"
- })
+ create_automation(
+ self,
+ model_id=model_id.id,
+ trigger='on_create_or_write',
+ filter_pre_domain="[('linked_id.another_field', '=', 'something')]",
+ _actions={'state': 'code', 'code': 'action = [rec.name for rec in records]'},
+ )
Comodel.create([
{'name': 'a first record', 'another_field': 'something'},
{'name': 'another record', 'another_field': 'something different'},
@@ -341,23 +553,807 @@ record['name'] = record.name + 'X'""",
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]"
- })
+ create_automation(
+ self,
+ model_id=model_id.id,
+ trigger='on_create_or_write',
+ filter_pre_domain="[('linked_id', '=', 'whatever')]",
+ _actions={'state': 'code', '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'})
+ def test_050_on_create_or_write_with_create_record(self):
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_create_or_write',
+ _actions={
+ 'state': 'object_create',
+ 'crud_model_id': self.project_model.id,
+ 'value': 'foo',
+ },
+ )
+ lead = self.create_lead()
+ search_result = self.env['test_base_automation.project'].name_search('foo')
+ self.assertEqual(len(search_result), 1, 'One record on the project model should have been created')
+
+ lead.write({'name': 'renamed lead'})
+ search_result = self.env['test_base_automation.project'].name_search('foo')
+ self.assertEqual(len(search_result), 2, 'Another record on the project model should have been created')
+
+ # ----------------------------
+ # The following does not work properly as it is a known
+ # limitation of the implementation since at least 14.0
+ # -> AssertionError: 4 != 3 : Another record on the secondary model should have been created
+
+ # # write on a field that is a dependency of another computed field
+ # lead.write({'priority': True})
+ # search_result = self.env['test_base_automation.project'].name_search('foo')
+ # self.assertEqual(len(search_result), 3, 'Another record on the secondary model should have been created')
+ # ----------------------------
+
+ def test_060_on_stage_set(self):
+ stage_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.project_model.id),
+ ('name', '=', 'stage_id'),
+ ])
+ stage1 = self.create_stage()
+ stage2 = self.create_stage()
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_stage_set',
+ trigger_field_ids=[stage_field.id],
+ filter_domain="[('stage_id', '=', %s)]" % stage1.id,
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+ project = self.create_project()
+ self.assertEqual(project.name, 'Project Test')
+ project.write({'stage_id': stage1.id})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'stage_id': stage1.id})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'stage_id': stage2.id})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'stage_id': False})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'stage_id': stage1.id})
+ self.assertEqual(project.name, 'Project Test!!')
+
+ def test_070_on_user_set(self):
+ user_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.lead_model.id),
+ ('name', '=', 'user_id'),
+ ])
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_user_set',
+ trigger_field_ids=[user_field.id],
+ filter_domain="[('user_id', '!=', False)]",
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+
+ lead = self.create_lead()
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'user_id': self.user_demo.id})
+ self.assertEqual(lead.name, 'Lead Test!!')
+ lead.write({'user_id': self.user_demo.id})
+ self.assertEqual(lead.name, 'Lead Test!!')
+ lead.write({'user_id': self.user_admin.id})
+ self.assertEqual(lead.name, 'Lead Test!!!')
+ lead.write({'user_id': False})
+ self.assertEqual(lead.name, 'Lead Test!!!')
+ lead.write({'user_id': self.user_demo.id})
+ self.assertEqual(lead.name, 'Lead Test!!!!')
+
+ def test_071_on_user_set(self):
+ # same test as above but with the user_ids many2many on a project
+ user_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.project_model.id),
+ ('name', '=', 'user_ids'),
+ ])
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_user_set',
+ trigger_field_ids=[user_field.id],
+ filter_domain="[('user_ids', '!=', False)]",
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+
+ project = self.create_project()
+ self.assertEqual(project.name, 'Project Test')
+ project.write({'user_ids': [Command.set([self.user_demo.id])]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'user_ids': [Command.set([self.user_demo.id])]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'user_ids': [Command.link(self.user_admin.id)]})
+ self.assertEqual(project.name, 'Project Test!!')
+ # Unlinking a user while there are still other users does trigger the automation
+ # This behavior could be changed in the future but needs a bit of investigation
+ project.write({'user_ids': [Command.unlink(self.user_admin.id)]})
+ self.assertEqual(project.name, 'Project Test!!!')
+ project.write({'user_ids': [Command.set([])]})
+ self.assertEqual(project.name, 'Project Test!!!')
+ project.write({'user_ids': [Command.set([self.user_demo.id])]})
+ self.assertEqual(project.name, 'Project Test!!!!')
+
+ def test_080_on_tag_set(self):
+ tag_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.project_model.id),
+ ('name', '=', 'tag_ids'),
+ ])
+ tag1 = self.create_tag()
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_tag_set',
+ trigger_field_ids=[tag_field.id],
+ filter_pre_domain="[('tag_ids', 'not in', [%s])]" % tag1.id,
+ filter_domain="[('tag_ids', 'in', [%s])]" % tag1.id,
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+ project = self.create_project()
+ self.assertEqual(project.name, 'Project Test')
+ project.write({'tag_ids': [Command.set([tag1.id])]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'tag_ids': [Command.set([tag1.id])]})
+ self.assertEqual(project.name, 'Project Test!')
+
+ tag2 = self.create_tag()
+ project.write({'tag_ids': [Command.link(tag2.id)]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'tag_ids': [Command.clear()]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'tag_ids': [Command.set([tag2.id])]})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'tag_ids': [Command.link(tag1.id)]})
+ self.assertEqual(project.name, 'Project Test!!')
+
+ def test_090_on_state_set(self):
+ state_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.lead_model.id),
+ ('name', '=', 'state'),
+ ])
+
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_state_set',
+ trigger_field_ids=[state_field.id],
+ filter_domain="[('state', '=', 'done')]",
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+
+ lead = self.create_lead()
+ self.assertEqual(lead.name, 'Lead Test')
+ lead.write({'state': 'open'})
+ self.assertEqual(lead.name, 'Lead Test')
+ lead.write({'state': 'done'})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'state': 'done'})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'state': 'open'})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'state': 'done'})
+ self.assertEqual(lead.name, 'Lead Test!!')
+
+ def test_100_on_priority_set(self):
+ priority_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.project_model.id),
+ ('name', '=', 'priority'),
+ ])
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_priority_set',
+ trigger_field_ids=[priority_field.id],
+ filter_domain="[('priority', '=', '2')]",
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+ project = self.create_project()
+ self.assertEqual(project.name, 'Project Test')
+ self.assertEqual(project.priority, '1')
+ project.write({'priority': '0'})
+ self.assertEqual(project.name, 'Project Test')
+ project.write({'priority': '2'})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'priority': '2'})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'priority': '0'})
+ self.assertEqual(project.name, 'Project Test!')
+ project.write({'priority': '2'})
+ self.assertEqual(project.name, 'Project Test!!')
+
+ def test_110_on_archive(self):
+ active_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.lead_model.id),
+ ('name', '=', 'active'),
+ ])
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_archive',
+ trigger_field_ids=[active_field.id],
+ filter_domain="[('active', '=', False)]",
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+ lead = self.create_lead()
+ self.assertEqual(lead.name, 'Lead Test')
+ lead.write({'active': False})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'active': True})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'active': False})
+ self.assertEqual(lead.name, 'Lead Test!!')
+ lead.write({'active': False})
+ self.assertEqual(lead.name, 'Lead Test!!')
+
+ def test_110_on_unarchive(self):
+ active_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', self.lead_model.id),
+ ('name', '=', 'active'),
+ ])
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_unarchive',
+ trigger_field_ids=[active_field.id],
+ filter_domain="[('active', '=', True)]",
+ _actions={'state': 'object_write', 'evaluation_type': 'equation', 'update_path': 'name', 'value': "record.name + '!'"},
+ )
+ lead = self.create_lead()
+ self.assertEqual(lead.name, 'Lead Test')
+ lead.write({'active': False})
+ self.assertEqual(lead.name, 'Lead Test')
+ lead.write({'active': True})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'active': False})
+ self.assertEqual(lead.name, 'Lead Test!')
+ lead.write({'active': True})
+ self.assertEqual(lead.name, 'Lead Test!!')
+ lead.write({'active': True})
+ self.assertEqual(lead.name, 'Lead Test!!')
+
+ def test_120_on_change(self):
+ Model = self.env.get(self.lead_model.model)
+ lead_name_field = self.env['ir.model.fields']._get(self.lead_model.model, "name")
+ self.assertEqual(lead_name_field.name in Model._onchange_methods, False)
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_change',
+ filter_domain="[('name', 'like', 'IMPORTANT')]",
+ on_change_field_ids=[lead_name_field.id],
+ _actions={
+ 'state': 'code',
+ 'code': """
+action = {
+ 'value': {
+ 'priority': '[IMPORTANT]' in record.name,
+ }
+}
+ """,
+ },
+ )
+ self.assertEqual(lead_name_field.name in Model._onchange_methods, True)
+
+ with Form(self.env[self.lead_model.model]) as f:
+ self.assertEqual(f.priority, False)
+ f.name = 'Lead Test'
+ self.assertEqual(f.priority, False)
+
+ # changed because contains "IMPORTANT", true because contains "[IMPORTANT]"
+ f.name = 'Lead Test [IMPORTANT]'
+ self.assertEqual(f.priority, True)
+
+ # not changed because does not contain "IMPORTANT"
+ f.name = 'Lead Test'
+ self.assertEqual(f.priority, True)
+
+ # changed because contains "IMPORTANT", false because does not contain "[IMPORTANT]"
+ f.name = 'Lead Test [NOT IMPORTANT]'
+ self.assertEqual(f.priority, False)
+
+ # changed because contains "IMPORTANT", true because contains "[IMPORTANT]"
+ f.name = 'Lead Test [IMPORTANT]'
+ self.assertEqual(f.priority, True)
+
+ def test_121_on_change_with_domain_field_not_in_view(self):
+ lead_name_field = self.env['ir.model.fields']._get(self.lead_model.model, "name")
+ create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_change',
+ filter_domain="[('active', '!=', False)]",
+ on_change_field_ids=[lead_name_field.id],
+ _actions={
+ 'state': 'code',
+ 'code': """
+action = {
+ 'value': {
+ 'priority': '[IMPORTANT]' in record.name,
+ }
+}
+ """,
+ },
+ )
+ my_view = self.env["ir.ui.view"].create({
+ "name": "My View",
+ "model": self.lead_model.model,
+ "type": "form",
+ "arch": """
+
+ """,
+ })
+ record = self.env[self.lead_model.model].create({
+ "name": "Test Lead",
+ "active": False,
+ "priority": False,
+ })
+ self.assertEqual(record.priority, False)
+ with Form(record, view=my_view) as f:
+ f.name = "[IMPORTANT] Lead"
+ self.assertEqual(record.priority, False)
+
+ record.name = "Test Lead"
+ record.active = True
+ self.assertEqual(record.priority, False)
+ with Form(record, view=my_view) as f:
+ f.name = "[IMPORTANT] Lead"
+ self.assertEqual(record.priority, True)
+
+ def test_130_on_unlink(self):
+ automation = create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_unlink',
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+
+ called_count = 0
+
+ def _patch(*args, **kwargs):
+ nonlocal called_count
+ called_count += 1
+ self.assertEqual(args[0], automation)
+
+ patcher = patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._process', _patch)
+ self.startPatcher(patcher)
+
+ lead = self.create_lead()
+ self.assertEqual(called_count, 0)
+ lead.unlink()
+ self.assertEqual(called_count, 1)
+
+ @property
+ def automation_cron(self):
+ return self.env.ref('base_automation.ir_cron_data_base_automation_check')
+
+ def test_004_check_method_trigger_field(self):
+ model = self.env["ir.model"]._get("base.automation.lead.test")
+ TIME_TRIGGERS = [
+ 'on_time',
+ 'on_time_created',
+ 'on_time_updated',
+ ]
+ self.env["base.automation"].search([('trigger', 'in', TIME_TRIGGERS)]).active = False
+
+ automation = self.env["base.automation"].create({
+ "name": "Cron BaseAuto",
+ "trigger": "on_time",
+ "model_id": model.id,
+ })
+
+ # first run, check we have a field set
+ # this does not happen using the UI where the trigger is forced to be set
+ self.assertFalse(automation.last_run)
+ with self.assertLogs('odoo.addons.base_automation', 'WARNING') as capture, self.enter_registry_test_mode():
+ self.automation_cron.method_direct_trigger()
+ self.assertRegex(capture.output[0], r"Missing date trigger")
+ automation.trg_date_id = model.field_id.filtered(lambda f: f.name == 'date_automation_last')
+
+ # normal run
+ with self.enter_registry_test_mode():
+ self.automation_cron.method_direct_trigger()
+ self.assertTrue(automation.last_run)
+
+ @common.freeze_time('2020-01-01 03:00:00')
+ def test_004_check_method_process(self):
+ model = self.env["ir.model"]._get("base.automation.lead.test")
+ TIME_TRIGGERS = [
+ 'on_time',
+ 'on_time_created',
+ 'on_time_updated',
+ ]
+ self.env["base.automation"].search([('trigger', 'in', TIME_TRIGGERS)]).active = False
+
+ automation = self.env["base.automation"].create({
+ "name": "Cron BaseAuto",
+ "trigger": "on_time",
+ "model_id": model.id,
+ "trg_date_id": model.field_id.filtered(lambda f: f.name == 'date_automation_last').id,
+ "trg_date_range": 2,
+ "trg_date_range_type": "minutes",
+ "trg_date_range_mode": "after",
+ })
+
+ with (
+ patch.object(automation.__class__, '_process', side_effect=automation._process) as mock,
+ self.enter_registry_test_mode(),
+ ):
+ with patch.object(self.env.cr, '_now', now := datetime.datetime.now()):
+ past_date = now - datetime.timedelta(1)
+ self.env["base.automation.lead.test"].create([{
+ 'name': f'lead {i}',
+ # 2 without a date, 8 set in past, 5 set in future (10, 11, ... minutes after now)
+ 'date_automation_last': False if i < 2 else past_date if i < 10 else now + datetime.timedelta(minutes=i),
+ } for i in range(15)])
+ with common.freeze_time('2020-01-01 03:02:01'), patch.object(self.env.cr, '_now', datetime.datetime.now()):
+ # process records
+ self.automation_cron.method_direct_trigger()
+ self.assertEqual(mock.call_count, 10)
+ self.assertEqual(automation.last_run, self.env.cr.now())
+ with common.freeze_time('2020-01-01 03:13:59'), patch.object(self.env.cr, '_now', datetime.datetime.now()):
+ # 2 in the future (because of timing)
+ # 10 previously done records because we use the date_automation_last as trigger without delay
+ self.automation_cron.method_direct_trigger()
+ self.assertEqual(mock.call_count, 22)
+ self.assertEqual(automation.last_run, self.env.cr.now())
+ # test triggering using a calendar
+ automation.trg_date_calendar_id = self.env["resource.calendar"].search([], limit=1).ensure_one()
+ automation.trg_date_range_type = 'day'
+ self.env["base.automation.lead.test"].create({'name': 'calendar'}) # for the run
+ with common.freeze_time('2020-02-02 03:11:00'), patch.object(self.env.cr, '_now', datetime.datetime.now()):
+ self.automation_cron.method_direct_trigger()
+ self.assertEqual(mock.call_count, 38)
+
+ def test_005_check_model_with_different_rec_name_char(self):
+ model = self.env["ir.model"]._get("base.automation.model.with.recname.char")
+
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_create_or_write',
+ _actions={
+ 'state': 'object_create',
+ 'crud_model_id': model.id,
+ 'value': "Test _rec_name Automation",
+ },
+ )
+
+ self.create_project()
+ record_count = self.env[model.model].search_count([('description', '=', 'Test _rec_name Automation')])
+ self.assertEqual(record_count, 1, "Only one record should have been created")
+
+ def test_006_check_model_with_different_m2o_name_create(self):
+ model = self.env["ir.model"]._get("base.automation.model.with.recname.m2o")
+
+ create_automation(
+ self,
+ model_id=self.project_model.id,
+ trigger='on_create_or_write',
+ _actions={
+ 'state': 'object_create',
+ 'crud_model_id': model.id,
+ 'value': "Test _rec_name Automation",
+ },
+ )
+
+ self.create_project()
+ record_count = self.env[model.model].search_count([('user_id', '=', 'Test _rec_name Automation')])
+ self.assertEqual(record_count, 1, "Only one record should have been created")
+
+ def test_140_copy_should_copy_actions(self):
+ """ Copying an automation should copy its actions. """
+ automation = create_automation(
+ self,
+ model_id=self.lead_model.id,
+ trigger='on_change',
+ _actions={'state': 'code', 'code': "record.write({'name': record.name + '!'})"},
+ )
+ action_ids = automation.action_server_ids
+
+ copy_automation = automation.copy()
+ copy_action_ids = copy_automation.action_server_ids
+ # Same number of actions but id should be different
+ self.assertEqual(len(action_ids), 1)
+ self.assertEqual(len(copy_action_ids), len(action_ids))
+ self.assertNotEqual(copy_action_ids, action_ids)
+
+ def test_add_followers_1(self):
+ create_automation(self,
+ model_id=self.env["ir.model"]._get("base.automation.lead.thread.test").id,
+ trigger="on_create",
+ _actions={
+ "state": "followers",
+ "followers_type": "generic",
+ "followers_partner_field_name": "user_id.partner_id"
+ }
+ )
+ user = self.env["res.users"].create({"login": "maggot_brain", "name": "Eddie Hazel"})
+ thread_test = self.env["base.automation.lead.thread.test"].create({
+ "name": "free your mind",
+ "user_id": user.id,
+ })
+ self.assertEqual(thread_test.message_follower_ids.partner_id, user.partner_id)
+
+ def test_add_followers_2(self):
+ user = self.env["res.users"].create({"login": "maggot_brain", "name": "Eddie Hazel"})
+ create_automation(self,
+ model_id=self.env["ir.model"]._get("base.automation.lead.thread.test").id,
+ trigger="on_create",
+ _actions={
+ "state": "followers",
+ "followers_type": "specific",
+ "partner_ids": [Command.link(user.partner_id.id)]
+ }
+ )
+ thread_test = self.env["base.automation.lead.thread.test"].create({
+ "name": "free your mind",
+ })
+ self.assertEqual(thread_test.message_follower_ids.partner_id, user.partner_id)
+
+ def test_cannot_have_actions_with_warnings(self):
+ with self.assertRaises(ValidationError) as e:
+ create_automation(
+ self,
+ model_id=self.env['ir.model']._get('ir.actions.server').id,
+ trigger='on_time',
+ _actions={
+ 'name': 'Send Webhook Notification',
+ 'state': 'webhook',
+ 'webhook_field_ids': [self.env['ir.model.fields']._get('ir.actions.server', 'code').id],
+ },
+ )
+ self.assertEqual(e.exception.args[0], "Following child actions have warnings: Send Webhook Notification")
+
@common.tagged('post_install', '-at_install')
class TestCompute(common.TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env.ref('base.user_admin').write({
+ 'email': 'mitchell.admin@example.com',
+ })
+
+ def test_automation_form_view(self):
+ automation_form = Form(self.env['base.automation'], view='base_automation.view_base_automation_form')
+
+ # Initialize some fields
+ automation_form.name = "Test Automation"
+ automation_form.model_id = self.env.ref('test_base_automation.model_test_base_automation_project')
+ automation_form.trigger = 'on_create_or_write'
+ self.assertEqual(automation_form.trigger_field_ids.ids, [])
+ self.assertEqual(automation_form.filter_domain, False)
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ # Changing the model must reset the trigger
+ automation_form.model_id = self.env.ref('test_base_automation.model_base_automation_lead_test')
+ self.assertEqual(automation_form.trigger, False)
+ self.assertEqual(automation_form.trigger_field_ids.ids, [])
+ self.assertEqual(automation_form.filter_domain, False)
+
+ # Some triggers must preset a filter_domain and trigger_field_ids
+ ## State is set to...
+ automation_form.trigger = 'on_state_set'
+ state_field_id = self.env.ref('test_base_automation.field_base_automation_lead_test__state').id
+ self.assertEqual(automation_form.trigger_field_ids.ids, [state_field_id])
+ self.assertEqual(automation_form.filter_domain, False)
+ automation_form.trg_selection_field_id = self.env['ir.model.fields.selection'].search([
+ ('field_id', '=', state_field_id),
+ ('value', '=', 'pending'),
+ ])
+ self.assertEqual(automation_form.trigger_field_ids.ids, [state_field_id])
+ self.assertEqual(automation_form.filter_domain, repr([('state', '=', 'pending')]))
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## Priority is set to...
+ automation_form.model_id = self.env.ref('test_base_automation.model_test_base_automation_project')
+ automation_form.trigger = 'on_priority_set'
+ priority_field_id = self.env.ref('test_base_automation.field_test_base_automation_project__priority').id
+ self.assertEqual(automation_form.trigger_field_ids.ids, [priority_field_id])
+ self.assertEqual(automation_form.filter_domain, False)
+ automation_form.trg_selection_field_id = self.env['ir.model.fields.selection'].search([
+ ('field_id', '=', priority_field_id),
+ ('value', '=', '2'),
+ ])
+ self.assertEqual(automation_form.trigger_field_ids.ids, [priority_field_id])
+ self.assertEqual(automation_form.filter_domain, repr([('priority', '=', '2')]))
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## Stage is set to...
+ automation_form.model_id = self.env.ref('test_base_automation.model_base_automation_lead_test')
+ automation_form.trigger = 'on_stage_set'
+ stage_field_id = self.env.ref('test_base_automation.field_base_automation_lead_test__stage_id').id
+ self.assertEqual(automation_form.trigger_field_ids.ids, [stage_field_id])
+ self.assertEqual(automation_form.filter_domain, False)
+ new_lead_stage = self.env['test_base_automation.stage'].create({'name': 'New'})
+ automation_form.trg_field_ref = new_lead_stage.id
+ self.assertEqual(automation_form.filter_domain, repr([('stage_id', '=', new_lead_stage.id)]))
+ self.assertEqual(automation_form.trigger_field_ids.ids, [stage_field_id])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## User is set
+ automation_form.trigger = 'on_user_set'
+ self.assertEqual(automation_form.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__user_id').id
+ ])
+ self.assertEqual(automation_form.filter_domain, repr([('user_id', '!=', False)]))
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## On archive
+ automation_form.trigger = 'on_archive'
+ self.assertEqual(automation_form.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__active').id
+ ])
+ self.assertEqual(automation_form.filter_domain, repr([('active', '=', False)]))
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## On unarchive
+ automation_form.trigger = 'on_unarchive'
+ self.assertEqual(automation_form.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__active').id
+ ])
+ self.assertEqual(automation_form.filter_domain, repr([('active', '=', True)]))
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## Tag is set to...
+ automation_form.trigger = 'on_tag_set'
+ a_lead_tag = self.env['test_base_automation.tag'].create({'name': '*AWESOME*'})
+ automation_form.trg_field_ref = a_lead_tag.id
+ self.assertEqual(automation_form.filter_domain, repr([('tag_ids', 'in', [a_lead_tag.id])]))
+ self.assertEqual(automation_form.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__tag_ids').id
+ ])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, repr([('tag_ids', 'not in', [a_lead_tag.id])]))
+
+ def test_automation_form_view_on_change_filter_domain(self):
+ a_lead_tag = self.env['test_base_automation.tag'].create({'name': '*AWESOME*'})
+ automation = self.env['base.automation'].create({
+ 'name': 'Test Automation',
+ 'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
+ 'trigger': 'on_tag_set',
+ 'trg_field_ref': a_lead_tag.id,
+ })
+ self.assertEqual(automation.filter_pre_domain, repr([('tag_ids', 'not in', [a_lead_tag.id])]))
+ self.assertEqual(automation.filter_domain, repr([('tag_ids', 'in', [a_lead_tag.id])]))
+ self.assertEqual(automation.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__tag_ids').id
+ ])
+ self.assertEqual(automation.on_change_field_ids.ids, [])
+
+ # Change the trigger to "On save" will erase the domains and the trigger fields
+ automation_form = Form(automation, view='base_automation.view_base_automation_form')
+ automation_form.trigger = 'on_create_or_write'
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+ self.assertEqual(automation.filter_domain, False)
+ self.assertEqual(automation.trigger_field_ids.ids, [])
+ self.assertEqual(automation.on_change_field_ids.ids, [])
+
+ # Change the domain will append each used field to the trigger fields
+ automation_form.filter_domain = repr([('priority', '=', True), ('employee', '=', False)])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+ self.assertEqual(automation.filter_domain, repr([('priority', '=', True), ('employee', '=', False)]))
+ self.assertSetEqual(set(automation.trigger_field_ids.ids), {
+ self.env.ref('test_base_automation.field_base_automation_lead_test__priority').id,
+ self.env.ref('test_base_automation.field_base_automation_lead_test__employee').id,
+ })
+ self.assertEqual(automation.on_change_field_ids.ids, [])
+
+ # Change the trigger fields will not change the domain
+ automation_form.trigger_field_ids.add(
+ self.env.ref('test_base_automation.field_base_automation_lead_test__tag_ids')
+ )
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+ self.assertEqual(automation.filter_domain, repr([('priority', '=', True), ('employee', '=', False)]))
+ self.assertItemsEqual(automation.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__priority').id,
+ self.env.ref('test_base_automation.field_base_automation_lead_test__employee').id,
+ self.env.ref('test_base_automation.field_base_automation_lead_test__tag_ids').id
+ ])
+ self.assertEqual(automation.on_change_field_ids.ids, [])
+
+ # Erase the domain will remove corresponding fields from the trigger fields
+ automation_form.filter_domain = False
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+ self.assertEqual(automation.filter_domain, False)
+ self.assertEqual(automation.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__tag_ids').id
+ ])
+ self.assertEqual(automation.on_change_field_ids.ids, [])
+
+ def test_automation_form_view_time_triggers(self):
+ # Starting from a "On save" automation
+ on_save_automation = self.env['base.automation'].create({
+ 'name': 'Test Automation',
+ 'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
+ 'trigger': 'on_create_or_write',
+ 'filter_domain': repr([('employee', '=', False)]),
+ 'trigger_field_ids': self.env.ref('test_base_automation.field_base_automation_lead_test__employee')
+ })
+
+ automation = on_save_automation.copy()
+ self.assertEqual(automation.filter_pre_domain, False)
+ self.assertEqual(automation.filter_domain, repr([('employee', '=', False)]))
+ self.assertEqual(automation.trg_date_id.id, False)
+ self.assertEqual(automation.trigger_field_ids.ids, [
+ self.env.ref('test_base_automation.field_base_automation_lead_test__employee').id
+ ])
+
+ # Changing to a time trigger must erase domains and trigger fields
+ ## Change the trigger to "On time created"
+ automation_form = Form(automation, view='base_automation.view_base_automation_form')
+ automation_form.trigger = 'on_time_created'
+ self.assertEqual(automation_form.filter_domain, False)
+ self.assertEqual(automation_form.trg_date_id, self.env.ref('test_base_automation.field_base_automation_lead_test__create_date'))
+ self.assertEqual(automation_form.trigger_field_ids.ids, [])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## Change the trigger to "On time updated"
+ automation = on_save_automation.copy()
+ automation_form = Form(automation, view='base_automation.view_base_automation_form')
+ automation_form.trigger = 'on_time_updated'
+ self.assertEqual(automation_form.filter_domain, False)
+ self.assertEqual(automation_form.trg_date_id, self.env.ref('test_base_automation.field_base_automation_lead_test__write_date'))
+ self.assertEqual(automation_form.trigger_field_ids.ids, [])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ ## Change the trigger to "On time"
+ automation = on_save_automation.copy()
+ automation_form = Form(automation, view='base_automation.view_base_automation_form')
+ automation_form.trigger = 'on_time'
+ automation_form.trg_date_id = self.env.ref('test_base_automation.field_base_automation_lead_test__create_date')
+ self.assertEqual(automation_form.filter_domain, False)
+ self.assertEqual(automation_form.trg_date_id, self.env.ref('test_base_automation.field_base_automation_lead_test__create_date'))
+ self.assertEqual(automation_form.trigger_field_ids.ids, [])
+ automation = automation_form.save()
+ self.assertEqual(automation.filter_pre_domain, False)
+
+ def test_automation_form_view_with_default_values_in_context(self):
+ # Use case where default model, trigger and filter_domain in context
+ context = {
+ 'default_name': 'Test Automation',
+ 'default_model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
+ 'default_trigger': 'on_create_or_write',
+ 'default_filter_domain': repr([('state', '=', 'draft')]),
+ }
+ # Create form should be pre-filled with the default values
+ automation = self.env['base.automation'].with_context(context)
+ default_trigger_field_ids = [self.env.ref('test_base_automation.field_base_automation_lead_test__state').id]
+ automation_form = Form(automation, view='base_automation.view_base_automation_form')
+ self.assertEqual(automation_form.name, context.get('default_name'))
+ self.assertEqual(automation_form.model_id.id, context.get('default_model_id'))
+ self.assertEqual(automation_form.trigger, context.get('default_trigger'))
+ self.assertEqual(automation_form.trigger_field_ids.ids, default_trigger_field_ids,
+ 'trigger_field_ids should match the fields in the default filter domain.')
+
+ automation_form.trigger = 'on_stage_set'
+ self.assertNotEqual(automation_form.trigger_field_ids.ids, default_trigger_field_ids,
+ 'When user changes trigger, the trigger_field_ids field should be updated')
+
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.
@@ -366,7 +1362,7 @@ class TestCompute(common.TransactionCase):
??? 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
+ This happens if a rule has a non-empty filter_pre_domain, even if
it's an empty list (``'[]'`` as opposed to ``False``).
"""
company1 = self.env['res.partner'].create({
@@ -386,37 +1382,62 @@ class TestCompute(common.TransactionCase):
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,
- })
+ create_automation(
+ self,
+ model_id=self.env.ref('base.model_res_partner').id,
+ filter_pre_domain=False,
+ trigger='on_create_or_write',
+ _actions={'state': 'code'}, # no-op action
+ )
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,
- })
+ create_automation(
+ self,
+ model_id=self.env.ref('base.model_res_partner').id,
+ filter_pre_domain='[]',
+ trigger='on_create_or_write',
+ _actions={'state': 'code'}, # no-op action
+ )
r.parent_id = company2
self.assertEqual(r.display_name, 'Awiclo, Bob')
+ def test_computation_sequence(self):
+ """ This test ensure sequential computation is done and all fields are correctly set
+ when a filter_pre_domain trigger computation of one of the chain element
+ """
+ project = self.env['test_base_automation.project'].create({})
+ task = self.env['test_base_automation.task'].create({
+ 'project_id': project.id,
+ 'allocated_hours': 100,
+ })
+
+ # this action is executed every time a task is modified
+ create_automation(
+ self,
+ model_id=self.env.ref('test_base_automation.model_test_base_automation_task').id,
+ trigger='on_create_or_write',
+ filter_pre_domain="[('remaining_hours', '>', 0)]",
+ _actions={'state': 'code'}, # no-op action
+ )
+
+ task.trigger_hours = 5
+ self.assertRecordValues(task, [{
+ 'effective_hours': 5,
+ 'remaining_hours': 95,
+ }])
+
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_automation(
+ self,
+ model_id=self.env.ref('test_base_automation.model_test_base_automation_task').id,
+ trigger='on_create_or_write',
+ filter_domain=repr([('project_id', '=', project.id)]),
+ _actions={'state': 'code'}, # no-op action
+ )
# create one task in project with 10 subtasks; all the subtasks are
# automatically assigned to project, too
@@ -425,16 +1446,16 @@ class TestCompute(common.TransactionCase):
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
+ # is marked to compute on many records, and automation rules 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
+ # An issue occurs when the domain of automation rules 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().
+ # amount of recursive calls between the automation rule and flush().
#
# The execution of task.unlink() looks like this:
# - mark 'project_id' to compute on subtasks
@@ -460,3 +1481,418 @@ class TestCompute(common.TransactionCase):
task.unlink()
finally:
sys.setrecursionlimit(limit)
+
+ def test_mail_triggers(self):
+ lead_model = self.env["ir.model"]._get("base.automation.lead.test")
+ with self.assertRaises(ValidationError):
+ create_automation(self, trigger="on_message_sent", model_id=lead_model.id)
+
+ lead_thread_model = self.env["ir.model"]._get("base.automation.lead.thread.test")
+ automation = create_automation(self, trigger="on_message_sent", model_id=lead_thread_model.id, _actions={
+ "state": "object_write",
+ "update_path": "active",
+ "update_boolean_value": "false"
+ })
+
+ ext_partner = self.env["res.partner"].create({"name": "ext", "email": "email@server.com"})
+ internal_partner = self.env["res.users"].browse(2).partner_id
+
+ obj = self.env["base.automation.lead.thread.test"].create({"name": "test"})
+ obj.message_subscribe([ext_partner.id, internal_partner.id])
+
+ obj.message_post(author_id=internal_partner.id, message_type="comment", subtype_xmlid="mail.mt_comment")
+ self.assertFalse(obj.active)
+
+ obj.active = True
+ obj.message_post(author_id=internal_partner.id, subtype_xmlid="mail.mt_comment")
+ self.assertTrue(obj.active)
+
+ obj.message_post(author_id=ext_partner.id, message_type="comment")
+ self.assertTrue(obj.active)
+
+ obj.message_post(author_id=internal_partner.id, message_type="comment")
+ self.assertTrue(obj.active)
+ obj.message_post(author_id=internal_partner.id, subtype_xmlid="mail.mt_comment", message_type="comment")
+ self.assertFalse(obj.active)
+
+ obj.active = True
+ # message doesn't have author_id, so it should be considered as external the automation should't be triggered
+ obj.message_post(author_id=False, email_from="test_abla@test.test", message_type="email", subtype_xmlid="mail.mt_comment")
+ self.assertTrue(obj.active)
+
+ automation.trigger = "on_message_received"
+ obj.active = True
+ obj.message_post(author_id=internal_partner.id, subtype_xmlid="mail.mt_comment", message_type="comment")
+ self.assertTrue(obj.active)
+
+ obj.message_post(author_id=ext_partner.id, message_type="comment")
+ self.assertTrue(obj.active)
+
+ obj.message_post(author_id=ext_partner.id, subtype_xmlid="mail.mt_comment", message_type="comment")
+ self.assertFalse(obj.active)
+
+ obj.active = True
+ obj.message_post(author_id=ext_partner.id, subtype_xmlid="mail.mt_comment")
+ self.assertTrue(obj.active)
+
+ obj.message_post(author_id=False, email_from="test_abla@test.test", message_type="email", subtype_xmlid="mail.mt_comment")
+ self.assertFalse(obj.active)
+
+ def test_multiple_mail_triggers(self):
+ lead_model = self.env["ir.model"]._get("base.automation.lead.test")
+ with self.assertRaises(ValidationError):
+ create_automation(self, trigger="on_message_sent", model_id=lead_model.id)
+
+ lead_thread_model = self.env["ir.model"]._get("base.automation.lead.thread.test")
+
+ create_automation(self, trigger="on_message_sent", model_id=lead_thread_model.id, _actions={
+ "state": "object_write",
+ "update_path": "active",
+ "update_boolean_value": "false"
+ })
+ create_automation(self, trigger="on_message_sent", model_id=lead_thread_model.id, _actions={
+ "state": "object_write",
+ "evaluation_type": "equation",
+ "update_path": "name",
+ "value": "record.name + '!'"
+ })
+
+ ext_partner = self.env["res.partner"].create({"name": "ext", "email": "email@server.com"})
+ internal_partner = self.env["res.users"].browse(2).partner_id
+
+ obj = self.env["base.automation.lead.thread.test"].create({"name": "test"})
+ obj.message_subscribe([ext_partner.id, internal_partner.id])
+
+ obj.message_post(author_id=internal_partner.id, message_type="comment", subtype_xmlid="mail.mt_comment")
+ self.assertFalse(obj.active)
+ self.assertEqual(obj.name, "test!")
+
+ def test_compute_on_create(self):
+ lead_model = self.env['ir.model']._get('base.automation.lead.test')
+ stage_field = self.env['ir.model.fields']._get('base.automation.lead.test', 'stage_id')
+ new_stage = self.env['test_base_automation.stage'].create({'name': 'New'})
+
+ create_automation(
+ self,
+ model_id=lead_model.id,
+ trigger='on_stage_set',
+ trigger_field_ids=[stage_field.id],
+ _actions={
+ 'state': 'object_create',
+ 'crud_model_id': self.env['ir.model']._get('res.partner').id,
+ 'value': "Test Partner Automation",
+ },
+ filter_domain=repr([('stage_id', '=', new_stage.id)]),
+ )
+
+ # Tricky case: the record is created with 'stage_id' being false, and
+ # the field is marked for recomputation. The field is then recomputed
+ # while evaluating 'filter_domain', which causes the execution of the
+ # automation. And as the domain is satisfied, the automation is
+ # processed again, but it must detect that it has just been run!
+ self.env['base.automation.lead.test'].create({
+ 'name': 'Test Lead',
+ })
+
+ # check that the automation has been run once
+ partner_count = self.env['res.partner'].search_count([('name', '=', 'Test Partner Automation')])
+ self.assertEqual(partner_count, 1, "Only one partner should have been created")
+
+ def test_00_form_save_update_related_model_id(self):
+ with Form(self.env['ir.actions.server'], view="base.view_server_action_form") as f:
+ f.name = "Test Action"
+ f.model_id = self.env["ir.model"]._get("res.partner")
+ f.state = "object_write"
+ f.update_path = "user_id"
+ f.evaluation_type = "value"
+ f.resource_ref = "res.users,2"
+
+ res_users_model = self.env["ir.model"]._get("res.users")
+ self.assertEqual(f.update_related_model_id, res_users_model)
+
+ def test_01_form_object_write_o2m_field(self):
+ aks_partner = self.env["res.partner"].create({"name": "A Kind Shepherd"})
+ bs_partner = self.env["res.partner"].create({"name": "Black Sheep"})
+
+ # test the 'object_write' type shows a resource_ref field for o2many
+ f = Form(self.env['ir.actions.server'], view="base.view_server_action_form")
+ f.name = "Adopt The Black Sheep"
+ f.model_id = self.env["ir.model"]._get("res.partner")
+ f.state = "object_write"
+ f.evaluation_type = "value"
+ f.update_path = "child_ids"
+ self.assertEqual(f.update_m2m_operation, "add")
+ self.assertEqual(f.value_field_to_show, "resource_ref")
+ f.resource_ref = f"res.partner,{bs_partner.id}"
+ action = f.save()
+
+ # test the action runs correctly
+ action.with_context(
+ active_model="res.partner",
+ active_id=aks_partner.id,
+ ).run()
+ self.assertEqual(aks_partner.child_ids, bs_partner)
+ self.assertEqual(bs_partner.parent_id, aks_partner)
+
+ # also check with 'remove' operation
+ f.update_m2m_operation = "remove"
+ action = f.save()
+ action.with_context(
+ active_model="res.partner",
+ active_id=aks_partner.id,
+ ).run()
+ self.assertEqual(aks_partner.child_ids.ids, [])
+ self.assertEqual(bs_partner.parent_id.id, False)
+
+ def test_02_form_object_write_with_sequence(self):
+ test_partner = self.env["res.partner"].create({"name": "Test Partner"})
+ test_sequence = self.env["ir.sequence"].create({
+ "name": "Test Sequence",
+ "padding": 4,
+ "prefix": "PARTNER/",
+ "suffix": "/TEST",
+ })
+
+ f = Form(self.env['ir.actions.server'], view="base.view_server_action_form")
+ f.model_id = self.env["ir.model"]._get("res.partner")
+ f.state = "object_write"
+ f.evaluation_type = "sequence"
+ self.assertEqual(f.warning, False)
+ f.update_path = "active"
+ self.assertEqual(f.warning, "A sequence must only be used with character fields.")
+ f.update_path = "ref"
+ self.assertEqual(f.warning, False)
+ f.sequence_id = test_sequence
+
+ action = f.save()
+ self.assertEqual(test_partner.ref, False)
+ action.with_context(
+ active_model="res.partner",
+ active_id=test_partner.id,
+ ).run()
+ self.assertEqual(test_partner.ref, "PARTNER/0001/TEST")
+
+ def test_03_server_action_code_history_wizard(self):
+ self.env.user.tz = 'Europe/Brussels' # UTC +2 for May 2025
+
+ def get_history(action):
+ return self.env["ir.actions.server.history"].search([("action_id", "=", action.id)])
+
+ def assert_history(action, expected):
+ history = get_history(action)
+ self.assertRecordValues(history, expected)
+
+ expected = []
+
+ with freeze_time("2025-05-01 08:00:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ action = self.env["ir.actions.server"].create({
+ "name": "Test Action",
+ "model_id": self.env["ir.model"]._get("res.partner").id,
+ "state": "code",
+ "code": "pass",
+ })
+ expected.insert(0, {
+ "code": "pass",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:00:00 AM - {self.env.ref('base.user_root').name}"),
+ })
+ assert_history(action, expected)
+
+ with freeze_time("2025-05-01 08:30:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ action.with_user(self.env.ref('base.user_admin')).write({"code": "hello"})
+ expected.insert(0, {
+ "code": "hello",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:30:00 AM - {self.env.ref('base.user_admin').name}"),
+ })
+ assert_history(action, expected)
+
+ with freeze_time("2025-05-05 11:30:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ action.with_user(self.env.ref('base.user_admin')).write({"code": "coucou"})
+ expected.insert(0, {
+ "code": "coucou",
+ "display_name": WhitespaceInsensitive(f"May 5, 2025, 1:30:00 PM - {self.env.ref('base.user_admin').name}"),
+ })
+ assert_history(action, expected)
+
+ with freeze_time("2025-05-12 09:30:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ with Form(self.env['server.action.history.wizard'].with_context(default_action_id=action.id)) as wizard_form:
+ self.assertRecordValues(wizard_form.revision, [
+ {
+ "code": "hello",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:30:00 AM - {self.env.ref('base.user_admin').name}"),
+ }
+ ])
+ first_diff = str(wizard_form.code_diff)
+ wizard_form.revision = get_history(action)[-1]
+ second_diff = str(wizard_form.code_diff)
+ self.assertNotEqual(first_diff, second_diff)
+ wizard_form.record.restore_revision()
+
+ self.assertEqual(action.code, "pass")
+ expected.insert(0, {
+ "code": "pass",
+ "display_name": WhitespaceInsensitive(f"May 12, 2025, 11:30:00 AM - {self.env.ref('base.user_root').name}"),
+ })
+ assert_history(action, expected)
+
+ def test_server_action_code_history_wizard_with_no_timezone(self):
+ self.env.user.tz = False
+
+ def get_history(action):
+ return self.env["ir.actions.server.history"].search([("action_id", "=", action.id)])
+
+ def assert_history(action, expected):
+ history = get_history(action)
+ self.assertRecordValues(history, expected)
+
+ expected = []
+
+ with freeze_time("2025-05-01 10:00:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ action = self.env["ir.actions.server"].create({
+ "name": "Test Action",
+ "model_id": self.env["ir.model"]._get("res.partner").id,
+ "state": "code",
+ "code": "pass",
+ })
+ expected.insert(0, {
+ "code": "pass",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:00:00 AM - {self.env.ref('base.user_root').name}"),
+ })
+ assert_history(action, expected)
+
+ with freeze_time("2025-05-01 10:00:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ action.with_user(self.env.ref('base.user_admin')).write({"code": "hello"})
+ expected.insert(0, {
+ "code": "hello",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:00:00 AM - {self.env.ref('base.user_admin').name}"),
+ })
+ assert_history(action, expected)
+
+ with freeze_time("2025-05-12 10:00:00"):
+ self.env.cr._now = datetime.datetime.now() # reset transaction's NOW
+ with Form(self.env['server.action.history.wizard'].with_context(default_action_id=action.id)) as wizard_form:
+ self.assertRecordValues(wizard_form.revision, [
+ {
+ "code": "pass",
+ "display_name": WhitespaceInsensitive(f"May 1, 2025, 10:00:00 AM - {self.env.ref('base.user_root').name}"),
+ }
+ ])
+ first_diff = str(wizard_form.code_diff)
+ wizard_form.revision = get_history(action)[-1]
+ second_diff = str(wizard_form.code_diff)
+ self.assertNotEqual(first_diff, second_diff)
+ wizard_form.record.restore_revision()
+
+ self.assertEqual(action.code, "pass")
+ expected.insert(0, {
+ "code": "pass",
+ "display_name": WhitespaceInsensitive(f"May 12, 2025, 10:00:00 AM - {self.env.ref('base.user_root').name}"),
+ })
+ assert_history(action, expected)
+
+
+@common.tagged("post_install", "-at_install")
+class TestHttp(common.HttpCase):
+ def test_webhook_trigger(self):
+ model = self.env["ir.model"]._get("base.automation.linked.test")
+ record_getter = "model.search([('name', '=', payload['name'])]) if payload.get('name') else None"
+ automation = create_automation(self, trigger="on_webhook", model_id=model.id, record_getter=record_getter, _actions={
+ "state": "object_write",
+ "update_path": "another_field",
+ "value": "written"
+ })
+
+ obj = self.env[model.model].create({"name": "some name"})
+ response = self.url_open(automation.url, data=json.dumps({"name": "some name"}))
+ self.assertEqual(response.json(), {"status": "ok"})
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(obj.another_field, "written")
+
+ obj.another_field = False
+ with mute_logger("odoo.addons.base_automation.models.base_automation"):
+ response = self.url_open(automation.url, data=json.dumps({}))
+ self.assertEqual(response.json(), {"status": "error"})
+ self.assertEqual(response.status_code, 500)
+ self.assertEqual(obj.another_field, False)
+
+ response = self.url_open("/web/hook/0123456789", data=json.dumps({"name": "some name"}))
+ self.assertEqual(response.json(), {"status": "error"})
+ self.assertEqual(response.status_code, 404)
+
+ def test_payload_in_action_server(self):
+ model = self.env["ir.model"]._get("base.automation.linked.test")
+ record_getter = "model.search([('name', '=', payload['name'])]) if payload.get('name') else None"
+ automation = create_automation(self, trigger="on_webhook", model_id=model.id, record_getter=record_getter, _actions={
+ "state": "code",
+ "code": "record.write({'another_field': json.dumps(payload)})"
+ })
+
+ obj = self.env[model.model].create({"name": "some name"})
+ self.url_open(automation.url, data=json.dumps({"name": "some name", "test_key": "test_value"}), headers={"Content-Type": "application/json"})
+ self.assertEqual(json.loads(obj.another_field), {
+ "name": "some name",
+ "test_key": "test_value",
+ })
+
+ obj.another_field = ""
+ self.url_open(automation.url + "?test_param=test_value&name=some%20name")
+ self.assertEqual(json.loads(obj.another_field), {
+ "name": "some name",
+ "test_param": "test_value",
+ })
+
+ def test_webhook_send_and_receive(self):
+ model = self.env["ir.model"]._get("base.automation.linked.test")
+ obj = self.env[model.model].create({"name": "some name"})
+
+ automation_receiver = create_automation(self, trigger="on_webhook", model_id=model.id, _actions={
+ "state": "code",
+ "code": "record.write({'another_field': json.dumps(payload)})"
+ })
+ name_field_id = self.env.ref("test_base_automation.field_base_automation_linked_test__name")
+ automation_sender = create_automation(self, trigger="on_write", model_id=model.id, trigger_field_ids=[(6, 0, [name_field_id.id])], _actions={
+ "name": "Send Webhook Notification",
+ "state": "webhook",
+ "webhook_url": automation_receiver.url,
+ })
+
+ # Changing the name will make an http request, post-commitedly
+ obj.name = "new_name"
+ self.cr.flush()
+ with self.allow_requests(all_requests=True):
+ self.cr.postcommit.run() # webhooks run in postcommit
+ self.cr.clear()
+ self._wait_remaining_requests() # just in case the request timeouts
+ self.assertEqual(json.loads(obj.another_field), {
+ '_action': f'Send Webhook Notification(#{automation_sender.action_server_ids[0].id})',
+ "_id": obj.id,
+ "_model": obj._name,
+ })
+
+ def test_on_change_get_views_cache(self):
+ model_name = "base.automation.lead.test"
+ my_view = self.env["ir.ui.view"].create({
+ "name": "My View",
+ "model": model_name,
+ "type": "form",
+ "arch": "",
+ })
+ self.assertEqual(
+ self.env[model_name].get_view(my_view.id)["arch"],
+ ''
+ )
+ model = self.env["ir.model"]._get(model_name)
+ active_field = self.env["ir.model.fields"]._get(model_name, "active")
+ create_automation(self, trigger="on_change", model_id=model.id, on_change_field_ids=[Command.set([active_field.id])], _actions={
+ "state": "code",
+ "code": "",
+ })
+ self.assertEqual(
+ self.env[model_name].get_view(my_view.id)["arch"],
+ ''
+ )
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_server_actions.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_server_actions.py
new file mode 100644
index 0000000..1372c97
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_server_actions.py
@@ -0,0 +1,77 @@
+# # Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.addons.base.models.ir_actions import ServerActionWithWarningsError
+from odoo.exceptions import ValidationError
+from odoo.addons.base.tests.test_ir_actions import TestServerActionsBase
+
+
+class TestServerActionsValidation(TestServerActionsBase):
+ def test_multi_action_children_warnings(self):
+ self.action.write({
+ 'state': 'multi',
+ 'child_ids': [self.test_server_action.id]
+ })
+ self.assertEqual(self.action.model_id.model, "res.partner")
+ self.assertEqual(self.test_server_action.model_id.model, "ir.actions.server")
+ self.assertEqual(self.action.warning, "Following child actions should have the same model (Contact): TestDummyServerAction")
+
+ new_action = self.action.copy()
+ with self.assertRaises(ValidationError) as ve:
+ new_action.write({
+ 'child_ids': [self.action.id]
+ })
+ self.assertEqual(ve.exception.args[0], "Following child actions have warnings: TestAction")
+
+ def test_webhook_payload_includes_group_restricted_fields(self):
+ self.test_server_action.write({
+ 'state': 'webhook',
+ 'webhook_field_ids': [self.env['ir.model.fields']._get('ir.actions.server', 'code').id],
+ })
+ self.assertEqual(self.test_server_action.warning, "Group-restricted fields cannot be included in "
+ "webhook payloads, as it could allow any user to "
+ "accidentally leak sensitive information. You will "
+ "have to remove the following fields from the webhook payload:\n"
+ "- Python Code")
+
+ def test_recursion_in_child(self):
+ new_action = self.action.copy()
+ self.action.write({
+ 'state': 'multi',
+ 'child_ids': [new_action.id]
+ })
+ with self.assertRaises(ValidationError) as ve:
+ new_action.write({
+ 'child_ids': [self.action.id]
+ })
+ self.assertEqual(ve.exception.args[0], "Recursion found in child server actions")
+
+ def test_non_relational_field_traversal(self):
+ self.action.write({
+ 'state': 'object_write',
+ 'update_path': 'parent_id.name',
+ 'value': 'TestNew',
+ })
+ with self.assertRaises(ValidationError) as ve:
+ self.action.write({'update_path': 'parent_id.name.something_else'})
+ self.assertEqual(ve.exception.args[0], "The path contained by the field "
+ "'Field to Update Path' contains a non-relational field"
+ " (Name) that is not the last field in the path. You "
+ "can't traverse non-relational fields (even in the quantum"
+ " realm). Make sure only the last field in the path is non-relational.")
+
+ def test_python_bad_expr(self):
+ with self.assertRaises(ValidationError) as ve:
+ self.test_server_action.write({'code': 'this is invalid python code'})
+ self.assertEqual(
+ ve.exception.args[0],
+ "SyntaxError : invalid syntax at line 1\n"
+ "this is invalid python code\n")
+
+ def test_cannot_run_if_warnings(self):
+ self.action.write({
+ 'state': 'multi',
+ 'child_ids': [self.test_server_action.id]
+ })
+ self.assertTrue(self.action.warning)
+ with self.assertRaises(ServerActionWithWarningsError) as e:
+ self.action.run()
+ self.assertEqual(e.exception.args[0], "Server action TestAction has one or more warnings, address them first.")
diff --git a/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_tour.py b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_tour.py
new file mode 100644
index 0000000..19e5621
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_base_automation/test_base_automation/tests/test_tour.py
@@ -0,0 +1,304 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from urllib.parse import urlencode
+import ast
+
+from odoo import Command
+
+from odoo.tests import HttpCase, tagged
+
+
+def _urlencode_kwargs(**kwargs):
+ return urlencode(kwargs)
+
+
+@tagged("post_install_l10n", "post_install", "-at_install")
+class BaseAutomationTestUi(HttpCase):
+ def _neutralize_preexisting_automations(self, neutralize_action=True):
+ self.env["base.automation"].with_context(active_test=False).search([]).write({"active": False})
+ if neutralize_action:
+ context = ast.literal_eval(self.env.ref("base_automation.base_automation_act").context)
+ del context["search_default_inactive"]
+ self.env.ref("base_automation.base_automation_act").context = str(context)
+
+ def test_01_base_automation_tour(self):
+ self._neutralize_preexisting_automations()
+ self.start_tour("/odoo/action-base_automation.base_automation_act?debug=tests", "test_base_automation", login="admin")
+ base_automation = self.env["base.automation"].search([])
+ self.assertEqual(base_automation.model_id.model, "res.partner")
+ self.assertEqual(base_automation.trigger, "on_create_or_write")
+ self.assertEqual(base_automation.action_server_ids.state, "object_write") # only one action
+ self.assertEqual(base_automation.action_server_ids.model_name, "res.partner")
+ self.assertEqual(base_automation.action_server_ids.update_field_id.name, "function")
+ self.assertEqual(base_automation.action_server_ids.value, "Test")
+
+ def test_base_automation_on_tag_added(self):
+ self._neutralize_preexisting_automations()
+ self.env["test_base_automation.tag"].create({"name": "test"})
+ self.start_tour("/odoo/action-base_automation.base_automation_act?debug=tests", "test_base_automation_on_tag_added", login="admin")
+
+ def test_open_automation_from_grouped_kanban(self):
+ self._neutralize_preexisting_automations()
+
+ test_view = self.env["ir.ui.view"].create(
+ {
+ "name": "test_view",
+ "model": "test_base_automation.project",
+ "type": "kanban",
+ "arch": """
+
+
+
+
+
+
+
+ """,
+ }
+ )
+ test_action = self.env["ir.actions.act_window"].create(
+ {
+ "name": "test action",
+ "res_model": "test_base_automation.project",
+ "view_ids": [Command.create({"view_id": test_view.id, "view_mode": "kanban"})],
+ }
+ )
+ tag = self.env["test_base_automation.tag"].create({"name": "test tag"})
+ self.env["test_base_automation.project"].create({"name": "test", "tag_ids": [Command.link(tag.id)]})
+
+ self.start_tour(f"/odoo/action-{test_action.id}?debug=0", "test_open_automation_from_grouped_kanban", login="admin")
+ base_auto = self.env["base.automation"].search([])
+ self.assertEqual(base_auto.name, "From Tour")
+ self.assertEqual(base_auto.model_name, "test_base_automation.project")
+ self.assertEqual(base_auto.trigger_field_ids.name, "tag_ids")
+ self.assertEqual(base_auto.trigger, "on_tag_set")
+ self.assertEqual(base_auto.trg_field_ref_model_name, "test_base_automation.tag")
+ self.assertEqual(base_auto.trg_field_ref, tag.id)
+
+ def test_kanban_automation_view_stage_trigger(self):
+ self._neutralize_preexisting_automations()
+
+ project_model = self.env.ref('test_base_automation.model_test_base_automation_project')
+ stage_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', project_model.id),
+ ('name', '=', 'stage_id'),
+ ])
+ test_stage = self.env['test_base_automation.stage'].create({'name': 'Stage value'})
+
+ automation = self.env["base.automation"].create({
+ "name": "Test Stage",
+ "trigger": "on_stage_set",
+ "model_id": project_model.id,
+ "trigger_field_ids": [stage_field.id],
+ "trg_field_ref": test_stage,
+ })
+
+ action = {
+ "name": "Set Active To False",
+ "base_automation_id": automation.id,
+ "state": "object_write",
+ "update_path": "user_ids.active",
+ "value": False,
+ "model_id": project_model.id
+ }
+ automation.write({"action_server_ids": [Command.create(action)]})
+
+ self.start_tour(
+ "/odoo/action-base_automation.base_automation_act",
+ "test_kanban_automation_view_stage_trigger", login="admin"
+ )
+
+ def test_kanban_automation_view_time_trigger(self):
+ self._neutralize_preexisting_automations()
+ model = self.env['ir.model']._get("base.automation.lead.test")
+
+ date_field = self.env['ir.model.fields'].search([
+ ('model_id', '=', model.id),
+ ('name', '=', 'date_automation_last'),
+ ])
+
+ self.env["base.automation"].create({
+ "name": "Test Date",
+ "trigger": "on_time",
+ "model_id": model.id,
+ "trg_date_range": 1,
+ "trg_date_range_type": "hour",
+ "trg_date_id": date_field.id,
+ })
+
+ self.start_tour(
+ "/odoo/action-base_automation.base_automation_act",
+ "test_kanban_automation_view_time_trigger", login="admin"
+ )
+
+ def test_kanban_automation_view_time_updated_trigger(self):
+ self._neutralize_preexisting_automations()
+ model = self.env.ref("base.model_res_partner")
+
+ self.env["base.automation"].create({
+ "name": "Test Date",
+ "trigger": "on_time_updated",
+ "model_id": model.id,
+ "trg_date_range": 1,
+ "trg_date_range_type": "hour",
+ })
+
+ self.start_tour(
+ "/odoo/action-base_automation.base_automation_act",
+ "test_kanban_automation_view_time_updated_trigger", login="admin"
+ )
+
+ def test_kanban_automation_view_create_action(self):
+ self._neutralize_preexisting_automations()
+ model = self.env.ref("base.model_res_partner")
+
+ automation = self.env["base.automation"].create({
+ "name": "Test",
+ "trigger": "on_create_or_write",
+ "model_id": model.id,
+ })
+
+ action = {
+ "name": "Create Contact with name NameX",
+ "base_automation_id": automation.id,
+ "state": "object_create",
+ "value": "NameX",
+ "model_id": model.id
+ }
+
+ automation.write({"action_server_ids": [Command.create(action)]})
+
+ self.start_tour(
+ "/odoo/action-base_automation.base_automation_act",
+ "test_kanban_automation_view_create_action", login="admin"
+ )
+
+ def test_resize_kanban(self):
+ self._neutralize_preexisting_automations()
+ model = self.env.ref("base.model_res_partner")
+
+ automation = self.env["base.automation"].create(
+ {
+ "name": "Test",
+ "trigger": "on_create_or_write",
+ "model_id": model.id,
+ }
+ )
+
+ action = {
+ "name": "Set Active To False",
+ "base_automation_id": automation.id,
+ "state": "object_write",
+ "update_path": "active",
+ "value": False,
+ "model_id": model.id,
+ }
+ automation.write({"action_server_ids": [Command.create(action) for i in range(3)]})
+
+ self.start_tour(
+ "/odoo/action-base_automation.base_automation_act",
+ "test_resize_kanban",
+ login="admin",
+ )
+
+ def test_form_view(self):
+ model = self.env.ref("base.model_res_partner")
+ automation = self.env["base.automation"].create(
+ {
+ "name": "Test",
+ "trigger": "on_create_or_write",
+ "model_id": model.id,
+ }
+ )
+ action = {
+ "name": "Update Active",
+ "base_automation_id": automation.id,
+ "state": "object_write",
+ "update_path": "active",
+ "update_boolean_value": "false",
+ "model_id": model.id,
+ }
+ automation.write(
+ {"action_server_ids": [Command.create(dict(action, name=action["name"] + f" {i}", sequence=i)) for i in range(3)]}
+ )
+ self.assertEqual(
+ automation.action_server_ids.mapped("name"),
+ ["Update Active 0", "Update Active 1", "Update Active 2"],
+ )
+
+ onchange_link_passes = 0
+ origin_link_onchange = type(self.env["ir.actions.server"]).onchange
+
+ def _onchange_base_auto_link(self_model, *args):
+ nonlocal onchange_link_passes
+ onchange_link_passes += 1
+ res = origin_link_onchange(self_model, *args)
+ if onchange_link_passes == 1:
+ default_keys = {k: v for k, v in self_model.env.context.items() if k.startswith("default_")}
+ self.assertEqual(
+ default_keys,
+ {"default_model_id": model.id, "default_usage": "base_automation"},
+ )
+ if onchange_link_passes == 2:
+ self.assertEqual(res["value"]["name"], "Add Followers")
+
+ return res
+
+ self.patch(type(self.env["ir.actions.server"]), "onchange", _onchange_base_auto_link)
+
+ self.start_tour(
+ (
+ f"/odoo/action-base_automation.base_automation_act/{automation.id}?debug=0"
+ ),
+ "test_form_view_resequence_actions",
+ login="admin",
+ )
+ self.assertEqual(onchange_link_passes, 2)
+ self.assertEqual(
+ automation.action_server_ids.mapped("name"),
+ ["Update Active 2", "Update Active 0", "Update Active 1"],
+ )
+
+ def test_form_view_model_id(self):
+ self.start_tour(
+ (
+ "/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
+ ),
+ "test_form_view_model_id",
+ login="admin",
+ )
+
+ def test_form_view_custom_reference_field(self):
+ self.env["test_base_automation.stage"].create({"name": "test stage"})
+ self.env["test_base_automation.tag"].create({"name": "test tag"})
+ self.start_tour(
+ (
+ "/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
+ ),
+ "test_form_view_custom_reference_field",
+ login="admin",
+ )
+
+ def test_form_view_mail_triggers(self):
+ self.start_tour(
+ (
+ "/odoo/action-base_automation.base_automation_act/new?view_type='form'&debug=0)"
+ ),
+ "test_form_view_mail_triggers",
+ login="admin",
+ )
+
+ def test_on_change_rule_creation(self):
+ """ test on_change rule creation from the UI """
+ self.start_tour("/odoo/action-base_automation.base_automation_act", 'base_automation.on_change_rule_creation', login="admin")
+
+ rule = self.env['base.automation'].search([], order="create_date desc", limit=1)[0]
+ view_model = self.env['ir.model']._get("ir.ui.view")
+ active_field = self.env['ir.model.fields'].search([
+ ('name', '=', 'active'),
+ ('model', '=', 'ir.ui.view'),
+ ])[0]
+ self.assertEqual(rule.name, "Test rule")
+ self.assertEqual(rule.model_id, view_model)
+ self.assertEqual(rule.trigger, 'on_change')
+ self.assertEqual(len(rule.on_change_field_ids), 1)
+ self.assertEqual(rule.on_change_field_ids[0], active_field)
diff --git a/odoo-bringout-oca-ocb-test_crm_full/README.md b/odoo-bringout-oca-ocb-test_crm_full/README.md
index bf8f679..0c7bd83 100644
--- a/odoo-bringout-oca-ocb-test_crm_full/README.md
+++ b/odoo-bringout-oca-ocb-test_crm_full/README.md
@@ -12,7 +12,6 @@ pip install odoo-bringout-oca-ocb-test_crm_full
## Dependencies
-This addon depends on:
- crm
- crm_iap_enrich
- crm_iap_mine
@@ -24,34 +23,12 @@ This addon depends on:
- website_crm_partner_assign
- website_crm_livechat
-## Manifest Information
-
-- **Name**: Test Full Crm Flow
-- **Version**: 1.0
-- **Category**: Hidden/Tests
-- **License**: LGPL-3
-- **Installable**: False
-
## Source
-Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_crm_full`.
+- Repository: https://github.com/OCA/OCB
+- Branch: 19.0
+- Path: addons/test_crm_full
## 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
+This package preserves the original LGPL-3 license.
diff --git a/odoo-bringout-oca-ocb-test_crm_full/pyproject.toml b/odoo-bringout-oca-ocb-test_crm_full/pyproject.toml
index 101fe71..07483ce 100644
--- a/odoo-bringout-oca-ocb-test_crm_full/pyproject.toml
+++ b/odoo-bringout-oca-ocb-test_crm_full/pyproject.toml
@@ -1,21 +1,23 @@
[project]
name = "odoo-bringout-oca-ocb-test_crm_full"
version = "16.0.0"
-description = "Test Full Crm Flow - Odoo addon"
+description = "Test Full Crm Flow -
+ Odoo addon
+ "
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
- "odoo-bringout-oca-ocb-crm>=16.0.0",
- "odoo-bringout-oca-ocb-crm_iap_enrich>=16.0.0",
- "odoo-bringout-oca-ocb-crm_iap_mine>=16.0.0",
- "odoo-bringout-oca-ocb-crm_sms>=16.0.0",
- "odoo-bringout-oca-ocb-event_crm>=16.0.0",
- "odoo-bringout-oca-ocb-sale_crm>=16.0.0",
- "odoo-bringout-oca-ocb-website_crm>=16.0.0",
- "odoo-bringout-oca-ocb-website_crm_iap_reveal>=16.0.0",
- "odoo-bringout-oca-ocb-website_crm_partner_assign>=16.0.0",
- "odoo-bringout-oca-ocb-website_crm_livechat>=16.0.0",
+ "odoo-bringout-oca-ocb-crm>=19.0.0",
+ "odoo-bringout-oca-ocb-crm_iap_enrich>=19.0.0",
+ "odoo-bringout-oca-ocb-crm_iap_mine>=19.0.0",
+ "odoo-bringout-oca-ocb-crm_sms>=19.0.0",
+ "odoo-bringout-oca-ocb-event_crm>=19.0.0",
+ "odoo-bringout-oca-ocb-sale_crm>=19.0.0",
+ "odoo-bringout-oca-ocb-website_crm>=19.0.0",
+ "odoo-bringout-oca-ocb-website_crm_iap_reveal>=19.0.0",
+ "odoo-bringout-oca-ocb-website_crm_partner_assign>=19.0.0",
+ "odoo-bringout-oca-ocb-website_crm_livechat>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@@ -25,7 +27,7 @@ classifiers = [
"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.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
diff --git a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/__manifest__.py b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/__manifest__.py
index 86302d6..8f2caec 100644
--- a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/__manifest__.py
+++ b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/__manifest__.py
@@ -20,5 +20,6 @@ backend. It notably includes IAP bridges modules to test their impact. """,
'website_crm_partner_assign',
'website_crm_livechat',
],
+ 'author': 'Odoo S.A.',
'license': 'LGPL-3',
}
diff --git a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/common.py b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/common.py
index bce1f74..101be0c 100644
--- a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/common.py
+++ b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/common.py
@@ -13,7 +13,6 @@ class TestCrmFullCommon(TestCrmCommon, MockIAPReveal, MockVisitor):
@classmethod
def setUpClass(cls):
super(TestCrmFullCommon, cls).setUpClass()
- cls._init_mail_gateway()
cls._activate_multi_company()
# Context data: dates
@@ -34,7 +33,6 @@ class TestCrmFullCommon(TestCrmCommon, MockIAPReveal, MockVisitor):
'email': 'partner.email.%02d@test.example.com' % idx,
'function': 'Noisy Customer',
'lang': 'fr_BE',
- 'mobile': '04569999%02d' % idx,
'name': 'PartnerCustomer',
'phone': '04560000%02d' % idx,
'street': 'Super Street, %092d' % idx,
diff --git a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/test_performance.py b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/test_performance.py
index 571f5e4..551a4a1 100644
--- a/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/test_performance.py
+++ b/odoo-bringout-oca-ocb-test_crm_full/test_crm_full/tests/test_performance.py
@@ -4,8 +4,7 @@
from freezegun import freeze_time
from odoo.addons.test_crm_full.tests.common import TestCrmFullCommon
-from odoo.tests.common import users, warmup, Form
-from odoo.tests import tagged
+from odoo.tests import Form, users, warmup, tagged
@tagged('crm_performance', 'post_install', '-at_install', '-standard')
@@ -15,10 +14,13 @@ class CrmPerformanceCase(TestCrmFullCommon):
super(CrmPerformanceCase, self).setUp()
# patch registry to simulate a ready environment
self.patch(self.env.registry, 'ready', True)
+ # we don't use mock_mail_gateway thus want to mock smtp to test the stack
+ self._mock_smtplib_connection()
+
self._flush_tracking()
self.user_sales_leads.write({
- 'groups_id': [
+ 'group_ids': [
(4, self.env.ref('event.group_event_user').id),
(4, self.env.ref('im_livechat.im_livechat_group_user').id),
]
@@ -40,16 +42,15 @@ class TestCrmPerformance(CrmPerformanceCase):
""" Test multiple lead creation (import) """
batch_size = 10
country_be = self.env.ref('base.be')
- lang_be_id = self.env['res.lang']._lang_get_id('fr_BE')
+ lang_be_id = self.env['res.lang']._get_data(code='fr_BE').id
- with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=194): # tcf 193 / com 194
+ with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=192): # tcf 191
self.env.cr._now = self.reference_now # force create_date to check schedulers
crm_values = [
{'country_id': country_be.id,
'email_from': 'address.email.%02d@test.example.com' % idx,
'function': 'Noisy Customer',
'lang_id': lang_be_id,
- 'mobile': '04551111%02d' % idx,
'name': 'Test Lead %02d' % idx,
'phone': '04550000%02d' % idx,
'street': 'Super Street, %092d' % idx,
@@ -70,14 +71,13 @@ class TestCrmPerformance(CrmPerformanceCase):
country_be = self.env.ref('base.be')
lang_be = self.env['res.lang']._lang_get('fr_BE')
- with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=189): # tcf only: 173 - com runbot: 174/175
+ with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=145): # tcf 142 / com 144
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['crm.lead']) as lead_form:
lead_form.country_id = country_be
lead_form.email_from = 'address.email@test.example.com'
lead_form.function = 'Noisy Customer'
lead_form.lang_id = lang_be
- lead_form.mobile = '0455111100'
lead_form.name = 'Test Lead'
lead_form.phone = '0455000011'
lead_form.street = 'Super Street, 00'
@@ -89,7 +89,7 @@ class TestCrmPerformance(CrmPerformanceCase):
@warmup
def test_lead_create_form_partner(self):
""" Test a single lead creation using Form with a partner """
- with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=199): # tcf 186 / com 188
+ with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=144): # tcf 141 / com 143
self.env.cr._now = self.reference_now # force create_date to check schedulers
with self.debug_mode():
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
@@ -105,16 +105,15 @@ class TestCrmPerformance(CrmPerformanceCase):
def test_lead_create_single_address(self):
""" Test multiple lead creation (import) """
country_be = self.env.ref('base.be')
- lang_be_id = self.env['res.lang']._lang_get_id('fr_BE')
+ lang_be_id = self.env['res.lang']._get_data(code='fr_BE').id
- with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=43): # tcf only: 41 - com runbot: 42
+ with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=30): # tcf 29
self.env.cr._now = self.reference_now # force create_date to check schedulers
crm_values = [
{'country_id': country_be.id,
'email_from': 'address.email.00@test.example.com',
'function': 'Noisy Customer',
'lang_id': lang_be_id,
- 'mobile': '0455111100',
'name': 'Test Lead',
'phone': '0455000000',
'street': 'Super Street, 00',
@@ -127,7 +126,7 @@ class TestCrmPerformance(CrmPerformanceCase):
@warmup
def test_lead_create_single_partner(self):
""" Test multiple lead creation (import) """
- with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=49): # tcf only: 47 - com runbot: 48
+ with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=30): # tcf 29
self.env.cr._now = self.reference_now # force create_date to check schedulers
crm_values = [
{'partner_id': self.partners[0].id,
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/README.md b/odoo-bringout-oca-ocb-test_discuss_full/README.md
index 032e3a1..5c8746a 100644
--- a/odoo-bringout-oca-ocb-test_discuss_full/README.md
+++ b/odoo-bringout-oca-ocb-test_discuss_full/README.md
@@ -10,45 +10,27 @@ pip install odoo-bringout-oca-ocb-test_discuss_full
## Dependencies
-This addon depends on:
- calendar
- crm
- crm_livechat
+- hr_attendance
+- hr_fleet
- hr_holidays
+- hr_homeworking
- im_livechat
- mail
- mail_bot
-- note
+- project_todo
- website_livechat
-
-## Manifest Information
-
-- **Name**: Test Discuss (full)
-- **Version**: 1.0
-- **Category**: Hidden
-- **License**: LGPL-3
-- **Installable**: True
+- website_sale
+- website_slides
## Source
-Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_discuss_full`.
+- Repository: https://github.com/OCA/OCB
+- Branch: 19.0
+- Path: addons/test_discuss_full
## 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
+This package preserves the original LGPL-3 license.
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/pyproject.toml b/odoo-bringout-oca-ocb-test_discuss_full/pyproject.toml
index 0467327..3ea8c23 100644
--- a/odoo-bringout-oca-ocb-test_discuss_full/pyproject.toml
+++ b/odoo-bringout-oca-ocb-test_discuss_full/pyproject.toml
@@ -1,20 +1,27 @@
[project]
name = "odoo-bringout-oca-ocb-test_discuss_full"
version = "16.0.0"
-description = "Test Discuss (full) - Test of Discuss with all possible overrides installed."
+description = "Test Discuss (full) -
+ Test of Discuss with all possible overrides installed.
+ "
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
- "odoo-bringout-oca-ocb-calendar>=16.0.0",
- "odoo-bringout-oca-ocb-crm>=16.0.0",
- "odoo-bringout-oca-ocb-crm_livechat>=16.0.0",
- "odoo-bringout-oca-ocb-hr_holidays>=16.0.0",
- "odoo-bringout-oca-ocb-im_livechat>=16.0.0",
- "odoo-bringout-oca-ocb-mail>=16.0.0",
- "odoo-bringout-oca-ocb-mail_bot>=16.0.0",
- "odoo-bringout-oca-ocb-note>=16.0.0",
- "odoo-bringout-oca-ocb-website_livechat>=16.0.0",
+ "odoo-bringout-oca-ocb-calendar>=19.0.0",
+ "odoo-bringout-oca-ocb-crm>=19.0.0",
+ "odoo-bringout-oca-ocb-crm_livechat>=19.0.0",
+ "odoo-bringout-oca-ocb-hr_attendance>=19.0.0",
+ "odoo-bringout-oca-ocb-hr_fleet>=19.0.0",
+ "odoo-bringout-oca-ocb-hr_holidays>=19.0.0",
+ "TODO_MAP-hr_homeworking>=19.0.0",
+ "odoo-bringout-oca-ocb-im_livechat>=19.0.0",
+ "odoo-bringout-oca-ocb-mail>=19.0.0",
+ "odoo-bringout-oca-ocb-mail_bot>=19.0.0",
+ "TODO_MAP-project_todo>=19.0.0",
+ "odoo-bringout-oca-ocb-website_livechat>=19.0.0",
+ "odoo-bringout-oca-ocb-website_sale>=19.0.0",
+ "odoo-bringout-oca-ocb-website_slides>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@@ -24,7 +31,7 @@ classifiers = [
"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.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/__manifest__.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/__manifest__.py
index 7e9acd9..bd6bffe 100644
--- a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/__manifest__.py
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/__manifest__.py
@@ -2,23 +2,34 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
- 'name': 'Test Discuss (full)',
- 'version': '1.0',
- 'category': 'Hidden',
- 'sequence': 9877,
- 'summary': 'Test of Discuss with all possible overrides installed.',
- 'description': """Test of Discuss with all possible overrides installed, including feature and performance tests.""",
- 'depends': [
- 'calendar',
- 'crm',
- 'crm_livechat',
- 'hr_holidays',
- 'im_livechat',
- 'mail',
- 'mail_bot',
- 'note',
- 'website_livechat',
+ "name": "Test Discuss (full)",
+ "version": "1.0",
+ "category": "Productivity/Discuss",
+ "sequence": 9877,
+ "summary": "Test of Discuss with all possible overrides installed.",
+ "description": """Test of Discuss with all possible overrides installed, including feature and performance tests.""",
+ "depends": [
+ "calendar",
+ "crm",
+ "crm_livechat",
+ "hr_attendance",
+ "hr_fleet",
+ "hr_holidays",
+ "hr_homeworking",
+ "im_livechat",
+ "mail",
+ "mail_bot",
+ "project_todo",
+ "website_livechat",
+ "website_sale",
+ "website_slides",
],
- 'installable': True,
- 'license': 'LGPL-3',
+ "installable": True,
+ "assets": {
+ "web.assets_tests": [
+ "test_discuss_full/static/tests/tours/**/*",
+ ],
+ },
+ "author": "Odoo S.A.",
+ "license": "LGPL-3",
}
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/avatar_card_tour.js b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/avatar_card_tour.js
new file mode 100644
index 0000000..c5c8073
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/avatar_card_tour.js
@@ -0,0 +1,60 @@
+import { registry } from "@web/core/registry";
+
+const steps = [
+ {
+ content: "Open the avatar card popover",
+ trigger: ".o-mail-Message-avatar",
+ run: "click",
+ },
+ {
+ content: "Check that the employee's work email is displayed",
+ trigger: ".o_avatar_card:contains(test_employee@test.com)",
+ },
+ {
+ content: "Check that the employee's department is displayed",
+ trigger: ".o_avatar_card:contains(Test Department)",
+ },
+ {
+ content: "Check that the employee's work phone is displayed",
+ trigger: ".o_avatar_card:contains(123456789)",
+ },
+ {
+ content: "Check that the employee's holiday status is displayed",
+ trigger: ".o_avatar_card:contains(Back on)",
+ },
+];
+
+registry.category("web_tour.tours").add("avatar_card_tour", {
+ steps: () => [
+ ...steps,
+ {
+ content: "Check that the employee's job title is displayed",
+ trigger: ".o_avatar_card:contains(Test Job Title)",
+ },
+ {
+ trigger: ".o-mail-ActivityMenu-counter:text('2')",
+ },
+ {
+ trigger: ".o_switch_company_menu button",
+ run: "click",
+ },
+ {
+ trigger: `[role=button][title='Switch to Company 2']`,
+ run: "click",
+ expectUnloadPage: true,
+ },
+ {
+ trigger: ".o-mail-ActivityMenu-counter:text('1')",
+ },
+ ],
+});
+
+registry.category("web_tour.tours").add("avatar_card_tour_no_hr_access", {
+ steps: () => [
+ ...steps,
+ {
+ content: "Check that the employee's job title is displayed",
+ trigger: ":not(.o_avatar_card:contains(Test Job Title))",
+ },
+ ],
+});
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/chatbot_redirect_to_portal_tour.js b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/chatbot_redirect_to_portal_tour.js
new file mode 100644
index 0000000..4cbca7e
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/chatbot_redirect_to_portal_tour.js
@@ -0,0 +1,28 @@
+import { registry } from "@web/core/registry";
+
+registry.category("web_tour.tours").add("chatbot_redirect_to_portal", {
+ url: "/contactus",
+ steps: () => [
+ {
+ trigger: ".o-livechat-root:shadow .o-livechat-LivechatButton",
+ run: "click",
+ },
+ {
+ trigger:
+ ".o-livechat-root:shadow .o-mail-Message:contains(Hello, were do you want to go?)",
+ run: "click",
+ },
+ {
+ trigger: ".o-livechat-root:shadow li button:contains(Go to the portal page)",
+ run: "click",
+ expectUnloadPage: true,
+ },
+ {
+ trigger: ".o-livechat-root:shadow .o-mail-Message:contains('Go to the portal page')",
+ },
+ { trigger: "#chatterRoot:shadow .o-mail-Chatter" },
+ {
+ trigger: ".o-livechat-root:shadow .o-mail-Message:last:contains('Tadam')",
+ },
+ ],
+});
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/im_livechat_session_open_tour.js b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/im_livechat_session_open_tour.js
new file mode 100644
index 0000000..617e938
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/static/tests/tours/im_livechat_session_open_tour.js
@@ -0,0 +1,17 @@
+import { registry } from "@web/core/registry";
+
+registry.category("web_tour.tours").add("im_livechat_session_open", {
+ steps: () => [
+ {
+ trigger: "button.o_switch_view.o_list",
+ run: "click",
+ },
+ {
+ trigger: ".o_data_cell:contains(Visitor)",
+ run: "click",
+ },
+ {
+ trigger: ".o-mail-Thread:contains('The conversation is empty.')",
+ },
+ ],
+});
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/__init__.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/__init__.py
index 72c0b71..56caed8 100644
--- a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/__init__.py
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/__init__.py
@@ -1,4 +1,9 @@
-# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from . import test_avatar_card_tour
+from . import test_livechat_hr_holidays
+from . import test_im_livechat_portal
from . import test_performance
+from . import test_performance_inbox
+from . import test_livechat_session_open
+from . import test_res_partner
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_avatar_card_tour.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_avatar_card_tour.py
new file mode 100644
index 0000000..374ba8b
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_avatar_card_tour.py
@@ -0,0 +1,138 @@
+from datetime import date, timedelta
+
+from odoo import Command
+from odoo.tests import tagged, users
+from odoo.tests.common import HttpCase, new_test_user
+from odoo.addons.mail.tests.common import MailCommon
+
+
+@tagged("post_install", "-at_install")
+class TestAvatarCardTour(MailCommon, HttpCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ new_test_user(
+ cls.env,
+ login="hr_user",
+ company_ids=[Command.link(cls.env.company.id), Command.link(cls.company_2.id)],
+ groups="hr.group_hr_user",
+ )
+
+ # hr setup for multi-company
+ department = (
+ cls.env["hr.department"]
+ .with_company(cls.company_2)
+ .create({"name": "Test Department", "company_id": cls.company_2.id})
+ )
+ job = (
+ cls.env["hr.job"]
+ .with_company(cls.company_2)
+ .create({"name": "Test Job Title", "company_id": cls.company_2.id})
+ )
+ other_partner = (
+ cls.env["res.partner"]
+ .with_company(cls.company_2)
+ .create({
+ "name": "Test Other Partner",
+ "company_id": cls.company_2.id,
+ "phone": "987654321",
+ })
+ )
+ test_employee = (
+ cls.env["hr.employee"]
+ .with_company(cls.company_2)
+ .create({
+ "name": "Test Employee",
+ "user_id": cls.user_employee_c2.id,
+ "company_id": cls.company_2.id,
+ "department_id": department.id,
+ "job_id": job.id,
+ "address_id": other_partner.id,
+ "work_email": "test_employee@test.com",
+ "work_phone": "123456789",
+ })
+ )
+ cls.test_employee = test_employee
+ cls.user_employee_c2.write({"employee_ids": [Command.link(test_employee.id)]})
+ new_test_user(
+ cls.env,
+ login="base_user",
+ company_ids=[Command.link(cls.env.company.id), Command.link(cls.company_2.id)],
+ )
+
+ # hr_holidays setup for multi-company
+ leave_type = (
+ cls.env["hr.leave.type"]
+ .with_company(cls.company_2)
+ .create(
+ {
+ "name": "Time Off multi company",
+ "company_id": cls.company_2.id,
+ "time_type": "leave",
+ "requires_allocation": False,
+ }
+ )
+ )
+ cls.env["hr.leave"].with_company(cls.company_2).with_context(
+ leave_skip_state_check=True
+ ).create(
+ {
+ "name": "Test Leave",
+ "company_id": cls.company_2.id,
+ "holiday_status_id": leave_type.id,
+ "employee_id": cls.test_employee.id,
+ "request_date_from": (date.today() - timedelta(days=1)),
+ "request_date_to": (date.today() + timedelta(days=1)),
+ "state": "validate",
+ }
+ )
+ cls.test_record = cls.env["hr.department"].create(
+ [
+ {"name": "Test", "company_id": cls.env.company.id},
+ {"name": "Test 2", "company_id": cls.env.company.id},
+ {"name": "Test 3", "company_id": cls.company_2.id},
+ ]
+ )
+
+ def _setup_channel(self, user):
+ self.user_employee_c2.partner_id.sudo().with_user(self.user_employee_c2).message_post(
+ body="Test message in chatter",
+ message_type="comment",
+ subtype_xmlid="mail.mt_comment",
+ )
+ activity_type_todo = "mail.mail_activity_data_todo"
+ self.test_record[0].activity_schedule(
+ activity_type_todo,
+ summary="Test Activity for Company 2",
+ user_id=user.id,
+ )
+ self.test_record[1].activity_schedule(
+ activity_type_todo,
+ summary="Another Test Activity for Company 2",
+ user_id=user.id,
+ )
+ self.test_record[2].activity_schedule(
+ activity_type_todo,
+ summary="Test Activity for Company 3",
+ user_id=user.id,
+ )
+
+ @users("admin", "hr_user")
+ def test_avatar_card_tour_multi_company(self):
+ # Clear existing activities to avoid interference with the test
+ self.env["mail.activity"].with_user(self.env.user).search([]).unlink()
+ self._setup_channel(self.env.user)
+ self.start_tour(
+ f"/odoo/res.partner/{self.user_employee_c2.partner_id.id}",
+ "avatar_card_tour",
+ login=self.env.user.login,
+ )
+
+ @users("base_user")
+ def test_avatar_card_tour_multi_company_no_hr_access(self):
+ self._setup_channel(self.env.user)
+ self.start_tour(
+ f"/odoo/res.partner/{self.user_employee_c2.partner_id.id}",
+ "avatar_card_tour_no_hr_access",
+ login=self.env.user.login,
+ )
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_im_livechat_portal.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_im_livechat_portal.py
new file mode 100644
index 0000000..a9c2102
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_im_livechat_portal.py
@@ -0,0 +1,52 @@
+from odoo import Command, tests
+from odoo.addons.website_livechat.tests.test_chatbot_ui import TestLivechatChatbotUI
+
+
+@tests.common.tagged("post_install", "-at_install")
+class TestImLivechatPortal(TestLivechatChatbotUI):
+ def test_chatbot_redirect_to_portal(self):
+ project = self.env["project.project"].create({"name": "Portal Project"})
+ task = self.env["project.task"].create(
+ {"name": "Test Task Name Match", "project_id": project.id}
+ )
+ chatbot_redirect_script = self.env["chatbot.script"].create({"title": "Redirection Bot"})
+ question_step = self.env["chatbot.script.step"].create(
+ [
+ {
+ "chatbot_script_id": chatbot_redirect_script.id,
+ "message": "Hello, were do you want to go?",
+ "step_type": "question_selection",
+ },
+ {
+ "chatbot_script_id": chatbot_redirect_script.id,
+ "message": "Tadam, we are on the page you asked for!",
+ "step_type": "text",
+ },
+ ]
+ )[0]
+ self.env["chatbot.script.answer"].create(
+ [
+ {
+ "name": "Go to the portal page",
+ "redirect_link": f"/my/tasks/{task.id}?access_token={task.access_token}",
+ "script_step_id": question_step.id,
+ },
+ ]
+ )
+ livechat_channel = self.env["im_livechat.channel"].create(
+ {
+ "name": "Redirection Channel",
+ "rule_ids": [
+ Command.create(
+ {
+ "regex_url": "/",
+ "chatbot_script_id": chatbot_redirect_script.id,
+ }
+ )
+ ],
+ }
+ )
+ default_website = self.env.ref("website.default_website")
+ default_website.channel_id = livechat_channel.id
+ self.env.ref("website.default_website").channel_id = livechat_channel.id
+ self.start_tour("/contactus", "chatbot_redirect_to_portal")
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_hr_holidays.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_hr_holidays.py
new file mode 100644
index 0000000..368b0e4
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_hr_holidays.py
@@ -0,0 +1,52 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from dateutil.relativedelta import relativedelta
+
+from odoo import Command, fields
+from odoo.tests.common import HttpCase, tagged
+from odoo.addons.mail.tests.common import MailCommon
+
+
+@tagged("post_install", "-at_install")
+class TestLivechatHrHolidays(HttpCase, MailCommon):
+ """Tests for bridge between im_livechat and hr_holidays modules."""
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.env["mail.presence"]._update_presence(cls.user_employee)
+ leave_type = cls.env["hr.leave.type"].create(
+ {"name": "Legal Leaves", "requires_allocation": False, "time_type": "leave"}
+ )
+ employee = cls.env["hr.employee"].create({"user_id": cls.user_employee.id})
+ cls.env["hr.leave"].with_context(leave_skip_state_check=True).create(
+ {
+ "employee_id": employee.id,
+ "holiday_status_id": leave_type.id,
+ "request_date_from": fields.Datetime.today() + relativedelta(days=-2),
+ "request_date_to": fields.Datetime.today() + relativedelta(days=2),
+ "state": "validate",
+ }
+ )
+
+ def test_operator_available_on_leave(self):
+ """Test operator is available on leave when they are online."""
+ livechat_channel = self.env["im_livechat.channel"].create(
+ {"name": "support", "user_ids": [Command.link(self.user_employee.id)]}
+ )
+ self.assertEqual(self.user_employee.im_status, "leave_online")
+ self.assertEqual(livechat_channel.available_operator_ids, self.user_employee)
+
+ def test_operator_limit_on_leave(self):
+ """Test livechat limit is correctly applied when operator is on leave and online."""
+ livechat_channel = self.env["im_livechat.channel"].create(
+ {
+ "max_sessions_mode": "limited",
+ "max_sessions": 1,
+ "name": "support",
+ "user_ids": [Command.link(self.user_employee.id)],
+ }
+ )
+ self.make_jsonrpc_request("/im_livechat/get_session", {"channel_id": livechat_channel.id})
+ self.assertEqual(self.user_employee.im_status, "leave_online")
+ self.assertFalse(livechat_channel.available_operator_ids)
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_session_open.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_session_open.py
new file mode 100644
index 0000000..3370c0d
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_livechat_session_open.py
@@ -0,0 +1,21 @@
+import odoo
+from odoo.addons.im_livechat.tests.common import TestImLivechatCommon
+from odoo.tests import new_test_user
+
+
+@odoo.tests.tagged("-at_install", "post_install")
+class TestImLivechatSessions(TestImLivechatCommon):
+ def test_livechat_session_open(self):
+ new_test_user(
+ self.env,
+ login="operator",
+ groups="base.group_user,im_livechat.im_livechat_group_manager",
+ )
+ self.make_jsonrpc_request(
+ "/im_livechat/get_session", {"channel_id": self.livechat_channel.id}
+ )
+ action = self.env.ref("im_livechat.discuss_channel_action_from_livechat_channel")
+ self.start_tour(
+ f"/odoo/livechat/{self.livechat_channel.id}/action-{action.id}", "im_livechat_session_open",
+ login="operator"
+ )
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance.py
index bcfc7a2..4dc1cb0 100644
--- a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance.py
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance.py
@@ -1,35 +1,179 @@
-# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import date
from dateutil.relativedelta import relativedelta
+from freezegun import freeze_time
+from unittest.mock import patch, PropertyMock
-from odoo import Command
-from odoo.tests.common import users, tagged, TransactionCase, warmup
-from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
+from odoo import Command, fields
+from odoo.fields import Domain
+from odoo.addons.mail.tests.common import MailCommon
+from odoo.addons.mail.tools.discuss import Store
+from odoo.tests.common import users, tagged, HttpCase, warmup
-@tagged('post_install', '-at_install')
-class TestDiscussFullPerformance(TransactionCase):
+@tagged('post_install', '-at_install', 'is_query_count')
+class TestDiscussFullPerformance(HttpCase, MailCommon):
+ # Queries for _query_count_init_store (in order):
+ # 1: search res_partner (odooot ref exists)
+ # 1: search res_groups (internalUserGroupId ref exists)
+ # 8: odoobot format:
+ # - fetch res_partner (_read_format)
+ # - search res_users (_compute_im_status)
+ # - search presence (_compute_im_status)
+ # - fetch presence (_compute_im_status)
+ # - _get_on_leave_ids (_compute_im_status hr_holidays override)
+ # - search employee (_compute_im_status hr_homeworking override)
+ # - fetch employee (_compute_im_status hr_homeworking override)
+ # - fetch res_users (_read_format)
+ # - fetch hr_employee (res.users _to_store)
+ # 5: settings:
+ # - search res_users_settings (_find_or_create_for_user)
+ # - fetch res_users_settings (_format_settings)
+ # - search res_users_settings_volumes (_format_settings)
+ # - search res_users_settings_embedded_action (_format_settings)
+ # - search res_lang_res_users_settings_rel (_format_settings)
+ # - search im_livechat_expertise_res_users_settings_rel (_format_settings)
+ # 2: hasCannedResponses
+ # - fetch res_groups_users_rel
+ # - search mail_canned_response
+ _query_count_init_store = 19
+ # Queries for _query_count_init_messaging (in order):
+ # 1: insert res_device_log
+ # 3: _search_is_member (for current user, first occurence _search_is_member for chathub given channel ids)
+ # - fetch res_users
+ # - search discuss_channel_member
+ # - fetch discuss_channel
+ # 1. search discuss_channel (chathub given channel ids)
+ # 2: _get_channels_as_member
+ # - search discuss_channel (member_domain)
+ # - search discuss_channel (pinned_member_domain)
+ # 2: _init_messaging (discuss)
+ # - fetch discuss_channel_member (is_self)
+ # - _compute_message_unread
+ # 3: _init_messaging (mail)
+ # - search bus_bus (_bus_last_id)
+ # - _get_needaction_count (inbox counter)
+ # - search mail_message (starred counter)
+ # 23: _process_request_for_all (discuss):
+ # - search discuss_channel (channels_domain)
+ # 22: channel add:
+ # - read group member (prefetch _compute_self_member_id from _compute_is_member)
+ # - read group member (_compute_invited_member_ids)
+ # - search discuss_channel_rtc_session
+ # - fetch discuss_channel_rtc_session
+ # - search member (channel_member_ids)
+ # - fetch discuss_channel_member (manual prefetch)
+ # 10: member _to_store:
+ # 10: partner _to_store:
+ # - fetch res_partner (partner _to_store)
+ # - fetch res_users (_compute_im_status)
+ # - search mail_presence (_compute_im_status)
+ # - fetch mail_presence (_compute_im_status)
+ # - _get_on_leave_ids (_compute_im_status override)
+ # - search hr_employee (_compute_im_status override)
+ # - fetch hr_employee (_compute_im_status override)
+ # - search hr_employee (res.users._to_store override)
+ # - search hr_leave (leave_date_to)
+ # - fetch res_users (_compute_main_user_id)
+ # - search bus_bus (_bus_last_id)
+ # - search ir_attachment (_compute_avatar_128)
+ # - count discuss_channel_member (member_count)
+ # - _compute_message_needaction
+ # - search discuss_channel_res_groups_rel (group_ids)
+ # - fetch res_groups (group_public_id)
+ _query_count_init_messaging = 35
+ # Queries for _query_count_discuss_channels (in order):
+ # 1: insert res_device_log
+ # 3: _search_is_member (for current user, first occurence _get_channels_as_member)
+ # - fetch res_users
+ # - search discuss_channel_member
+ # - fetch discuss_channel
+ # 2: _get_channels_as_member
+ # - search discuss_channel (member_domain)
+ # - search discuss_channel (pinned_member_domain)
+ # 36: channel _to_store_defaults:
+ # - read group member (prefetch _compute_self_member_id from _compute_is_member)
+ # - read group member (_compute_invited_member_ids)
+ # - search discuss_channel_rtc_session
+ # - fetch discuss_channel_rtc_session
+ # - search member (channel_member_ids)
+ # - search member (channel_name_member_ids)
+ # - fetch discuss_channel_member (manual prefetch)
+ # 18: member _to_store:
+ # - search im_livechat_channel_member_history (livechat member type)
+ # - fetch im_livechat_channel_member_history (livechat member type)
+ # 13: partner _to_store:
+ # - fetch res_partner (partner _to_store)
+ # - fetch res_users (_compute_im_status)
+ # - search mail_presence (_compute_im_status)
+ # - fetch mail_presence (_compute_im_status)
+ # - _get_on_leave_ids (_compute_im_status override)
+ # - search hr_employee (_compute_im_status override)
+ # - fetch hr_employee (_compute_im_status override)
+ # - search hr_employee (res.users._to_store override)
+ # - search hr_leave (leave_date_to)
+ # - search res_users_settings (livechat username)
+ # - fetch res_users_settings (livechat username)
+ # - fetch res_users (_compute_main_user_id)
+ # - fetch res_country (livechat override)
+ # 3: guest _to_store:
+ # - fetch mail_guest
+ # - fetch mail_presence (_compute_im_status)
+ # - fetch res_country
+ # - search bus_bus (_bus_last_id from _to_store_defaults)
+ # - search ir_attachment (_compute_avatar_128)
+ # - count discuss_channel_member (member_count)
+ # - _compute_message_needaction
+ # - search discuss_channel_res_groups_rel (group_ids)
+ # - fetch im_livechat_channel_member_history (requested_by_operator)
+ # - fetch res_groups (group_ids)
+ # - _compute_message_unread
+ # - fetch im_livechat_channel
+ # 2: fetch livechat_expertise_ids
+ # - fetch livechat_conversation_tag_ids
+ # - read livechat_conversation_tag_ids
+ # 1: _get_last_messages
+ # 20: message _to_store:
+ # - search mail_message_schedule
+ # - fetch mail_message
+ # - search mail_message_res_partner_starred_rel
+ # - search message_attachment_rel
+ # - search mail_link_preview
+ # - search mail_message_res_partner_rel
+ # - search mail_message_reaction
+ # - search mail_notification
+ # - search rating_rating
+ # - fetch mail_notification
+ # - search mail_message_subtype
+ # - search discuss_call_history
+ # - fetch mail_message_reaction
+ # - fetch mail_message_subtype
+ # - fetch partner (_author_to_store)
+ # - search user (_author_to_store)
+ # - fetch user (_author_to_store)
+ # - fetch discuss_call_history
+ # - search mail_tracking_value
+ # - _compute_rating_stats
+ _query_count_discuss_channels = 64
+
def setUp(self):
super().setUp()
+ self.maxDiff = None
self.group_user = self.env.ref('base.group_user')
- self.env['mail.shortcode'].search([]).unlink()
- self.shortcodes = self.env['mail.shortcode'].create([
- {'source': 'hello', 'substitution': 'Hello. How may I help you?'},
- {'source': 'bye', 'substitution': 'Thanks for your feedback. Good bye!'},
- ])
+ self.livechat_user = self.env.ref("im_livechat.im_livechat_group_user")
+ self.password = 'Pl1bhD@2!kXZ'
self.users = self.env['res.users'].create([
{
'email': 'e.e@example.com',
- 'groups_id': [Command.link(self.group_user.id)],
+ 'group_ids': [Command.link(self.group_user.id), Command.link(self.livechat_user.id)],
'login': 'emp',
'name': 'Ernest Employee',
'notification_type': 'inbox',
'odoobot_state': 'disabled',
+ 'password': self.password,
'signature': '--\nErnest',
},
- {'name': 'test1', 'login': 'test1', 'email': 'test1@example.com'},
+ {'name': 'test1', 'login': 'test1', 'password': self.password, 'email': 'test1@example.com', 'country_id': self.env.ref('base.in').id},
{'name': 'test2', 'login': 'test2', 'email': 'test2@example.com'},
{'name': 'test3', 'login': 'test3'},
{'name': 'test4', 'login': 'test4'},
@@ -49,959 +193,1668 @@ class TestDiscussFullPerformance(TransactionCase):
'user_id': user.id,
} for user in self.users])
self.leave_type = self.env['hr.leave.type'].create({
- 'requires_allocation': 'no',
+ 'requires_allocation': False,
'name': 'Legal Leaves',
'time_type': 'leave',
})
self.leaves = self.env['hr.leave'].create([{
- 'date_from': date.today() + relativedelta(days=-2),
- 'date_to': date.today() + relativedelta(days=2),
+ 'request_date_from': fields.Datetime.today() + relativedelta(days=-2),
+ 'request_date_to': fields.Datetime.today() + relativedelta(days=2),
'employee_id': employee.id,
'holiday_status_id': self.leave_type.id,
} for employee in self.employees])
-
- @users('emp')
- @warmup
- def test_init_messaging(self):
- """Test performance of `_init_messaging`."""
- self.channel_general = self.env.ref('mail.channel_all_employees') # Unfortunately #general cannot be deleted. Assertions below assume data from a fresh db.
+ self.authenticate(self.users[0].login, self.password)
+ Channel = self.env["discuss.channel"].with_user(self.users[0])
+ self.channel_general = self.env.ref('mail.channel_all_employees') # Unfortunately #general cannot be deleted. Assertions below assume data from a fresh db with demo.
self.channel_general.message_ids.unlink() # Remove messages to avoid depending on demo data.
- self.env['mail.channel'].sudo().search([('id', '!=', self.channel_general.id)]).unlink()
+ self.channel_general.last_interest_dt = False # Reset state
+ self.channel_general.channel_member_ids.sudo().last_interest_dt = False # Reset state
+ self.env['discuss.channel'].sudo().search([('id', '!=', self.channel_general.id)]).unlink()
self.user_root = self.env.ref('base.user_root')
# create public channels
- self.channel_channel_public_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public channel 1', group_id=None)['id'])
- self.channel_channel_public_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[4] + self.users[8]).partner_id.ids)
- self.channel_channel_public_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='public channel 2', group_id=None)['id'])
- self.channel_channel_public_2.add_members((self.users[0] + self.users[2] + self.users[4] + self.users[7] + self.users[9]).partner_id.ids)
+ self.channel_channel_public_1 = Channel._create_channel(
+ name="public channel 1", group_id=None
+ )
+ self.channel_channel_public_1._add_members(users=self.users[0] | self.users[2] | self.users[3] | self.users[4] | self.users[8])
+ self.channel_channel_public_2 = Channel._create_channel(
+ name="public channel 2", group_id=None
+ )
+ self.channel_channel_public_2._add_members(users=self.users[0] | self.users[2] | self.users[4] | self.users[7] | self.users[9])
# create group-restricted channels
- self.channel_channel_group_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group restricted channel 1', group_id=self.env.ref('base.group_user').id)['id'])
- self.channel_channel_group_1.add_members((self.users[0] + self.users[2] + self.users[3] + self.users[6] + self.users[12]).partner_id.ids)
- self.channel_channel_group_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_create(name='group restricted channel 2', group_id=self.env.ref('base.group_user').id)['id'])
- self.channel_channel_group_2.add_members((self.users[0] + self.users[2] + self.users[6] + self.users[7] + self.users[13]).partner_id.ids)
+ self.channel_channel_group_1 = Channel._create_channel(
+ name="group restricted channel 1", group_id=self.env.ref("base.group_user").id
+ )
+ self.channel_channel_group_1._add_members(users=self.users[0] | self.users[2] | self.users[3] | self.users[6] | self.users[12])
+ self.channel_channel_group_2 = Channel._create_channel(
+ name="group restricted channel 2", group_id=self.env.ref("base.group_user").id
+ )
+ self.channel_channel_group_2._add_members(users=self.users[0] | self.users[2] | self.users[6] | self.users[7] | self.users[13])
# create chats
- self.channel_chat_1 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[14]).partner_id.ids)['id'])
- self.channel_chat_2 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[15]).partner_id.ids)['id'])
- self.channel_chat_3 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[2]).partner_id.ids)['id'])
- self.channel_chat_4 = self.env['mail.channel'].browse(self.env['mail.channel'].channel_get((self.users[0] + self.users[3]).partner_id.ids)['id'])
+ self.channel_chat_1 = Channel._get_or_create_chat((self.users[0] + self.users[14]).partner_id.ids)
+ self.channel_chat_2 = Channel._get_or_create_chat((self.users[0] + self.users[15]).partner_id.ids)
+ self.channel_chat_3 = Channel._get_or_create_chat((self.users[0] + self.users[2]).partner_id.ids)
+ self.channel_chat_4 = Channel._get_or_create_chat((self.users[0] + self.users[3]).partner_id.ids)
# create groups
- self.channel_group_1 = self.env['mail.channel'].browse(self.env['mail.channel'].create_group((self.users[0] + self.users[12]).partner_id.ids)['id'])
+ self.channel_group_1 = Channel._create_group((self.users[0] + self.users[12]).partner_id.ids)
# create livechats
- im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]})
- self.users[0].im_status = 'online' # make available for livechat (ignore leave)
- self.channel_livechat_1 = self.env['mail.channel'].browse(im_livechat_channel._open_livechat_mail_channel(anonymous_name='anon 1', previous_operator_id=self.users[0].partner_id.id, user_id=self.users[1].id, country_id=self.env.ref('base.in').id)['id'])
- self.channel_livechat_1.with_user(self.users[1]).message_post(body="test")
- self.channel_livechat_2 = self.env['mail.channel'].browse(im_livechat_channel.with_user(self.env.ref('base.public_user'))._open_livechat_mail_channel(anonymous_name='anon 2', previous_operator_id=self.users[0].partner_id.id, country_id=self.env.ref('base.be').id)['id'])
- self.channel_livechat_2.with_user(self.env.ref('base.public_user')).sudo().message_post(body="test")
+ self.im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]})
+ self.env['mail.presence']._update_presence(self.users[0])
+ self.authenticate('test1', self.password)
+ self.channel_livechat_1 = Channel.browse(
+ self.make_jsonrpc_request(
+ "/im_livechat/get_session",
+ {
+ "channel_id": self.im_livechat_channel.id,
+ "previous_operator_id": self.users[0].partner_id.id,
+ },
+ )["channel_id"]
+ )
+ self.channel_livechat_1.with_user(self.users[1]).message_post(body="test", message_type="comment")
+ # add conversation tags into livechat channels
+ self.conversation_tag = self.env["im_livechat.conversation.tag"].create({"name": "Support", "color": 1})
+ self.channel_livechat_1.livechat_conversation_tag_ids = [Command.link(self.conversation_tag.id)]
+ self.authenticate(None, None)
+ with patch(
+ "odoo.http.GeoIP.country_code",
+ new_callable=PropertyMock(return_value=self.env.ref("base.be").code),
+ ):
+ self.channel_livechat_2 = Channel.browse(
+ self.make_jsonrpc_request(
+ "/im_livechat/get_session",
+ {
+ "channel_id": self.im_livechat_channel.id,
+ "previous_operator_id": self.users[0].partner_id.id,
+ },
+ )["channel_id"]
+ )
+ self.guest = self.channel_livechat_2.channel_member_ids.guest_id.sudo()
+ self.make_jsonrpc_request("/mail/message/post", {
+ "post_data": {
+ "body": "test",
+ "message_type": "comment",
+ },
+ "thread_id": self.channel_livechat_2.id,
+ "thread_model": "discuss.channel",
+ }, cookies={
+ self.guest._cookie_name: self.guest._format_auth_cookie(),
+ })
# add needaction
self.users[0].notification_type = 'inbox'
- message = self.channel_channel_public_1.message_post(body='test', message_type='comment', author_id=self.users[2].partner_id.id, partner_ids=self.users[0].partner_id.ids)
+ message_0 = self.channel_channel_public_1.message_post(
+ body="test",
+ message_type="comment",
+ author_id=self.users[2].partner_id.id,
+ partner_ids=self.users[0].partner_id.ids,
+ )
+ members = self.channel_channel_public_1.channel_member_ids
+ member = members.filtered(lambda m: m.partner_id == self.users[0].partner_id).with_user(self.users[0])
+ member._mark_as_read(message_0.id)
# add star
- message.toggle_message_starred()
+ message_0.toggle_message_starred()
self.env.company.sudo().name = 'YourCompany'
+ # add folded channel
+ members = self.channel_chat_1.channel_member_ids
+ member = members.with_user(self.users[0]).filtered(lambda m: m.is_self)
+ # add call invitation
+ members = self.channel_channel_group_1.channel_member_ids
+ member_0 = members.with_user(self.users[0]).filtered(lambda m: m.is_self)
+ member_2 = members.with_user(self.users[2]).filtered(lambda m: m.is_self)
+ self.channel_channel_group_1_invited_member = member_0
+ # sudo: discuss.channel.rtc.session - creating a session in a test file
+ data = {"channel_id": self.channel_channel_group_1.id, "channel_member_id": member_2.id}
+ session = self.env["discuss.channel.rtc.session"].sudo().create(data)
+ member_0.rtc_inviting_session_id = session
+ # add some reactions with different users on different messages
+ message_1 = self.channel_general.message_post(
+ body="test", message_type="comment", author_id=self.users[0].partner_id.id
+ )
+ self.authenticate(self.users[0].login, self.password)
+ self._add_reactions(message_0, ["😊", "😏"])
+ self._add_reactions(message_1, ["😊"])
+ self.authenticate(self.users[1].login, self.password)
+ self._add_reactions(message_0, ["😊", "😏"])
+ self._add_reactions(message_1, ["😊", "😁"])
+ self.authenticate(self.users[2].login, self.password)
+ self._add_reactions(message_0, ["😊", "😁"])
+ self._add_reactions(message_1, ["😊", "😁", "👍"])
+ self.env.cr.precommit.run() # trigger the creation of bus.bus records
- self.maxDiff = None
- self.env.flush_all()
- self.env.invalidate_all()
- with self.assertQueryCount(emp=self._get_query_count()):
- init_messaging = self.users[0].with_user(self.users[0])._init_messaging()
+ def _add_reactions(self, message, reactions):
+ for reaction in reactions:
+ self.make_jsonrpc_request(
+ "/mail/message/reaction",
+ {
+ "action": "add",
+ "content": reaction,
+ "message_id": message.id,
+ },
+ )
- self.assertEqual(init_messaging, self._get_init_messaging_result())
+ def _run_test(self, /, *, fn, count, results):
+ self.authenticate(self.users[0].login, self.password)
+ self.env["res.lang"]._get_data(code="en_US") # cache language for validation
+ with self.assertQueryCount(emp=count):
+ if self.warm:
+ with self.env.cr._enable_logging():
+ res = fn()
+ else:
+ res = fn()
+ self.assertEqual(res, results)
- def _get_init_messaging_result(self):
- """
- Returns the result of a call to init_messaging.
-
- The point of having a separate getter is to allow it to be overriden.
+ @freeze_time("2025-04-22 21:18:33")
+ @users('emp')
+ @warmup
+ def test_10_init_store_data(self):
+ """Test performance of `_init_store_data`."""
+
+ def test_fn():
+ store = Store()
+ self.env["res.users"].with_user(self.users[0])._init_store_data(store)
+ return store.get_result()
+
+ self._run_test(
+ fn=test_fn,
+ count=self._query_count_init_store,
+ results=self._get_init_store_data_result(),
+ )
+
+ @freeze_time("2025-04-22 21:18:33")
+ @users('emp')
+ @warmup
+ def test_20_init_messaging(self):
+ """Test performance of `init_messaging`."""
+ self._run_test(
+ fn=lambda: self.make_jsonrpc_request(
+ "/mail/data",
+ {"fetch_params": [["discuss.channel", [self.channel_chat_1.id]], "init_messaging"]},
+ ),
+ count=self._query_count_init_messaging,
+ results=self._get_init_messaging_result(),
+ )
+
+ @freeze_time("2025-04-22 21:18:33")
+ @users("emp")
+ @warmup
+ def test_30_discuss_channels(self):
+ """Test performance of `/mail/data` with `channels_as_member`."""
+ self._run_test(
+ fn=lambda: self.make_jsonrpc_request(
+ "/mail/data", {"fetch_params": ["channels_as_member"]}
+ ),
+ count=self._query_count_discuss_channels,
+ results=self._get_discuss_channels_result(),
+ )
+
+ def _get_init_store_data_result(self):
+ """Returns the result of a call to init_messaging.
+ The point of having a separate getter is to allow it to be overriden.
"""
+ xmlid_to_res_id = self.env["ir.model.data"]._xmlid_to_res_id
+ user_0 = self.users[0]
+ partner_0 = user_0.partner_id
return {
- 'hasLinkPreviewFeature': True,
- 'needaction_inbox_counter': 1,
- 'starred_counter': 1,
- 'channels': [
+ "res.partner": self._filter_partners_fields(
{
- 'authorizedGroupFullName': self.group_user.full_name,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_general._get_avatar_cache_key(),
- 'channel_type': 'channel',
- 'channelMembers': [('insert', sorted([{
- 'channel': {
- 'id': self.channel_general.id,
- },
- 'id': self.channel_general.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- }], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_general.id,
- 'memberCount': len(self.group_user.users | self.user_root),
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.user_root.id,
- 'defaultDisplayMode': False,
- 'description': 'General announcements for all employees.',
- 'group_based_subscription': True,
- 'id': self.channel_general.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_general.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': 'general',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_general.uuid,
+ "active": False,
+ "avatar_128_access_token": self.user_root.partner_id._get_avatar_128_access_token(),
+ "email": "odoobot@example.com",
+ "id": self.user_root.partner_id.id,
+ "im_status": "bot",
+ "im_status_access_token": self.user_root.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": self.user_root.id,
+ "name": "OdooBot",
+ "write_date": fields.Datetime.to_string(self.user_root.partner_id.write_date),
},
{
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_channel_public_1._get_avatar_cache_key(),
- 'channel_type': 'channel',
- 'channelMembers': [('insert', sorted([{
- 'channel': {
- 'id': self.channel_channel_public_1.id,
- },
- 'id': self.channel_channel_public_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- }], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_channel_public_1.id,
- 'memberCount': 5,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_channel_public_1.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_channel_public_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_channel_public_1._channel_last_message_ids()),
- 'message_needaction_counter': 1,
- 'name': 'public channel 1',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': next(res['message_id'] for res in self.channel_channel_public_1._channel_last_message_ids()),
- 'state': 'open',
- 'uuid': self.channel_channel_public_1.uuid,
+ "active": True,
+ "avatar_128_access_token": partner_0._get_avatar_128_access_token(),
+ "id": partner_0.id,
+ "im_status": "online",
+ "im_status_access_token": partner_0._get_im_status_access_token(),
+ "main_user_id": user_0.id,
+ "name": "Ernest Employee",
+ "write_date": fields.Datetime.to_string(partner_0.write_date),
+ },
+ ),
+ "res.users": self._filter_users_fields(
+ {
+ "employee_ids": [],
+ "id": self.user_root.id,
+ "partner_id": self.partner_root.id,
+ "share": False,
},
{
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_channel_public_2._get_avatar_cache_key(),
- 'channel_type': 'channel',
- 'channelMembers': [('insert', sorted([{
- 'channel': {
- 'id': self.channel_channel_public_2.id,
- },
- 'id': self.channel_channel_public_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- }], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_channel_public_2.id,
- 'memberCount': 5,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_channel_public_2.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_channel_public_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_channel_public_2._channel_last_message_ids()),
- 'message_needaction_counter': 0,
- 'name': 'public channel 2',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': next(res['message_id'] for res in self.channel_channel_public_2._channel_last_message_ids()),
- 'state': 'open',
- 'uuid': self.channel_channel_public_2.uuid,
+ "id": user_0.id,
+ "is_admin": False,
+ "is_livechat_manager": False,
+ "notification_type": "inbox",
+ "partner_id": partner_0.id,
+ "share": False,
+ "signature": ["markup", str(user_0.signature)],
},
- {
- 'authorizedGroupFullName': self.group_user.full_name,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_channel_group_1._get_avatar_cache_key(),
- 'channel_type': 'channel',
- 'channelMembers': [('insert', sorted([{
- 'channel': {
- 'id': self.channel_channel_group_1.id,
- },
- 'id': self.channel_channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- }], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_channel_group_1.id,
- 'memberCount': 5,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_channel_group_1.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_channel_group_1._channel_last_message_ids()),
- 'message_needaction_counter': 0,
- 'name': 'group restricted channel 1',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': next(res['message_id'] for res in self.channel_channel_group_1._channel_last_message_ids()),
- 'state': 'open',
- 'uuid': self.channel_channel_group_1.uuid,
+ ),
+ "Store": {
+ "channel_types_with_seen_infos": sorted(["chat", "group", "livechat"]),
+ "action_discuss_id": xmlid_to_res_id("mail.action_discuss"),
+ "hasCannedResponses": True,
+ "hasGifPickerFeature": False,
+ "hasLinkPreviewFeature": True,
+ "has_access_livechat": True,
+ "hasMessageTranslationFeature": False,
+ "has_access_create_lead": False,
+ "internalUserGroupId": self.env.ref("base.group_user").id,
+ "mt_comment": self.env.ref("mail.mt_comment").id,
+ "mt_note": self.env.ref("mail.mt_note").id,
+ "odoobot": self.user_root.partner_id.id,
+ "self_partner": self.users[0].partner_id.id,
+ "settings": {
+ "channel_notifications": False,
+ "id": self.env["res.users.settings"]._find_or_create_for_user(self.users[0]).id,
+ "is_discuss_sidebar_category_channel_open": True,
+ "is_discuss_sidebar_category_chat_open": True,
+ "livechat_expertise_ids": [],
+ "livechat_lang_ids": [],
+ "livechat_username": False,
+ "push_to_talk_key": False,
+ "use_push_to_talk": False,
+ "user_id": {"id": self.users[0].id},
+ "voice_active_duration": 200,
+ "volumes": [("ADD", [])],
+ "embedded_actions_config_ids": {},
},
- {
- 'authorizedGroupFullName': self.group_user.full_name,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_channel_group_2._get_avatar_cache_key(),
- 'channel_type': 'channel',
- 'channelMembers': [('insert', sorted([{
- 'channel': {
- 'id': self.channel_channel_group_2.id,
- },
- 'id': self.channel_channel_group_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- }], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_channel_group_2.id,
- 'memberCount': 5,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_channel_group_2.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_channel_group_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_channel_group_2._channel_last_message_ids()),
- 'message_needaction_counter': 0,
- 'name': 'group restricted channel 2',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': next(res['message_id'] for res in self.channel_channel_group_2._channel_last_message_ids()),
- 'state': 'open',
- 'uuid': self.channel_channel_group_2.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_group_1._get_avatar_cache_key(),
- 'channel_type': 'group',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_group_1.id,
- },
- 'id': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_group_1.id,
- },
- 'id': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[12].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': False,
- 'id': self.users[12].partner_id.id,
- 'im_status': 'offline',
- 'name': 'test12',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[12].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_group_1.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_group_1.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': '',
- 'rtcSessions': [('insert', [])],
- 'seen_message_id': False,
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_group_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[12].partner_id).id,
- 'partner_id': self.users[12].partner_id.id,
- 'seen_message_id': False,
- }
- ],
- 'state': 'open',
- 'uuid': self.channel_group_1.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_chat_1._get_avatar_cache_key(),
- 'channel_type': 'chat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_chat_1.id,
- },
- 'id': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_chat_1.id,
- },
- 'id': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[14].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': False,
- 'id': self.users[14].partner_id.id,
- 'im_status': 'offline',
- 'name': 'test14',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[14].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_chat_1.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_chat_1.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': 'Ernest Employee, test14',
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[14].partner_id).id,
- 'partner_id': self.users[14].partner_id.id,
- 'seen_message_id': False,
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_chat_1.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_chat_2._get_avatar_cache_key(),
- 'channel_type': 'chat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_chat_2.id,
- },
- 'id': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_chat_2.id,
- },
- 'id': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[15].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': False,
- 'id': self.users[15].partner_id.id,
- 'im_status': 'offline',
- 'name': 'test15',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[15].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_chat_2.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_chat_2.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': 'Ernest Employee, test15',
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[15].partner_id).id,
- 'partner_id': self.users[15].partner_id.id,
- 'seen_message_id': False,
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_chat_2.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_chat_3._get_avatar_cache_key(),
- 'channel_type': 'chat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_chat_3.id,
- },
- 'id': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_chat_3.id,
- },
- 'id': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[2].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'test2@example.com',
- 'id': self.users[2].partner_id.id,
- 'im_status': 'offline',
- 'name': 'test2',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[2].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_chat_3.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_chat_3.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': 'Ernest Employee, test2',
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_3.channel_member_ids.filtered(lambda m: m.partner_id == self.users[2].partner_id).id,
- 'partner_id': self.users[2].partner_id.id,
- 'seen_message_id': False,
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_chat_3.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': [('clear',)],
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_chat_4._get_avatar_cache_key(),
- 'channel_type': 'chat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_chat_4.id,
- },
- 'id': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_chat_4.id,
- },
- 'id': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[3].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'email': False,
- 'id': self.users[3].partner_id.id,
- 'im_status': 'offline',
- 'name': 'test3',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[3].id,
- 'isInternalUser': True,
- },
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_chat_4.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_chat_4.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': False,
- 'message_needaction_counter': 0,
- 'name': 'Ernest Employee, test3',
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_chat_4.channel_member_ids.filtered(lambda m: m.partner_id == self.users[3].partner_id).id,
- 'partner_id': self.users[3].partner_id.id,
- 'seen_message_id': False,
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_chat_4.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': {
- 'code': 'IN',
- 'id': self.env.ref('base.in').id,
- 'name': 'India',
- },
- 'anonymous_name': False,
- 'avatarCacheKey': self.channel_livechat_1._get_avatar_cache_key(),
- 'channel_type': 'livechat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_livechat_1.id,
- },
- 'id': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'country': [('clear',)],
- 'id': self.users[0].partner_id.id,
- 'is_public': False,
- 'name': 'Ernest Employee',
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_livechat_1.id,
- },
- 'id': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[1].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'country': [('clear',)],
- 'id': self.users[1].partner_id.id,
- 'is_public': False,
- 'name': 'test1',
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_livechat_1.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.user.id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_livechat_1.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_livechat_1._channel_last_message_ids()),
- 'message_needaction_counter': 0,
- 'name': 'test1 Ernest Employee',
- 'operator_pid': (self.users[0].partner_id.id, 'Ernest Employee'),
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': False,
- 'id': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- {
- 'fetched_message_id': next(res['message_id'] for res in self.channel_livechat_1._channel_last_message_ids()),
- 'id': self.channel_livechat_1.channel_member_ids.filtered(lambda m: m.partner_id == self.users[1].partner_id).id,
- 'partner_id': self.users[1].partner_id.id,
- 'seen_message_id': next(res['message_id'] for res in self.channel_livechat_1._channel_last_message_ids()),
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_livechat_1.uuid,
- },
- {
- 'authorizedGroupFullName': False,
- 'channel': {
- 'anonymous_country': {
- 'id': self.env.ref('base.be').id,
- 'code': 'BE',
- 'name': 'Belgium',
- },
- 'anonymous_name': 'anon 2',
- 'avatarCacheKey': self.channel_livechat_2._get_avatar_cache_key(),
- 'channel_type': 'livechat',
- 'channelMembers': [('insert', sorted([
- {
- 'channel': {
- 'id': self.channel_livechat_2.id,
- },
- 'id': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'persona': {
- 'partner': {
- 'active': True,
- 'country': [('clear',)],
- 'id': self.users[0].partner_id.id,
- 'is_public': False,
- 'name': 'Ernest Employee',
- },
- },
- },
- {
- 'channel': {
- 'id': self.channel_livechat_2.id,
- },
- 'id': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.env.ref('base.public_partner')).id,
- 'persona': {
- 'partner': {
- 'active': False,
- 'id': self.env.ref('base.public_partner').id,
- 'is_public': True,
- 'name': 'Public user',
- },
- },
- },
- ], key=lambda member_data: member_data['id']))],
- 'custom_channel_name': False,
- 'id': self.channel_livechat_2.id,
- 'memberCount': 2,
- 'serverMessageUnreadCounter': 0,
- },
- 'create_uid': self.env.ref('base.public_user').id,
- 'defaultDisplayMode': False,
- 'description': False,
- 'group_based_subscription': False,
- 'id': self.channel_livechat_2.id,
- 'invitedMembers': [('insert', [])],
- 'is_minimized': False,
- 'is_pinned': True,
- 'last_interest_dt': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).last_interest_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'last_message_id': next(res['message_id'] for res in self.channel_livechat_2._channel_last_message_ids()),
- 'message_needaction_counter': 0,
- 'name': 'anon 2 Ernest Employee',
- 'operator_pid': (self.users[0].partner_id.id, 'Ernest Employee'),
- 'rtcSessions': [('insert', [])],
- 'seen_partners_info': [
- {
- 'fetched_message_id': next(res['message_id'] for res in self.channel_livechat_2._channel_last_message_ids()),
- 'id': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.env.ref('base.public_partner')).id,
- 'partner_id': self.env.ref('base.public_user').partner_id.id,
- 'seen_message_id': next(res['message_id'] for res in self.channel_livechat_2._channel_last_message_ids()),
- },
- {
- 'fetched_message_id': False,
- 'id': self.channel_livechat_2.channel_member_ids.filtered(lambda m: m.partner_id == self.users[0].partner_id).id,
- 'partner_id': self.users[0].partner_id.id,
- 'seen_message_id': False,
- },
- ],
- 'seen_message_id': False,
- 'state': 'open',
- 'uuid': self.channel_livechat_2.uuid,
- },
- ],
- 'companyName': 'YourCompany',
- 'shortcodes': [
- {
- 'id': self.shortcodes[0].id,
- 'source': 'hello',
- 'substitution': 'Hello. How may I help you?',
- },
- {
- 'id': self.shortcodes[1].id,
- 'source': 'bye',
- 'substitution': 'Thanks for your feedback. Good bye!',
- },
- ],
- 'internalUserGroupId': self.env.ref('base.group_user').id,
- 'menu_id': self.env['ir.model.data']._xmlid_to_res_id('mail.menu_root_discuss'),
- 'partner_root': {
- 'active': False,
- 'email': 'odoobot@example.com',
- 'id': self.user_root.partner_id.id,
- 'im_status': 'bot',
- 'name': 'OdooBot',
- 'out_of_office_date_end': False,
- 'user': [('clear',)],
- },
- 'currentGuest': False,
- 'current_partner': {
- 'active': True,
- 'email': 'e.e@example.com',
- 'id': self.users[0].partner_id.id,
- 'im_status': 'offline',
- 'name': 'Ernest Employee',
- 'out_of_office_date_end': False,
- 'user': {
- 'id': self.users[0].id,
- 'isInternalUser': True,
- },
- },
- 'current_user_id': self.users[0].id,
- 'current_user_settings': {
- 'id': self.env['res.users.settings']._find_or_create_for_user(self.users[0]).id,
- 'is_discuss_sidebar_category_channel_open': True,
- 'is_discuss_sidebar_category_chat_open': True,
- 'is_discuss_sidebar_category_livechat_open': True,
- 'push_to_talk_key': False,
- 'use_push_to_talk': False,
- 'user_id': {'id': self.users[0].id},
- 'voice_active_duration': 0,
- 'volume_settings_ids': [('insert', [])],
},
}
- def _get_query_count(self):
+ def _get_init_messaging_result(self):
+ """Returns the result of a call to init_messaging.
+ The point of having a separate getter is to allow it to be overriden.
"""
- Returns the expected query count.
- The point of having a separate getter is to allow it to be overriden.
+ # sudo: bus.bus: reading non-sensitive last id
+ bus_last_id = self.env["bus.bus"].sudo()._bus_last_id()
+ return {
+ "discuss.channel": self._filter_channels_fields(
+ self._expected_result_for_channel(self.channel_chat_1),
+ self._expected_result_for_channel(self.channel_channel_group_1),
+ ),
+ "discuss.channel.member": [
+ self._res_for_member(self.channel_chat_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_chat_1, self.users[14].partner_id),
+ self._res_for_member(self.channel_channel_group_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_channel_group_1, self.users[2].partner_id),
+ ],
+ "discuss.channel.rtc.session": [
+ self._expected_result_for_rtc_session(self.channel_channel_group_1, self.users[2]),
+ ],
+ "res.groups": [{'full_name': 'Role / User', 'id': self.env.ref("base.group_user").id}],
+ "res.partner": self._filter_partners_fields(
+ self._expected_result_for_persona(self.users[0]),
+ self._expected_result_for_persona(self.users[14]),
+ self._expected_result_for_persona(self.users[2], only_inviting=True),
+ ),
+ "res.users": self._filter_users_fields(
+ self._res_for_user(self.users[0]),
+ self._res_for_user(self.users[14]),
+ ),
+ "hr.employee": [
+ self._res_for_employee(self.users[0].employee_ids[0]),
+ self._res_for_employee(self.users[14].employee_ids[0]),
+ ],
+ "Store": {
+ "inbox": {
+ "counter": 1,
+ "counter_bus_id": bus_last_id,
+ "id": "inbox",
+ "model": "mail.box",
+ },
+ "starred": {
+ "counter": 1,
+ "counter_bus_id": bus_last_id,
+ "id": "starred",
+ "model": "mail.box",
+ },
+ "initChannelsUnreadCounter": 3,
+ },
+ }
+
+ def _get_discuss_channels_result(self):
+ """Returns the result of a call to `/mail/data` with `channels_as_member`.
+ The point of having a separate getter is to allow it to be overriden.
"""
- return 81
+ return {
+ "discuss.call.history": [
+ {
+ "duration_hour": self.channel_channel_group_1.call_history_ids.duration_hour,
+ "end_dt": False,
+ "id": self.channel_channel_group_1.call_history_ids.id,
+ },
+ ],
+ "discuss.channel": self._filter_channels_fields(
+ self._expected_result_for_channel(self.channel_general),
+ self._expected_result_for_channel(self.channel_channel_public_1),
+ self._expected_result_for_channel(self.channel_channel_public_2),
+ self._expected_result_for_channel(self.channel_channel_group_1),
+ self._expected_result_for_channel(self.channel_channel_group_2),
+ self._expected_result_for_channel(self.channel_group_1),
+ self._expected_result_for_channel(self.channel_chat_1),
+ self._expected_result_for_channel(self.channel_chat_2),
+ self._expected_result_for_channel(self.channel_chat_3),
+ self._expected_result_for_channel(self.channel_chat_4),
+ self._expected_result_for_channel(self.channel_livechat_1),
+ self._expected_result_for_channel(self.channel_livechat_2),
+ ),
+ "discuss.channel.member": [
+ self._res_for_member(self.channel_general, self.users[0].partner_id),
+ self._res_for_member(self.channel_channel_public_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_channel_public_2, self.users[0].partner_id),
+ self._res_for_member(self.channel_channel_group_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_channel_group_1, self.users[2].partner_id),
+ self._res_for_member(self.channel_channel_group_2, self.users[0].partner_id),
+ self._res_for_member(self.channel_group_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_group_1, self.users[12].partner_id),
+ self._res_for_member(self.channel_chat_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_chat_1, self.users[14].partner_id),
+ self._res_for_member(self.channel_chat_2, self.users[0].partner_id),
+ self._res_for_member(self.channel_chat_2, self.users[15].partner_id),
+ self._res_for_member(self.channel_chat_3, self.users[0].partner_id),
+ self._res_for_member(self.channel_chat_3, self.users[2].partner_id),
+ self._res_for_member(self.channel_chat_4, self.users[0].partner_id),
+ self._res_for_member(self.channel_chat_4, self.users[3].partner_id),
+ self._res_for_member(self.channel_livechat_1, self.users[0].partner_id),
+ self._res_for_member(self.channel_livechat_1, self.users[1].partner_id),
+ self._res_for_member(self.channel_livechat_2, self.users[0].partner_id),
+ self._res_for_member(self.channel_livechat_2, guest=True),
+ ],
+ "discuss.channel.rtc.session": [
+ self._expected_result_for_rtc_session(self.channel_channel_group_1, self.users[2]),
+ ],
+ "im_livechat.channel": [
+ self._expected_result_for_livechat_channel(),
+ ],
+ "im_livechat.conversation.tag": [
+ {"id": self.conversation_tag.id, "name": "Support", "color": self.conversation_tag.color},
+ ],
+ "mail.guest": [
+ self._expected_result_for_persona(guest=True),
+ ],
+ "mail.message": self._filter_messages_fields(
+ self._expected_result_for_message(self.channel_general),
+ self._expected_result_for_message(self.channel_channel_public_1),
+ self._expected_result_for_message(self.channel_channel_public_2),
+ self._expected_result_for_message(self.channel_channel_group_1),
+ self._expected_result_for_message(self.channel_channel_group_2),
+ self._expected_result_for_message(self.channel_livechat_1),
+ self._expected_result_for_message(self.channel_livechat_2),
+ ),
+ "mail.notification": [
+ self._expected_result_for_notification(self.channel_channel_public_1),
+ ],
+ "mail.message.subtype": [
+ {"description": False, "id": self.env.ref("mail.mt_note").id},
+ {"description": False, "id": self.env.ref("mail.mt_comment").id},
+ ],
+ "mail.thread": self._filter_threads_fields(
+ self._expected_result_for_thread(self.channel_general),
+ self._expected_result_for_thread(self.channel_channel_public_1),
+ self._expected_result_for_thread(self.channel_channel_public_2),
+ self._expected_result_for_thread(self.channel_channel_group_1),
+ self._expected_result_for_thread(self.channel_channel_group_2),
+ self._expected_result_for_thread(self.channel_livechat_1),
+ self._expected_result_for_thread(self.channel_livechat_2),
+ ),
+ "MessageReactions": [
+ *self._expected_result_for_message_reactions(self.channel_general),
+ *self._expected_result_for_message_reactions(self.channel_channel_public_1),
+ ],
+ "res.country": [
+ {"code": "IN", "id": self.env.ref("base.in").id, "name": "India"},
+ {"code": "BE", "id": self.env.ref("base.be").id, "name": "Belgium"},
+ ],
+ "res.groups": [{"full_name": "Role / User", "id": self.env.ref("base.group_user").id}],
+ "res.partner": self._filter_partners_fields(
+ self._expected_result_for_persona(
+ self.users[0],
+ also_livechat=True,
+ also_notification=True,
+ ),
+ self._expected_result_for_persona(self.users[2]),
+ self._expected_result_for_persona(self.users[12]),
+ self._expected_result_for_persona(self.users[14]),
+ self._expected_result_for_persona(self.users[15]),
+ self._expected_result_for_persona(self.users[3]),
+ self._expected_result_for_persona(self.users[1], also_livechat=True),
+ self._expected_result_for_persona(self.user_root),
+ ),
+ "res.users": self._filter_users_fields(
+ self._res_for_user(self.users[0]),
+ self._res_for_user(self.users[12]),
+ self._res_for_user(self.users[14]),
+ self._res_for_user(self.users[15]),
+ self._res_for_user(self.users[2]),
+ self._res_for_user(self.users[3]),
+ self._res_for_user(self.user_root),
+ self._res_for_user(self.users[1]),
+ ),
+ "hr.employee": [
+ self._res_for_employee(self.users[0].employee_ids[0]),
+ self._res_for_employee(self.users[12].employee_ids[0]),
+ self._res_for_employee(self.users[14].employee_ids[0]),
+ self._res_for_employee(self.users[15].employee_ids[0]),
+ self._res_for_employee(self.users[2].employee_ids[0]),
+ self._res_for_employee(self.users[3].employee_ids[0]),
+ ],
+ }
+
+ def _expected_result_for_channel(self, channel):
+ # sudo: bus.bus: reading non-sensitive last id
+ bus_last_id = self.env["bus.bus"].sudo()._bus_last_id()
+ members = channel.channel_member_ids
+ member_0 = members.filtered(lambda m: m.partner_id == self.users[0].partner_id)
+ member_2 = members.filtered(lambda m: m.partner_id == self.users[2].partner_id)
+ member_12 = members.filtered(lambda m: m.partner_id == self.users[12].partner_id)
+ last_interest_dt = fields.Datetime.to_string(channel.last_interest_dt)
+ if channel == self.channel_general:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_type": "channel",
+ "create_uid": self.user_root.id,
+ "default_display_mode": False,
+ "description": "General announcements for all employees.",
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "group_ids": channel.group_ids.ids,
+ "group_public_id": self.env.ref("base.group_user").id,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": len(self.group_user.all_user_ids),
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "general",
+ "parent_channel_id": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_channel_public_1:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_type": "channel",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "group_ids": [],
+ "group_public_id": False,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 5,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 1,
+ "name": "public channel 1",
+ "parent_channel_id": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_channel_public_2:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_type": "channel",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "group_ids": [],
+ "group_public_id": False,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 5,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "public channel 2",
+ "parent_channel_id": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_channel_group_1:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_type": "channel",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "group_ids": [],
+ "group_public_id": self.env.ref("base.group_user").id,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", [member_0.id]]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 5,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "group restricted channel 1",
+ "parent_channel_id": False,
+ # sudo: discuss.channel.rtc.session - reading a session in a test file
+ "rtc_session_ids": [["ADD", [member_2.sudo().rtc_session_ids.id]]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_channel_group_2:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_type": "channel",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "group_ids": [],
+ "group_public_id": self.env.ref("base.group_user").id,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 5,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "group restricted channel 2",
+ "parent_channel_id": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_group_1:
+ return {
+ "avatar_cache_key": channel.avatar_cache_key,
+ "channel_name_member_ids": [member_0.id, member_12.id],
+ "channel_type": "group",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "from_message_id": False,
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "",
+ "parent_channel_id": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_chat_1:
+ return {
+ "channel_type": "chat",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "Ernest Employee, test14",
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_chat_2:
+ return {
+ "channel_type": "chat",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "Ernest Employee, test15",
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_chat_3:
+ return {
+ "channel_type": "chat",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "Ernest Employee, test2",
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_chat_4:
+ return {
+ "channel_type": "chat",
+ "create_uid": self.env.user.id,
+ "default_display_mode": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "Ernest Employee, test3",
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_livechat_1:
+ return {
+ "ai_agent_id": False,
+ "channel_type": "livechat",
+ "country_id": self.env.ref("base.in").id,
+ "create_uid": self.users[1].id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "livechat_end_dt": False,
+ "livechat_channel_id": self.im_livechat_channel.id,
+ "livechat_conversation_tag_ids": [self.conversation_tag.id],
+ "livechat_note": False,
+ "livechat_outcome": "no_answer",
+ "livechat_status": "in_progress",
+ "livechat_lang_id": False,
+ "livechat_visitor_id": False,
+ "livechat_expertise_ids": [],
+ "livechat_operator_id": self.users[0].partner_id.id,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "test1 Ernest Employee",
+ "requested_by_operator": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ if channel == self.channel_livechat_2:
+ return {
+ "ai_agent_id": False,
+ "channel_type": "livechat",
+ "country_id": self.env.ref("base.be").id,
+ "create_uid": self.env.ref("base.public_user").id,
+ "default_display_mode": False,
+ "description": False,
+ "fetchChannelInfoState": "fetched",
+ "id": channel.id,
+ "invited_member_ids": [["ADD", []]],
+ "is_editable": True,
+ "last_interest_dt": last_interest_dt,
+ "livechat_end_dt": False,
+ "livechat_channel_id": self.im_livechat_channel.id,
+ "livechat_conversation_tag_ids": [],
+ "livechat_note": False,
+ "livechat_outcome": "no_answer",
+ "livechat_status": "in_progress",
+ "livechat_lang_id": False,
+ "livechat_visitor_id": False,
+ "livechat_expertise_ids": [],
+ "livechat_operator_id": self.users[0].partner_id.id,
+ "member_count": 2,
+ "message_needaction_counter_bus_id": bus_last_id,
+ "message_needaction_counter": 0,
+ "name": "Visitor Ernest Employee",
+ "requested_by_operator": False,
+ "rtc_session_ids": [["ADD", []]],
+ "uuid": channel.uuid,
+ }
+ return {}
+
+ def _res_for_member(self, channel, partner=None, guest=None):
+ members = channel.channel_member_ids
+ member_0 = members.filtered(lambda m: m.partner_id == self.users[0].partner_id)
+ member_0_last_interest_dt = fields.Datetime.to_string(member_0.last_interest_dt)
+ member_0_last_seen_dt = fields.Datetime.to_string(member_0.last_seen_dt)
+ member_0_create_date = fields.Datetime.to_string(member_0.create_date)
+ member_1 = members.filtered(lambda m: m.partner_id == self.users[1].partner_id)
+ member_2 = members.filtered(lambda m: m.partner_id == self.users[2].partner_id)
+ member_3 = members.filtered(lambda m: m.partner_id == self.users[3].partner_id)
+ member_12 = members.filtered(lambda m: m.partner_id == self.users[12].partner_id)
+ member_14 = members.filtered(lambda m: m.partner_id == self.users[14].partner_id)
+ member_15 = members.filtered(lambda m: m.partner_id == self.users[15].partner_id)
+ last_message = channel._get_last_messages()
+ last_message_of_partner_0 = self.env["mail.message"].search(
+ Domain("author_id", "=", member_0.partner_id.id)
+ & Domain("model", "=", "discuss.channel")
+ & Domain("res_id", "=", channel.id),
+ order="id desc",
+ limit=1,
+ )
+ member_g = members.filtered(lambda m: m.guest_id)
+ guest = member_g.guest_id
+ # sudo: bus.bus: reading non-sensitive last id
+ bus_last_id = self.env["bus.bus"].sudo()._bus_last_id()
+ if channel == self.channel_general and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 1,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_channel_public_1 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": last_message.id,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": last_message.id + 1,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": last_message.id,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_channel_public_2 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": last_message.id,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": last_message.id + 1,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": last_message.id,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_channel_group_1 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": last_message_of_partner_0.id,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": last_message_of_partner_0.id + 1,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": member_0.rtc_inviting_session_id.id,
+ "seen_message_id": last_message_of_partner_0.id,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_channel_group_1 and partner == self.users[2].partner_id:
+ return {
+ "id": member_2.id,
+ "partner_id": self.users[2].partner_id.id,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_channel_group_2 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": last_message.id,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": last_message.id + 1,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": last_message.id,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_group_1 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_group_1 and partner == self.users[12].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_12.create_date),
+ "last_seen_dt": False,
+ "fetched_message_id": False,
+ "id": member_12.id,
+ "partner_id": self.users[12].partner_id.id,
+ "seen_message_id": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_1 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_1 and partner == self.users[14].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_14.create_date),
+ "last_seen_dt": False,
+ "fetched_message_id": False,
+ "id": member_14.id,
+ "partner_id": self.users[14].partner_id.id,
+ "seen_message_id": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_2 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_2 and partner == self.users[15].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_15.create_date),
+ "last_seen_dt": False,
+ "fetched_message_id": False,
+ "id": member_15.id,
+ "partner_id": self.users[15].partner_id.id,
+ "seen_message_id": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_3 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_3 and partner == self.users[2].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_2.create_date),
+ "last_seen_dt": False,
+ "fetched_message_id": False,
+ "id": member_2.id,
+ "partner_id": self.users[2].partner_id.id,
+ "seen_message_id": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_4 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 0,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_chat_4 and partner == self.users[3].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_3.create_date),
+ "last_seen_dt": False,
+ "fetched_message_id": False,
+ "id": member_3.id,
+ "partner_id": self.users[3].partner_id.id,
+ "seen_message_id": False,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_livechat_1 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "livechat_member_type": "agent",
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 1,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": fields.Datetime.to_string(member_0.unpin_dt),
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_livechat_1 and partner == self.users[1].partner_id:
+ return {
+ "create_date": fields.Datetime.to_string(member_1.create_date),
+ "last_seen_dt": fields.Datetime.to_string(member_1.last_seen_dt),
+ "fetched_message_id": last_message.id,
+ "id": member_1.id,
+ "livechat_member_type": "visitor",
+ "partner_id": self.users[1].partner_id.id,
+ "seen_message_id": last_message.id,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_livechat_2 and partner == self.users[0].partner_id:
+ return {
+ "create_date": member_0_create_date,
+ "custom_channel_name": False,
+ "custom_notifications": False,
+ "fetched_message_id": False,
+ "id": member_0.id,
+ "livechat_member_type": "agent",
+ "last_interest_dt": member_0_last_interest_dt,
+ "message_unread_counter": 1,
+ "message_unread_counter_bus_id": bus_last_id,
+ "mute_until_dt": False,
+ "last_seen_dt": member_0_last_seen_dt,
+ "new_message_separator": 0,
+ "partner_id": self.users[0].partner_id.id,
+ "rtc_inviting_session_id": False,
+ "seen_message_id": False,
+ "unpin_dt": fields.Datetime.to_string(member_0.unpin_dt),
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ if channel == self.channel_livechat_2 and guest:
+ return {
+ "create_date": fields.Datetime.to_string(member_g.create_date),
+ "last_seen_dt": fields.Datetime.to_string(member_g.last_seen_dt),
+ "fetched_message_id": last_message.id,
+ "id": member_g.id,
+ "livechat_member_type": "visitor",
+ "guest_id": guest.id,
+ "seen_message_id": last_message.id,
+ "channel_id": {"id": channel.id, "model": "discuss.channel"},
+ }
+ return {}
+
+ def _expected_result_for_livechat_channel(self):
+ return {"id": self.im_livechat_channel.id, "name": "support"}
+
+ def _expected_result_for_message(self, channel):
+ last_message = channel._get_last_messages()
+ create_date = fields.Datetime.to_string(last_message.create_date)
+ date = fields.Datetime.to_string(last_message.date)
+ write_date = fields.Datetime.to_string(last_message.write_date)
+ user_0 = self.users[0]
+ user_1 = self.users[1]
+ user_2 = self.users[2]
+ members = channel.channel_member_ids
+ member_g = members.filtered(lambda m: m.guest_id)
+ guest = member_g.guest_id
+ if channel == self.channel_general:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": user_0.partner_id.id,
+ "body": ["markup", "test
"],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "general",
+ "email_from": '"Ernest Employee" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "comment",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [
+ {"content": "👍", "message": last_message.id},
+ {"content": "😁", "message": last_message.id},
+ {"content": "😊", "message": last_message.id},
+ ],
+ "record_name": "general",
+ "res_id": 1,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_note").id,
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_channel_public_1:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": user_2.partner_id.id,
+ "body": ["markup", "test
"],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "public channel 1",
+ "email_from": '"test2" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "comment",
+ "model": "discuss.channel",
+ "needaction": True,
+ "notification_ids": [last_message.notification_ids.id],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [self.users[0].partner_id.id],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [
+ {"content": "😁", "message": last_message.id},
+ {"content": "😊", "message": last_message.id},
+ {"content": "😏", "message": last_message.id},
+ ],
+ "record_name": "public channel 1",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": True,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_note").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_channel_public_2:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": user_0.partner_id.id,
+ "body": [
+ "markup",
+ 'created this channel.
',
+ ],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "public channel 2",
+ "email_from": '"Ernest Employee" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "notification",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [],
+ "record_name": "public channel 2",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_comment").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_channel_group_1:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": self.user_root.partner_id.id,
+ "body": [
+ "markup",
+ '',
+ ],
+ "call_history_ids": [channel.call_history_ids[0].id],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "group restricted channel 1",
+ "email_from": '"OdooBot" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "notification",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [],
+ "record_name": "group restricted channel 1",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_note").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_channel_group_2:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": user_0.partner_id.id,
+ "body": [
+ "markup",
+ 'created this channel.
',
+ ],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "group restricted channel 2",
+ "email_from": '"Ernest Employee" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "notification",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [],
+ "record_name": "group restricted channel 2",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_comment").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_livechat_1:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": False,
+ "author_id": user_1.partner_id.id,
+ "body": ["markup", "test
"],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "test1 Ernest Employee",
+ "email_from": '"test1" ',
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "comment",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [],
+ "record_name": "test1 Ernest Employee",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_note").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ if channel == self.channel_livechat_2:
+ return {
+ "attachment_ids": [],
+ "author_guest_id": guest.id,
+ "author_id": False,
+ "body": ["markup", "test
"],
+ "create_date": create_date,
+ "date": date,
+ "default_subject": "Visitor Ernest Employee",
+ "email_from": False,
+ "id": last_message.id,
+ "incoming_email_cc": False,
+ "incoming_email_to": False,
+ "message_link_preview_ids": [],
+ "message_type": "comment",
+ "model": "discuss.channel",
+ "needaction": False,
+ "notification_ids": [],
+ "thread": {"id": channel.id, "model": "discuss.channel"},
+ "parent_id": False,
+ "partner_ids": [],
+ "pinned_at": False,
+ "rating_id": False,
+ "reactions": [],
+ "record_name": "Visitor Ernest Employee",
+ "res_id": channel.id,
+ "scheduledDatetime": False,
+ "starred": False,
+ "subject": False,
+ "subtype_id": self.env.ref("mail.mt_note").id,
+ "trackingValues": [],
+ "write_date": write_date,
+ }
+ return {}
+
+ def _expected_result_for_message_reactions(self, channel):
+ last_message = channel._get_last_messages()
+ partner_0 = self.users[0].partner_id.id
+ partner_1 = self.users[1].partner_id.id
+ partner_2 = self.users[2].partner_id.id
+ reactions_0 = last_message.sudo().reaction_ids.filtered(lambda r: r.content == "👍")
+ reactions_1 = last_message.sudo().reaction_ids.filtered(lambda r: r.content == "😁")
+ reactions_2 = last_message.sudo().reaction_ids.filtered(lambda r: r.content == "😊")
+ reactions_3 = last_message.sudo().reaction_ids.filtered(lambda r: r.content == "😏")
+ if channel == self.channel_general:
+ return [
+ {
+ "content": "👍",
+ "count": 1,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_2],
+ "sequence": min(reactions_0.ids),
+ },
+ {
+ "content": "😁",
+ "count": 2,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_2, partner_1],
+ "sequence": min(reactions_1.ids),
+ },
+ {
+ "content": "😊",
+ "count": 3,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_2, partner_1, partner_0],
+ "sequence": min(reactions_2.ids),
+ },
+ ]
+ if channel == self.channel_channel_public_1:
+ return [
+ {
+ "content": "😁",
+ "count": 1,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_2],
+ "sequence": min(reactions_1.ids),
+ },
+ {
+ "content": "😊",
+ "count": 3,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_2, partner_1, partner_0],
+ "sequence": min(reactions_2.ids),
+ },
+ {
+ "content": "😏",
+ "count": 2,
+ "guests": [],
+ "message": last_message.id,
+ "partners": [partner_1, partner_0],
+ "sequence": min(reactions_3.ids),
+ },
+ ]
+ return []
+
+ def _expected_result_for_notification(self, channel):
+ last_message = channel._get_last_messages()
+ if channel == self.channel_channel_public_1:
+ return {
+ "mail_email_address": False,
+ "failure_type": False,
+ "id": last_message.notification_ids.id,
+ "mail_message_id": last_message.id,
+ "notification_status": "sent",
+ "notification_type": "inbox",
+ "res_partner_id": self.users[0].partner_id.id,
+ }
+ return {}
+
+ def _expected_result_for_persona(
+ self,
+ user=None,
+ guest=None,
+ only_inviting=False,
+ also_livechat=False,
+ also_notification=False,
+ ):
+ if user == self.users[0]:
+ res = {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": "e.e@example.com",
+ "id": user.partner_id.id,
+ "im_status": "online",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "Ernest Employee",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if also_livechat:
+ res.update(
+ {
+ "country_id": False,
+ "is_public": False,
+ "user_livechat_username": False,
+ }
+ )
+ if also_notification:
+ res["name"] = "Ernest Employee"
+ return res
+ if user == self.users[1]:
+ res = {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "country_id": self.env.ref("base.in").id,
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "is_public": False,
+ "main_user_id": user.id,
+ "name": "test1",
+ "mention_token": user.partner_id._get_mention_token(),
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if also_livechat:
+ res["offline_since"] = False
+ res["user_livechat_username"] = False
+ res["email"] = user.email
+ return res
+ if user == self.users[2]:
+ if only_inviting:
+ return {
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "name": "test2",
+ "mention_token": user.partner_id._get_mention_token(),
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ return {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": "test2@example.com",
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "test2",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if user == self.users[3]:
+ return {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": False,
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "test3",
+ "write_date": fields.Datetime.to_string(self.users[3].partner_id.write_date),
+ }
+ if user == self.users[12]:
+ return {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": False,
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "test12",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if user == self.users[14]:
+ return {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": False,
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "test14",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if user == self.users[15]:
+ return {
+ "active": True,
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "email": False,
+ "id": user.partner_id.id,
+ "im_status": "offline",
+ "im_status_access_token": user.partner_id._get_im_status_access_token(),
+ "is_company": False,
+ "main_user_id": user.id,
+ "mention_token": user.partner_id._get_mention_token(),
+ "name": "test15",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if user == self.user_root:
+ return {
+ "avatar_128_access_token": user.partner_id._get_avatar_128_access_token(),
+ "id": user.partner_id.id,
+ "is_company": False,
+ "main_user_id": user.id,
+ "name": "OdooBot",
+ "write_date": fields.Datetime.to_string(user.partner_id.write_date),
+ }
+ if guest:
+ return {
+ "avatar_128_access_token": self.guest._get_avatar_128_access_token(),
+ "country_id": self.guest.country_id.id,
+ "id": self.guest.id,
+ "im_status": "offline",
+ "im_status_access_token": self.guest._get_im_status_access_token(),
+ "name": "Visitor",
+ "offline_since": False,
+ "write_date": fields.Datetime.to_string(self.guest.write_date),
+ }
+ return {}
+
+ def _expected_result_for_rtc_session(self, channel, user):
+ members = channel.channel_member_ids
+ member_2 = members.filtered(lambda m: m.partner_id == self.users[2].partner_id)
+ if channel == self.channel_channel_group_1 and user == self.users[2]:
+ return {
+ # sudo: discuss.channel.rtc.session - reading a session in a test file
+ "channel_member_id": member_2.id,
+ "id": member_2.sudo().rtc_session_ids.id,
+ "is_camera_on": False,
+ "is_deaf": False,
+ "is_screen_sharing_on": False,
+ "is_muted": False,
+ }
+ return {}
+
+ def _expected_result_for_thread(self, channel):
+ common_data = {
+ "id": channel.id,
+ "model": "discuss.channel",
+ "module_icon": "/mail/static/description/icon.png",
+ "rating_avg": 0.0,
+ "rating_count": 0,
+ }
+ if channel == self.channel_general:
+ return {**common_data, "display_name": "general"}
+ if channel == self.channel_channel_public_1:
+ return {**common_data, "display_name": "public channel 1"}
+ if channel == self.channel_channel_public_2:
+ return {**common_data, "display_name": "public channel 2"}
+ if channel == self.channel_channel_group_1:
+ return {**common_data, "display_name": "group restricted channel 1"}
+ if channel == self.channel_channel_group_2:
+ return {**common_data, "display_name": "group restricted channel 2"}
+ if channel == self.channel_livechat_1:
+ return {**common_data, "display_name": "test1 Ernest Employee"}
+ if channel == self.channel_livechat_2:
+ return {**common_data, "display_name": "Visitor Ernest Employee"}
+ return {}
+
+ def _res_for_user(self, user):
+ partner = user.partner_id
+ if user == self.users[0]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[1]:
+ return {
+ "id": user.id,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[2]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[3]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[12]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[14]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.users[15]:
+ return {
+ "id": user.id,
+ "employee_ids": user.employee_ids.ids,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ if user == self.user_root:
+ return {
+ "id": user.id,
+ "partner_id": partner.id,
+ "share": False,
+ }
+ return {}
+
+ def _res_for_employee(self, employee):
+ return {
+ "id": employee.id,
+ "leave_date_to": False,
+ }
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance_inbox.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance_inbox.py
new file mode 100644
index 0000000..a810f56
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_performance_inbox.py
@@ -0,0 +1,82 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from itertools import chain
+
+from odoo.addons.mail.tests.common import MailCommon
+from odoo.tests.common import HttpCase, tagged, warmup
+
+
+@tagged("post_install", "-at_install", "is_query_count")
+class TestInboxPerformance(HttpCase, MailCommon):
+ @warmup
+ def test_fetch_with_rating_stats_enabled(self):
+ """
+ Computation of rating_stats should run a single query per model with rating_stats enabled.
+ """
+ # Queries (in order):
+ # - search website (get_current_website by domain)
+ # - search website (get_current_website default)
+ # - search website_rewrite (_get_rewrites) sometimes occurs depending on the routing cache
+ # - insert res_device_log
+ # - _xmlid_lookup (_get_public_users)
+ # - fetch website (_get_cached_values)
+ # - get_param ir_config_parameter (_pre_dispatch website_sale)
+ # 4 _message_fetch:
+ # 2 _search_needaction:
+ # - fetch res_users (current user)
+ # - search ir_rule (_get_rules for mail.notification)
+ # - search ir_rule (_get_rules)
+ # - search mail_message
+ # 30 message _to_store:
+ # - search mail_message_schedule
+ # - fetch mail_message
+ # - search mail_followers
+ # 2 thread _to_store:
+ # - fetch slide_channel
+ # - fetch product_template
+ # - search mail_message_res_partner_starred_rel (_compute_starred)
+ # - search message_attachment_rel
+ # - search mail_link_preview
+ # - search mail_message_reaction
+ # - search mail_message_res_partner_rel
+ # - fetch mail_message_subtype
+ # - search mail_notification
+ # 7 _filtered_for_web_client:
+ # - fetch mail_notification
+ # 4 _compute_domain:
+ # - search ir_rule (_get_rules for res.partner)
+ # - search res_groups_users_rel
+ # - search rule_group_rel
+ # - fetch ir_rule
+ # - fetch res_company
+ # - fetch res_partner
+ # 2 _compute_rating_id:
+ # - search rating_rating
+ # - fetch rating_rating
+ # - search mail_tracking_value
+ # 3 _author_to_store:
+ # - fetch res_partner
+ # - search res_users
+ # - fetch res_users
+ # - search ir_rule (_get_rules for rating.rating)
+ # - read group rating_rating (_rating_get_stats_per_record for slide.channel)
+ # - read group rating_rating (_compute_rating_stats for slide.channel)
+ # - read group rating_rating (_rating_get_stats_per_record for product.template)
+ # - read group rating_rating (_compute_rating_stats for product.template)
+ # - get_param ir_config_parameter (_save_session)
+ first_model_records = self.env["product.template"].create(
+ [{"name": "Product A1"}, {"name": "Product A2"}]
+ )
+ second_model_records = self.env["slide.channel"].create(
+ [{"name": "Course B1"}, {"name": "Course B2"}]
+ )
+ for record in chain(first_model_records, second_model_records):
+ record.message_post(
+ body=f"Test message for {record.name}
",
+ message_type="comment",
+ partner_ids=[self.user_employee.partner_id.id],
+ rating_value="4",
+ )
+ self.authenticate(self.user_employee.login, self.user_employee.password)
+ with self.assertQueryCount(43):
+ self.make_jsonrpc_request("/mail/inbox/messages")
diff --git a/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_res_partner.py b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_res_partner.py
new file mode 100644
index 0000000..12a9f1d
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_discuss_full/test_discuss_full/tests/test_res_partner.py
@@ -0,0 +1,12 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.mail.tests.common import mail_new_test_user
+from odoo.addons.mail.tests.common import MailCommon
+from odoo.addons.mail.tools.discuss import Store
+
+
+class TestResPartner(MailCommon):
+
+ def test_portal_user_store_data_access(self):
+ portal_user = mail_new_test_user(self.env, login="portal-user", groups="base.group_portal")
+ Store().add(portal_user.partner_id.with_user(self.user_employee_c2))
diff --git a/odoo-bringout-oca-ocb-test_event_full/README.md b/odoo-bringout-oca-ocb-test_event_full/README.md
index 4f537b2..3a2806d 100644
--- a/odoo-bringout-oca-ocb-test_event_full/README.md
+++ b/odoo-bringout-oca-ocb-test_event_full/README.md
@@ -14,7 +14,6 @@ pip install odoo-bringout-oca-ocb-test_event_full
## Dependencies
-This addon depends on:
- event
- event_booth
- event_crm
@@ -23,43 +22,18 @@ This addon depends on:
- event_sms
- payment_demo
- website_event_booth_sale_exhibitor
-- website_event_crm_questions
- website_event_exhibitor
-- website_event_questions
-- website_event_meet
- website_event_sale
- website_event_track
- website_event_track_live
- website_event_track_quiz
-## Manifest Information
-
-- **Name**: Test Full Event Flow
-- **Version**: 1.0
-- **Category**: Hidden/Tests
-- **License**: LGPL-3
-- **Installable**: False
-
## Source
-Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_event_full`.
+- Repository: https://github.com/OCA/OCB
+- Branch: 19.0
+- Path: addons/test_event_full
## 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
+This package preserves the original LGPL-3 license.
diff --git a/odoo-bringout-oca-ocb-test_event_full/pyproject.toml b/odoo-bringout-oca-ocb-test_event_full/pyproject.toml
index b12e81e..ddbccfb 100644
--- a/odoo-bringout-oca-ocb-test_event_full/pyproject.toml
+++ b/odoo-bringout-oca-ocb-test_event_full/pyproject.toml
@@ -1,27 +1,26 @@
[project]
name = "odoo-bringout-oca-ocb-test_event_full"
version = "16.0.0"
-description = "Test Full Event Flow - Odoo addon"
+description = "Test Full Event Flow -
+ Odoo addon
+ "
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
- "odoo-bringout-oca-ocb-event>=16.0.0",
- "odoo-bringout-oca-ocb-event_booth>=16.0.0",
- "odoo-bringout-oca-ocb-event_crm>=16.0.0",
- "odoo-bringout-oca-ocb-event_crm_sale>=16.0.0",
- "odoo-bringout-oca-ocb-event_sale>=16.0.0",
- "odoo-bringout-oca-ocb-event_sms>=16.0.0",
- "odoo-bringout-oca-ocb-payment_demo>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_booth_sale_exhibitor>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_crm_questions>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_exhibitor>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_questions>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_meet>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_sale>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_track>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_track_live>=16.0.0",
- "odoo-bringout-oca-ocb-website_event_track_quiz>=16.0.0",
+ "odoo-bringout-oca-ocb-event>=19.0.0",
+ "odoo-bringout-oca-ocb-event_booth>=19.0.0",
+ "odoo-bringout-oca-ocb-event_crm>=19.0.0",
+ "odoo-bringout-oca-ocb-event_crm_sale>=19.0.0",
+ "odoo-bringout-oca-ocb-event_sale>=19.0.0",
+ "odoo-bringout-oca-ocb-event_sms>=19.0.0",
+ "TODO_MAP-payment_demo>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_booth_sale_exhibitor>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_exhibitor>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_sale>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_track>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_track_live>=19.0.0",
+ "odoo-bringout-oca-ocb-website_event_track_quiz>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@@ -31,7 +30,7 @@ classifiers = [
"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.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/__manifest__.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/__manifest__.py
index 82d233c..fe828fc 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/__manifest__.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/__manifest__.py
@@ -19,10 +19,7 @@ automatic lead generation, full Online support, ...
'event_sms',
'payment_demo',
'website_event_booth_sale_exhibitor',
- 'website_event_crm_questions',
'website_event_exhibitor',
- 'website_event_questions',
- 'website_event_meet',
'website_event_sale',
'website_event_track',
'website_event_track_live',
@@ -35,8 +32,12 @@ automatic lead generation, full Online support, ...
],
'assets': {
'web.assets_tests': [
- 'test_event_full/static/**/*',
+ 'test_event_full/static/src/js/tours/*',
+ ],
+ 'web.assets_unit_tests': [
+ 'test_event_full/static/src/js/tests/*',
],
},
+ 'author': 'Odoo S.A.',
'license': 'LGPL-3',
}
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/data/event_type_data.xml b/odoo-bringout-oca-ocb-test_event_full/test_event_full/data/event_type_data.xml
deleted file mode 100644
index 4ebf4a2..0000000
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/data/event_type_data.xml
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
-
- Standard
- Standard
-
-
-
- Premium
- Premium
-
- 90
-
-
-
-
- Europe/Paris
-
-
-
-
- Test Type
- Template note
-
- 30
-
- Ticket Instructions
-
-
-
-
- simple_choice
-
-
- Question1
-
-
- Q1-Answer1
- 1
-
-
-
- Q1-Answer2
- 2
-
-
-
- simple_choice
-
-
- Question2
-
-
- Q2-Answer1
- 1
-
-
-
- Q2-Answer2
- 2
-
-
-
- text_box
-
-
- Question3
-
-
-
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tests/test_template_reference_field_widget.test.js b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tests/test_template_reference_field_widget.test.js
new file mode 100644
index 0000000..c6b85bd
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tests/test_template_reference_field_widget.test.js
@@ -0,0 +1,124 @@
+import { defineMailModels } from "@mail/../tests/mail_test_helpers";
+import { describe, expect, test } from "@odoo/hoot";
+import { click, select } from "@odoo/hoot-dom";
+import { animationFrame } from "@odoo/hoot-mock";
+import { defineModels, fields, models, mountView, onRpc } from "@web/../tests/web_test_helpers";
+
+class EventMail extends models.Model {
+ _name = "event.mail";
+
+ template_ref = fields.Reference({
+ selection: [
+ ["mail.template", "Mail Template"],
+ ["sms.template", "SMS Template"],
+ ["some.template", "Some Template"],
+ ],
+ });
+
+ _records = [
+ {
+ id: 1,
+ template_ref: "mail.template,1",
+ },
+ {
+ id: 2,
+ template_ref: "sms.template,1",
+ },
+ {
+ id: 3,
+ template_ref: "some.template,1",
+ },
+ ];
+}
+class MailTemplate extends models.Model {
+ _name = "mail.template";
+
+ name = fields.Char();
+
+ _records = [{ id: 1, name: "Mail Template 1" }];
+}
+class SmsTemplate extends models.Model {
+ _name = "sms.template";
+
+ name = fields.Char();
+
+ _records = [{ id: 1, name: "SMS template 1" }];
+}
+class SomeTemplate extends models.Model {
+ _name = "some.template";
+
+ name = fields.Char();
+
+ _records = [{ id: 1, name: "Some Template 1" }];
+}
+defineMailModels();
+defineModels([EventMail, MailTemplate, SmsTemplate, SomeTemplate]);
+
+describe.current.tags("desktop");
+
+test("Reference field displays right icons", async () => {
+ // bypass list controller check
+ onRpc("has_group", () => true);
+
+ await mountView({
+ type: "list",
+ resModel: "event.mail",
+ arch: `
+
+
+
`,
+ });
+
+ // each field cell will be the field of a different record (1 field/line)
+ expect(".o_field_cell").toHaveCount(3);
+ expect(".o_field_cell.o_EventMailTemplateReferenceField_cell").toHaveCount(3);
+ expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(1);
+ expect(".o_field_cell:eq(1) .fa-mobile").toHaveCount(1);
+ expect(".o_field_cell:eq(2) .fa-envelope").toHaveCount(0);
+ expect(".o_field_cell:eq(2) .fa-mobile").toHaveCount(0);
+
+ // select a sms.template instead of mail.template
+
+ await click(".o_field_cell:eq(0)");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) select.o_input");
+ await select("sms.template");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) .o_field_many2one_selection input");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) .o-autocomplete--dropdown-item");
+ // click out
+ await click(".o_list_renderer");
+ await animationFrame();
+
+ expect(".o_field_cell:eq(0) .fa-mobile").toHaveCount(1);
+ expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(0);
+
+ // select a some other model to check it has no icon
+
+ await click(".o_field_cell:eq(0)");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) select.o_input");
+ await select("some.template");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) .o_field_many2one_selection input");
+ await animationFrame();
+ await click(".o_field_cell:eq(0) .o-autocomplete--dropdown-item");
+ await click(".o_list_renderer");
+ await animationFrame();
+
+ expect(".o_field_cell:eq(0) .fa-mobile").toHaveCount(0);
+ expect(".o_field_cell:eq(0) .fa-envelope").toHaveCount(0);
+
+ // select no record for the model
+
+ await click(".o_field_cell:eq(1)");
+ await animationFrame();
+ await click(".o_field_cell:eq(1) select.o_input");
+ await select("mail.template");
+ await click(".o_list_renderer");
+ await animationFrame();
+
+ expect(".o_field_cell:eq(1) .fa-mobile").toHaveCount(0);
+ expect(".o_field_cell:eq(1) .fa-envelope").toHaveCount(0);
+});
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_performance_tour.js b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_performance_tour.js
index 290d581..d48e813 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_performance_tour.js
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_performance_tour.js
@@ -1,85 +1,76 @@
-odoo.define('test_event_full.tour.performance', function (require) {
-"use strict";
-
-var tour = require('web_tour.tour');
+import { registry } from "@web/core/registry";
+import * as wsTourUtils from '@website_sale/js/tours/tour_utils';
var registerSteps = [{
- content: "Select 2 units of 'Ticket1' ticket type",
- trigger: '#o_wevent_tickets_collapse .row.o_wevent_ticket_selector[name="Ticket1"] select',
- run: 'text 2',
+ content: "Open ticket modal",
+ trigger: 'button.btn-primary:contains("Register")',
+ run: "click",
}, {
- content: "Select 1 unit of 'Ticket2' ticket type",
- trigger: '#o_wevent_tickets_collapse .row.o_wevent_ticket_selector[name="Ticket2"] select',
- run: 'text 1',
+ content: "Add 2 units of 'Ticket1' ticket type by clicking the '+' button",
+ trigger: 'button[data-increment-type*="plus"]',
+ run: "dblclick",
+}, {
+ content: "Edit 1 unit of 'Ticket2' ticket type",
+ trigger: '.modal input:eq(2)',
+ run: "edit 1",
}, {
content: "Click on 'Register' button",
trigger: '#o_wevent_tickets .btn-primary:contains("Register"):not(:disabled)',
run: 'click',
}, {
- content: "Fill attendees details",
- trigger: 'form[id="attendee_registration"] .btn:contains("Continue")',
- run: function () {
- $("input[name='1-name']").val("Raoulette Poiluchette");
- $("input[name='1-phone']").val("0456112233");
- $("input[name='1-email']").val("raoulette@example.com");
- $("div[name*='Question1'] select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Q1-Answer2')").val());
- $("div[name*='Question2'] select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Q2-Answer1')").val());
- $("input[name='2-name']").val("Michel Tractopelle");
- $("input[name='2-phone']").val("0456332211");
- $("input[name='2-email']").val("michel@example.com");
- $("div[name*='Question1'] select[name*='question_answer-2']").val($("select[name*='question_answer-2'] option:contains('Q1-Answer1')").val());
- $("div[name*='Question2'] select[name*='question_answer-2']").val($("select[name*='question_answer-2'] option:contains('Q2-Answer2')").val());
- $("input[name='3-name']").val("Hubert Boitaclous");
- $("input[name='3-phone']").val("0456995511");
- $("input[name='3-email']").val("hubert@example.com");
- $("div[name*='Question1'] select[name*='question_answer-3']").val($("select[name*='question_answer-3'] option:contains('Q1-Answer2')").val());
- $("div[name*='Question2'] select[name*='question_answer-3']").val($("select[name*='question_answer-3'] option:contains('Q2-Answer2')").val());
- $("textarea[name*='question_answer']").text("Random answer from random guy");
- },
+ content: "Choose the 'Q1-Answer2' answer of the 'Question1' for the first ticket",
+ trigger: 'select[name*="1-simple_choice"]',
+ run: "selectByIndex 2",
+}, {
+ content: "Choose the 'Q2-Answer1' answer of the 'Question2' for the first ticket",
+ trigger: 'select[name*="1-simple_choice"]:last',
+ run: "selectByIndex 1",
+}, {
+ content: "Choose the 'Q1-Answer1' answer of the 'Question1' for the second ticket",
+ trigger: 'select[name*="2-simple_choice"]',
+ run: "selectByIndex 1",
+}, {
+ content: "Choose the 'Q2-Answer2' answer of the 'Question2' for the second ticket",
+ trigger: 'select[name*="2-simple_choice"]:last',
+ run: "selectByIndex 2",
+}, {
+ content: "Choose the 'Q1-Answer2' answer of the 'Question1' for the third ticket",
+ trigger: 'select[name*="3-simple_choice"]',
+ run: "selectByIndex 2",
+}, {
+ content: "Choose the 'Q2-Answer2' answer of the 'Question2' for the third ticket",
+ trigger: 'select[name*="3-simple_choice"]:last',
+ run: "selectByIndex 2",
+}, {
+ content: "Fill the text content of the 'Question3' for the third ticket",
+ trigger: 'textarea[name*="text_box"]',
+ run: "edit Random answer from random guy",
}, {
content: "Validate attendees details",
- extra_trigger: "input[name='1-name'], input[name='2-name'], input[name='3-name']",
- trigger: 'button:contains("Continue")',
+ trigger: 'button[type=submit]:last',
run: 'click',
-}, {
- content: "Address filling",
- trigger: 'select[name="country_id"]',
- run: function () {
- $('input[name="name"]').val('Raoulette Poiluchette');
- $('input[name="phone"]').val('0456112233');
- $('input[name="email"]').val('raoulette@example.com');
- $('input[name="street"]').val('Cheesy Crust Street, 42');
- $('input[name="city"]').val('CheeseCity');
- $('input[name="zip"]').val('8888');
- $('#country_id option:eq(1)').attr('selected', true);
- },
-}, {
- content: "Next",
- trigger: '.oe_cart .btn:contains("Next")',
-}, {
- content: 'Select Test payment provider',
- trigger: '.o_payment_option_card:contains("Demo")'
-}, {
- content: 'Add card number',
- trigger: 'input[name="customer_input"]',
- run: 'text 4242424242424242'
-}, {
- content: "Pay now",
- extra_trigger: "#cart_products:contains(Ticket1):contains(Ticket2)",
- trigger: 'button:contains(Pay Now)',
- run: 'click',
-}, {
- content: 'Payment is successful',
- trigger: '.oe_website_sale_tx_status:contains("Your payment has been successfully processed.")',
- run: function () {}
-}];
+ expectUnloadPage: true,
+},
+...wsTourUtils.fillAdressForm({
+ name: "Raoulette Poiluchette",
+ phone: "0456112233",
+ email: "raoulette@example.com",
+ street: "Cheesy Crust Street, 42",
+ city: "CheeseCity",
+ zip: "8888",
+}),
+{
+ content: "Confirm address",
+ trigger: 'a[name="website_sale_main_button"]',
+ run: "click",
+ expectUnloadPage: true,
+},
+...wsTourUtils.payWithDemo(),
+];
-tour.register('wevent_performance_register', {
- test: true
-}, [].concat(
+registry.category("web_tour.tours").add('wevent_performance_register', {
+ steps: () => [].concat(
registerSteps,
)
-);
-
});
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_register_tour.js b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_register_tour.js
index ee7a1ad..a8e9e83 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_register_tour.js
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/static/src/js/tours/wevent_register_tour.js
@@ -1,18 +1,57 @@
-odoo.define('test_event_full.tour.register', function (require) {
-"use strict";
-
-var tour = require('web_tour.tour');
+import { registry } from "@web/core/registry";
+import { session } from "@web/session";
/**
* TALKS STEPS
*/
+const reminderToggleSteps = function (talkName, reminderOn, toggleReminder) {
+ let steps = [];
+ if (reminderOn) {
+ steps = steps.concat([{
+ content: `Check Favorite for ${talkName} was already on`,
+ trigger: "div.o_wetrack_js_reminder i.fa-bell",
+ }]);
+ }
+ else {
+ steps = steps.concat([{
+ content: `Check Favorite for ${talkName} was off`,
+ trigger: "div.o_wetrack_js_reminder i.fa-bell-o",
+ }]);
+ if (toggleReminder) {
+ steps = steps.concat([{
+ content: "Set Favorite",
+ trigger: "i[title='Set Favorite']",
+ run: "click",
+ }]);
+ if (session.is_public){
+ steps = steps.concat([{
+ content: "The form of the email reminder modal is filled",
+ trigger: "#o_wetrack_email_reminder_form input[name='email']",
+ run: "fill visitor@odoo.com",
+ },
+ {
+ content: "The form is submit",
+ trigger: "#o_wetrack_email_reminder_form button[type='submit']",
+ run: "click",
+ }]);
+ }
+ steps = steps.concat([{
+ content: `Check Favorite for ${talkName} is now on`,
+ trigger: "div.o_wetrack_js_reminder i.fa-bell",
+ }]);
+ }
+ }
+ return steps;
+};
-var discoverTalkSteps = function (talkName, fromList, reminderOn, toggleReminder) {
+const discoverTalkSteps = function (talkName, fromList, checkToggleReminder, reminderOn, toggleReminder) {
var steps;
if (fromList) {
steps = [{
content: 'Go on "' + talkName + '" talk in List',
trigger: 'a:contains("' + talkName + '")',
+ run: "click",
+ expectUnloadPage: true,
}];
}
else {
@@ -20,109 +59,98 @@ var discoverTalkSteps = function (talkName, fromList, reminderOn, toggleReminder
content: 'Click on Live Track',
trigger: 'article span:contains("' + talkName + '")',
run: 'click',
+ expectUnloadPage: true,
}];
}
steps = steps.concat([{
content: `Check we are on the "${talkName}" talk page`,
trigger: 'div.o_wesession_track_main',
- run: function () {}, // it's a check
}]);
-
- if (reminderOn) {
- steps = steps.concat([{
- content: `Check Favorite for ${talkName} was already on`,
- trigger: 'div.o_wetrack_js_reminder i.fa-bell',
- extra_trigger: 'span.o_wetrack_js_reminder_text:contains("Favorite On")',
- run: function () {}, // it's a check
- }]);
- }
- else {
- steps = steps.concat([{
- content: `Check Favorite for ${talkName} was off`,
- trigger: 'span.o_wetrack_js_reminder_text:contains("Set Favorite")',
- run: function () {}, // it's a check
- }]);
- if (toggleReminder) {
- steps = steps.concat([{
- content: "Set Favorite",
- trigger: 'span.o_wetrack_js_reminder_text',
- run: 'click',
- }, {
- content: `Check Favorite for ${talkName} is now on`,
- trigger: 'div.o_wetrack_js_reminder i.fa-bell',
- extra_trigger: 'span.o_wetrack_js_reminder_text:contains("Favorite On")',
- run: function () {}, // it's a check
- }]);
- }
+ if (checkToggleReminder){
+ steps = steps.concat(reminderToggleSteps(talkName, reminderOn, toggleReminder));
}
return steps;
};
-
-/**
- * ROOMS STEPS
- */
-
-var discoverRoomSteps = function (roomName) {
- var steps = [{
- content: 'Go on "' + roomName + '" room in List',
- trigger: 'a.o_wevent_meeting_room_card h4:contains("' + roomName + '")',
- run: function() {
- // can't click on it, it will try to launch Jitsi and fail on chrome headless
- },
- }];
- return steps;
-};
-
-
/**
* REGISTER STEPS
*/
-var registerSteps = [{
- content: 'Go on Register',
- trigger: 'a.btn-primary:contains("Register")',
-}, {
- content: "Select 2 units of 'Standard' ticket type",
- trigger: '#o_wevent_tickets_collapse .row:has(.o_wevent_registration_multi_select:contains("Free")) select',
- run: 'text 2',
-}, {
- content: "Click on 'Register' button",
- trigger: '#o_wevent_tickets .btn-primary:contains("Register"):not(:disabled)',
- run: 'click',
-}, {
- content: "Fill attendees details",
- trigger: 'form[id="attendee_registration"] .btn:contains("Continue")',
- run: function () {
- $("input[name='1-name']").val("Raoulette Poiluchette");
- $("input[name='1-phone']").val("0456112233");
- $("input[name='1-email']").val("raoulette@example.com");
- $("select[name*='question_answer-1']").val($("select[name*='question_answer-1'] option:contains('Consumers')").val());
- $("input[name='2-name']").val("Michel Tractopelle");
- $("input[name='2-phone']").val("0456332211");
- $("input[name='2-email']").val("michel@example.com");
- $("select[name*='question_answer-2']").val($("select[name*='question_answer-1'] option:contains('Research')").val());
- $("textarea[name*='question_answer']").text("An unicorn told me about you. I ate it afterwards.");
+const registerSteps = [
+ {
+ content: "Open ticket modal",
+ trigger: "button.btn-primary:contains(Register):enabled",
+ run: "click",
},
-}, {
- content: "Validate attendees details",
- extra_trigger: "input[name='1-name'], input[name='2-name'], input[name='3-name']",
- trigger: 'button:contains("Continue")',
- run: 'click',
-}, {
- trigger: 'div.o_wereg_confirmed_attendees span:contains("Raoulette Poiluchette")',
- run: function () {} // check
-}, {
- trigger: 'div.o_wereg_confirmed_attendees span:contains("Michel Tractopelle")',
- run: function () {} // check
-}, {
- content: "Click on 'register favorites talks' button",
- trigger: 'a:contains("register to your favorites talks now")',
- run: 'click',
-}, {
- trigger: 'h1:contains("Book your talks")',
- run: function() {},
-}];
+ {
+ content: "Edit 2 units of 'Standard' ticket type",
+ trigger: ".modal .o_wevent_ticket_selector input",
+ run: "edit 2",
+ },
+ {
+ content: "Click on 'Register' button",
+ trigger: ".modal #o_wevent_tickets .btn-primary:contains(Register):enabled",
+ run: "click",
+ },
+ {
+ content: "Wait the modal is shown before continue",
+ trigger: ".modal.modal_shown.show form[id=attendee_registration]",
+ },
+ {
+ trigger: ".modal input[name*='1-name']",
+ run: "edit Raoulette Poiluchette",
+ },
+ {
+ trigger: ".modal input[name*='1-phone']",
+ run: "edit 0456112233",
+ },
+ {
+ trigger: ".modal input[name*='1-email']",
+ run: "edit raoulette@example.com",
+ },
+ {
+ trigger: ".modal select[name*='1-simple_choice']",
+ run: "selectByLabel Consumers",
+ },
+ {
+ trigger: ".modal input[name*='2-name']",
+ run: "edit Michel Tractopelle",
+ },
+ {
+ trigger: ".modal input[name*='2-phone']",
+ run: "edit 0456332211",
+ },
+ {
+ trigger: ".modal input[name*='2-email']",
+ run: "edit michel@example.com",
+ },
+ {
+ trigger: ".modal select[name*='2-simple_choice']",
+ run: "selectByLabel Research",
+ },
+ {
+ trigger: ".modal textarea[name*='text_box']",
+ run: "edit An unicorn told me about you. I ate it afterwards.",
+ },
+ {
+ trigger: ".modal input[name*='1-name'], input[name*='2-name'], input[name*='3-name']",
+ },
+ {
+ content: "Validate attendees details",
+ trigger: ".modal button[type=submit]:enabled",
+ run: "click",
+ expectUnloadPage: true,
+ },
+ {
+ content: "Click on 'register favorites talks' button",
+ trigger: "a:contains(register to your favorites talks now)",
+ run: "click",
+ expectUnloadPage: true,
+ },
+ {
+ trigger: "h5:contains(Book your talks)",
+ },
+];
/**
* MAIN STEPS
@@ -132,43 +160,46 @@ var initTourSteps = function (eventName) {
return [{
content: 'Go on "' + eventName + '" page',
trigger: 'a[href*="/event"]:contains("' + eventName + '"):first',
+ run: "click",
+ expectUnloadPage: true,
}];
};
var browseTalksSteps = [{
- content: 'Browse Talks',
- trigger: 'a:contains("Talks")',
+ content: 'Browse Talks Menu',
+ trigger: 'a[href*="#"]:contains("Talks")',
+ run: "click",
+}, {
+ content: 'Browse Talks Submenu',
+ trigger: 'a.dropdown-item span:contains("Talks")',
+ run: "click",
+ expectUnloadPage: true,
}, {
content: 'Check we are on the talk list page',
- trigger: 'h1:contains("Book your talks")',
- run: function () {} // check
+ trigger: 'h5:contains("Book your talks")',
}];
-var browseMeetSteps = [{
- content: 'Browse Meet',
- trigger: 'a:contains("Community")',
+var browseBackSteps = [{
+ content: 'Browse Back',
+ trigger: 'a:contains("All Talks")',
+ run: "click",
+ expectUnloadPage: true,
}, {
- content: 'Check we are on the community page',
- trigger: 'span:contains("Join a room")',
- run: function () {} // check
+ content: 'Check we are back on the talk list page',
+ trigger: 'h5:contains("Book your talks")',
}];
-
-tour.register('wevent_register', {
+registry.category("web_tour.tours").add('wevent_register', {
url: '/event',
- test: true
-}, [].concat(
+ steps: () => [].concat(
initTourSteps('Online Reveal'),
browseTalksSteps,
- discoverTalkSteps('What This Event Is All About', true, true),
- browseTalksSteps,
- discoverTalkSteps('Live Testimonial', false, false, false),
- browseTalksSteps,
- discoverTalkSteps('Our Last Day Together !', true, false, true),
- browseMeetSteps,
- discoverRoomSteps('Best wood for furniture'),
+ discoverTalkSteps('What This Event Is All About', true, true, true),
+ browseBackSteps,
+ discoverTalkSteps('Live Testimonial', false, false, false, false),
+ browseBackSteps,
+ discoverTalkSteps('Our Last Day Together!', true, true, false, true),
+ browseBackSteps,
registerSteps,
)
-);
-
});
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/__init__.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/__init__.py
index 94b6abc..8469ebf 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/__init__.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/__init__.py
@@ -7,5 +7,5 @@ from . import test_event_event
from . import test_event_mail
from . import test_event_security
from . import test_performance
+from . import test_wevent_menu
from . import test_wevent_register
-from . import test_event_discount
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/common.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/common.py
index 9107fb5..be6d551 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/common.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/common.py
@@ -3,10 +3,14 @@
from datetime import datetime, timedelta, time
+from odoo import Command
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
+from odoo.addons.base.tests.test_ir_cron import CronMixinCase
+from odoo.addons.event.tests.common import EventCase
from odoo.addons.event_crm.tests.common import EventCrmCase
-from odoo.addons.mail.tests.common import mail_new_test_user
+from odoo.addons.mail.tests.common import mail_new_test_user, MailCase
from odoo.addons.sales_team.tests.common import TestSalesCommon
+from odoo.addons.sms.tests.common import SMSCase
from odoo.addons.website.tests.test_website_visitor import MockVisitor
@@ -15,7 +19,6 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
@classmethod
def setUpClass(cls):
super(TestEventFullCommon, cls).setUpClass()
- cls._init_mail_gateway()
# Context data: dates
# ------------------------------------------------------------
@@ -37,6 +40,7 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
# set country in order to format Belgian numbers
cls.company_admin.write({
'country_id': cls.env.ref('base.be').id,
+ 'email': 'info@yourcompany.com',
})
cls.event_user = mail_new_test_user(
cls.env,
@@ -55,7 +59,6 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
'country_id': cls.env.ref('base.be').id,
'email': 'customer.test@example.com',
'name': 'Test Customer',
- 'mobile': '0456123456',
'phone': '0456123456',
})
# make a SO for a customer, selling some tickets
@@ -68,14 +71,16 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
cls.ticket_product = cls.env['product.product'].create({
'description_sale': 'Ticket Product Description',
- 'detailed_type': 'event',
+ 'type': 'service',
+ 'service_tracking': 'event',
'list_price': 10,
'name': 'Test Registration Product',
'standard_price': 30.0,
})
cls.booth_product = cls.env['product.product'].create({
'description_sale': 'Booth Product Description',
- 'detailed_type': 'event_booth',
+ 'type': 'service',
+ 'service_tracking': 'event_booth',
'list_price': 20,
'name': 'Test Booth Product',
'standard_price': 60.0,
@@ -120,9 +125,8 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
# ------------------------------------------------------------
test_registration_report = cls.env.ref('test_event_full.event_registration_report_test')
subscription_template = cls.env.ref('event.event_subscription')
- subscription_template.write({'report_template': test_registration_report.id})
+ subscription_template.write({'report_template_ids': [(6, 0, test_registration_report.ids)]})
cls.test_event_type = cls.env['event.type'].create({
- 'auto_confirm': True,
'default_timezone': 'Europe/Paris',
'event_type_booth_ids': [
(0, 0, {'booth_category_id': cls.event_booth_categories[0].id,
@@ -145,21 +149,18 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
'event_type_mail_ids': [
(0, 0, {'interval_unit': 'now', # right at subscription
'interval_type': 'after_sub',
- 'notification_type': 'mail',
'template_ref': 'mail.template,%i' % subscription_template.id,
}
),
(0, 0, {'interval_nbr': 1, # 1 days before event
'interval_unit': 'days',
'interval_type': 'before_event',
- 'notification_type': 'mail',
'template_ref': 'mail.template,%i' % cls.env['ir.model.data']._xmlid_to_res_id('event.event_reminder'),
}
),
(0, 0, {'interval_nbr': 1, # 1 days after event
'interval_unit': 'days',
'interval_type': 'after_event',
- 'notification_type': 'sms',
'template_ref': 'sms.template,%i' % cls.env['ir.model.data']._xmlid_to_res_id('event_sms.sms_template_data_event_reminder'),
}
),
@@ -228,15 +229,15 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
'is_published': True,
}
- cls.test_event = cls.env['event.event'].create({
- 'name': 'Test Event',
- 'auto_confirm': True,
- 'date_begin': datetime.now() + timedelta(days=1),
- 'date_end': datetime.now() + timedelta(days=5),
- 'date_tz': 'Europe/Brussels',
- 'event_type_id': cls.test_event_type.id,
- 'is_published': True,
- })
+ with cls.mock_datetime_and_now(cls, cls.reference_now):
+ cls.test_event = cls.env['event.event'].create({
+ 'name': 'Test Event',
+ 'date_begin': datetime.now() + timedelta(days=1),
+ 'date_end': datetime.now() + timedelta(days=5),
+ 'date_tz': 'Europe/Brussels',
+ 'event_type_id': cls.test_event_type.id,
+ 'is_published': True,
+ })
# update post-synchronize data
ticket_1 = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Ticket1')
ticket_2 = cls.test_event.event_ticket_ids.filtered(lambda t: t.name == 'Ticket2')
@@ -251,39 +252,36 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
], limit=1)
cls.customer_data = [
- {'email': 'customer.email.%02d@test.example.com' % x,
- 'name': 'My Customer %02d' % x,
- 'mobile': '04569999%02d' % x,
+ {'email': f'customer.email.{idx:02d}@test.example.com',
+ 'name': f'My Customer {idx:02d}',
'partner_id': False,
- 'phone': '04560000%02d' % x,
- } for x in range(0, 10)
+ 'phone': f'04560000{idx:02d}',
+ } for idx in range(0, 10)
]
cls.website_customer_data = [
- {'email': 'website.email.%02d@test.example.com' % x,
- 'name': 'My Customer %02d' % x,
- 'mobile': '04569999%02d' % x,
+ {'email': f'website.email.{idx:02d}@test.example.com',
+ 'name': f'My Customer {idx:02d}',
'partner_id': cls.env.ref('base.public_partner').id,
- 'phone': '04560000%02d' % x,
+ 'phone': f'04560000{idx:02d}',
'registration_answer_ids': [
(0, 0, {
'question_id': cls.test_event.question_ids[0].id,
- 'value_answer_id': cls.test_event.question_ids[0].answer_ids[(x % 2)].id,
+ 'value_answer_id': cls.test_event.question_ids[0].answer_ids[(idx % 2)].id,
}), (0, 0, {
'question_id': cls.test_event.question_ids[1].id,
- 'value_answer_id': cls.test_event.question_ids[1].answer_ids[(x % 2)].id,
+ 'value_answer_id': cls.test_event.question_ids[1].answer_ids[(idx % 2)].id,
}), (0, 0, {
'question_id': cls.test_event.question_ids[2].id,
- 'value_text_box': 'CustomerAnswer%s' % x,
+ 'value_text_box': f'CustomerAnswer{idx}',
})
],
- } for x in range(0, 10)
+ } for idx in range(0, 10)
]
cls.partners = cls.env['res.partner'].create([
- {'email': 'partner.email.%02d@test.example.com' % x,
- 'name': 'PartnerCustomer',
- 'mobile': '04569999%02d' % x,
- 'phone': '04560000%02d' % x,
- } for x in range(0, 10)
+ {'email': f'partner.email.{idx:02d}@test.example.com',
+ 'name': f'PartnerCustomer {idx:02d}',
+ 'phone': f'04560000{idx:02d}',
+ } for idx in range(0, 10)
])
def assertLeadConvertion(self, rule, registrations, partner=None, **expected):
@@ -304,6 +302,98 @@ class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
self.assertIn(answer.value_text_box, lead.description) # better: check multi line
+class TestEventMailCommon(EventCase, SMSCase, MailCase, CronMixinCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ cls.event_cron_id = cls.env.ref('event.event_mail_scheduler')
+ # deactivate other schedulers to avoid messing with crons
+ cls.env['event.mail'].search([]).unlink()
+ # consider asynchronous sending as default sending
+ cls.env["ir.config_parameter"].set_param("event.event_mail_async", False)
+
+ cls.env.company.write({
+ 'email': 'info@yourcompany.example.com',
+ 'name': 'YourCompany',
+ })
+
+ # prepare SMS templates
+ cls.sms_template_sub = cls.env['sms.template'].create({
+ 'name': 'Test SMS Subscription',
+ 'model_id': cls.env.ref('event.model_event_registration').id,
+ 'body': '{{ object.event_id.organizer_id.name }} registration confirmation.',
+ 'lang': '{{ object.partner_id.lang }}'
+ })
+ cls.sms_template_rem = cls.env['sms.template'].create({
+ 'name': 'Test SMS Reminder',
+ 'model_id': cls.env.ref('event.model_event_registration').id,
+ 'body': '{{ object.event_id.organizer_id.name }} reminder',
+ 'lang': '{{ object.partner_id.lang }}'
+ })
+
+ # freeze some datetimes, and ensure more than 1D+1H before event starts
+ # to ease time-based scheduler check
+ # Since `now` is used to set the `create_date` of an event and create_date
+ # has often microseconds, we set it to ensure that the scheduler we still be
+ # launched if scheduled_date == create_date - microseconds
+ cls.reference_now = datetime(2021, 3, 20, 14, 30, 15, 123456)
+ cls.event_date_begin = datetime(2021, 3, 25, 8, 0, 0)
+ cls.event_date_end = datetime(2021, 3, 28, 18, 0, 0)
+
+ cls._setup_test_reports()
+ with cls.mock_datetime_and_now(cls, cls.reference_now):
+ cls.test_event = cls.env['event.event'].create({
+ 'name': 'TestEventMail',
+ 'user_id': cls.user_eventmanager.id,
+ 'date_begin': cls.event_date_begin,
+ 'date_end': cls.event_date_end,
+ 'event_mail_ids': [
+ (0, 0, { # right at subscription: mail
+ 'interval_unit': 'now',
+ 'interval_type': 'after_sub',
+ 'notification_type': 'mail',
+ 'template_ref': f'mail.template,{cls.template_subscription.id}',
+ }),
+ (0, 0, { # right at subscription: sms
+ 'interval_unit': 'now',
+ 'interval_type': 'after_sub',
+ 'notification_type': 'sms',
+ 'template_ref': f'sms.template,{cls.sms_template_sub.id}',
+ }),
+ (0, 0, { # 3 days before event: mail
+ 'interval_nbr': 3,
+ 'interval_unit': 'days',
+ 'interval_type': 'before_event',
+ 'notification_type': 'mail',
+ 'template_ref': f'mail.template,{cls.template_reminder.id}',
+ }),
+ (0, 0, { # 3 days before event: SMS
+ 'interval_nbr': 3,
+ 'interval_unit': 'days',
+ 'interval_type': 'before_event',
+ 'notification_type': 'sms',
+ 'template_ref': f'sms.template,{cls.sms_template_rem.id}',
+ }),
+ (0, 0, { # 1h after event: mail
+ 'interval_nbr': 1,
+ 'interval_unit': 'hours',
+ 'interval_type': 'after_event',
+ 'notification_type': 'mail',
+ 'template_ref': f'mail.template,{cls.template_reminder.id}',
+ }),
+ (0, 0, { # 1h after event: SMS
+ 'interval_nbr': 1,
+ 'interval_unit': 'hours',
+ 'interval_type': 'after_event',
+ 'notification_type': 'sms',
+ 'template_ref': f'sms.template,{cls.sms_template_rem.id}',
+ }),
+ ],
+ })
+
+
class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor):
def setUp(self):
@@ -322,7 +412,8 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'description_sale': 'Mighty Description',
'list_price': 10,
'standard_price': 30.0,
- 'detailed_type': 'event',
+ 'type': 'service',
+ 'service_tracking': 'event',
})
self.event_tag_category_1 = self.env['event.tag.category'].create({
@@ -336,13 +427,12 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'color': 8,
})
self.env['event.event'].search(
- [('name', 'like', '%Online Reveal%')]
+ [('name', 'like', 'Online Reveal')]
).write(
{'name': 'Do not click on me'}
)
self.event = self.env['event.event'].create({
'name': 'Online Reveal TestEvent',
- 'auto_confirm': True,
'stage_id': self.env.ref('event.event_stage_booked').id,
'address_id': False,
'user_id': self.user_demo.id,
@@ -377,7 +467,6 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'email': 'constantin@test.example.com',
'country_id': self.env.ref('base.be').id,
'phone': '0485112233',
- 'mobile': False,
})
self.event_speaker = self.env['res.partner'].create({
'name': 'Brandon Freeman',
@@ -392,7 +481,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
self.event_question_1 = self.env['event.question'].create({
'title': 'Which field are you working in',
'question_type': 'simple_choice',
- 'event_id': self.event.id,
+ 'event_ids': [Command.set(self.event.ids)],
'once_per_order': False,
'answer_ids': [
(0, 0, {'name': 'Consumers'}),
@@ -403,7 +492,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
self.event_question_2 = self.env['event.question'].create({
'title': 'How did you hear about us ?',
'question_type': 'text_box',
- 'event_id': self.event.id,
+ 'event_ids': [Command.set(self.event.ids)],
'once_per_order': True,
})
@@ -421,6 +510,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'wishlisted_by_default': True,
'user_id': self.user_admin.id,
'partner_id': self.event_speaker.id,
+ 'description': 'Performance of Raoul Grosbedon.'
})
self.track_1 = self.env['event.track'].create({
'name': 'Live Testimonial',
@@ -431,9 +521,10 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'is_published': True,
'user_id': self.user_admin.id,
'partner_id': self.event_speaker.id,
+ 'description': 'Description of the live.'
})
self.track_2 = self.env['event.track'].create({
- 'name': 'Our Last Day Together !',
+ 'name': 'Our Last Day Together!',
'event_id': self.event.id,
'stage_id': self.env.ref('website_event_track.event_track_stage3').id,
'date': self.reference_now + timedelta(days=1),
@@ -441,22 +532,7 @@ class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor
'is_published': True,
'user_id': self.user_admin.id,
'partner_id': self.event_speaker.id,
- })
-
- # ------------------------------------------------------------
- # MEETING ROOMS
- # ----------------------------------------------------------
-
- self.env['event.meeting.room'].create({
- 'name': 'Best wood for furniture',
- 'summary': 'Let\'s talk about wood types for furniture',
- 'target_audience': 'wood expert(s)',
- 'is_pinned': True,
- 'website_published': True,
- 'event_id': self.event.id,
- 'room_lang_id': self.env.ref('base.lang_en').id,
- 'room_max_capacity': '12',
- 'room_participant_count': 9,
+ 'description': 'Description of our last day together.'
})
self.env.flush_all()
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_crm.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_crm.py
index e0cecb8..e509cbf 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_crm.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_crm.py
@@ -2,9 +2,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
-from odoo.tests import users
+from odoo.tests import tagged, users
+@tagged("event_crm")
class TestEventCrm(TestEventFullCommon):
@classmethod
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_discount.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_discount.py
deleted file mode 100644
index e6e04ad..0000000
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_discount.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import time
-
-from odoo.tests import tagged
-from odoo.fields import Command
-
-from odoo.addons.test_event_full.tests.common import TestEventFullCommon
-
-
-@tagged('post_install', '-at_install')
-class TestEventTicketPriceRounding(TestEventFullCommon):
-
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
-
- cls.ticket_product.write({
- 'lst_price': 1.0
- })
-
- cls.currency_jpy = cls.env['res.currency'].create({
- 'name': 'JPX',
- 'symbol': '¥',
- 'rounding': 1.0,
- 'rate_ids': [Command.create({'rate': 133.6200, 'name': time.strftime('%Y-%m-%d')})],
- })
-
- cls.currency_cad = cls.env['res.currency'].create({
- 'name': 'CXD',
- 'symbol': '$',
- 'rounding': 0.01,
- 'rate_ids': [Command.create({'rate': 1.338800, 'name': time.strftime('%Y-%m-%d')})],
- })
-
- cls.pricelist_usd = cls.env['product.pricelist'].create({
- 'name': 'Pricelist USD',
- 'currency_id': cls.env.ref('base.USD').id,
- })
-
- cls.pricelist_jpy = cls.env['product.pricelist'].create({
- 'name': 'Pricelist JPY',
- 'currency_id': cls.currency_jpy.id,
- })
-
- cls.pricelist_cad = cls.env['product.pricelist'].create({
- 'name': 'Pricelist CAD',
- 'currency_id': cls.currency_cad.id,
- })
-
- cls.event_type = cls.env['event.type'].create({
- 'name': 'Test Event Type',
- 'auto_confirm': True,
- 'event_type_ticket_ids': [
- (0, 0, {
- 'name': 'Test Event Ticket',
- 'product_id': cls.ticket_product.id,
- 'price': 30.0,
- })
- ],
- })
-
- cls.event_ticket = cls.event_type.event_type_ticket_ids[0]
-
- def test_no_discount_usd(self):
- ticket = self.event_ticket.with_context(pricelist=self.pricelist_usd.id)
- ticket._compute_price_reduce()
- self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the USD pricelist.")
-
- def test_no_discount_jpy(self):
- ticket = self.event_ticket.with_context(pricelist=self.pricelist_jpy.id)
- ticket._compute_price_reduce()
- self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the JPY pricelist.")
-
- def test_no_discount_cad(self):
- ticket = self.event_ticket.with_context(pricelist=self.pricelist_cad.id)
- ticket._compute_price_reduce()
- self.assertAlmostEqual(ticket.price_reduce, 30.0, places=6, msg="No discount should be applied for the CAD pricelist.")
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_event.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_event.py
index 8bb0fdb..a065259 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_event.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_event.py
@@ -36,16 +36,13 @@ class TestEventEvent(TestEventFullCommon):
# check result
self.assertEqual(event.address_id, self.env.user.company_id.partner_id)
- self.assertTrue(event.auto_confirm)
self.assertEqual(event.country_id, self.env.user.company_id.country_id)
self.assertEqual(event.date_tz, 'Europe/Paris')
self.assertEqual(event.event_booth_count, 4)
self.assertEqual(len(event.event_mail_ids), 3)
self.assertEqual(len(event.event_ticket_ids), 2)
self.assertTrue(event.introduction_menu)
- self.assertTrue(event.location_menu)
- self.assertTrue(event.menu_register_cta)
- self.assertEqual(event.message_partner_ids, self.env.user.partner_id + self.env.user.company_id.partner_id)
+ self.assertEqual(event.message_partner_ids, self.env.user.partner_id)
self.assertEqual(event.note, 'Template note
')
self.assertTrue(event.register_menu)
self.assertEqual(len(event.question_ids), 3)
@@ -75,12 +72,23 @@ class TestEventEvent(TestEventFullCommon):
self.assertTrue(event.is_ongoing)
self.assertTrue(event.event_registrations_started)
+ def test_event_kanban_state_on_stage_change(self):
+ """Test that kanban_state updates correctly when stage is changed."""
+ test_event_1 = self.env['event.event'].browse(self.test_event.ids)
+ test_event_2 = test_event_1.copy()
+
+ test_event_1.kanban_state = 'done'
+ test_event_2.kanban_state = 'cancel' # Event Cancelled
+
+ new_stage = self.env['event.stage'].create({'name': 'New Stage', 'sequence': 1})
+ (test_event_1 | test_event_2).stage_id = new_stage.id # Change event stage
+
+ self.assertEqual(test_event_1.kanban_state, 'normal', 'kanban state should reset to "normal" on stage change')
+ self.assertEqual(test_event_2.kanban_state, 'cancel', 'kanban state should not reset on stage change')
+
@freeze_time('2021-12-01 11:00:00')
@users('event_user')
def test_event_seats_and_schedulers(self):
- now = datetime.now() # used to force create_date, as sql is not wrapped by freeze gun
- self.env.cr._now = now
-
test_event = self.env['event.event'].browse(self.test_event.ids)
ticket_1 = test_event.event_ticket_ids.filtered(lambda ticket: ticket.name == 'Ticket1')
ticket_2 = test_event.event_ticket_ids.filtered(lambda ticket: ticket.name == 'Ticket2')
@@ -94,14 +102,15 @@ class TestEventEvent(TestEventFullCommon):
self.assertFalse(ticket_2.sale_available)
# make 9 registrations (let 1 on ticket)
- with self.mock_mail_gateway():
+ with self.mock_datetime_and_now(self.reference_now), \
+ self.mock_mail_gateway():
self.env['event.registration'].create([
- {'create_date': now,
- 'email': 'test.customer.%02d@test.example.com' % x,
- 'phone': '04560011%02d' % x,
- 'event_id': test_event.id,
- 'event_ticket_id': ticket_1.id,
- 'name': 'Customer %d' % x,
+ {
+ 'email': 'test.customer.%02d@test.example.com' % x,
+ 'phone': '04560011%02d' % x,
+ 'event_id': test_event.id,
+ 'event_ticket_id': ticket_1.id,
+ 'name': 'Customer %d' % x,
}
for x in range(0, 9)
])
@@ -114,27 +123,29 @@ class TestEventEvent(TestEventFullCommon):
self.assertEqual(ticket_2.seats_available, 0)
# prevent registration due to ticket limit
- with self.assertRaises(exceptions.ValidationError):
+ with self.mock_datetime_and_now(self.reference_now), \
+ self.assertRaises(exceptions.ValidationError):
self.env['event.registration'].create([
- {'create_date': now,
- 'email': 'additional.customer.%02d@test.example.com' % x,
- 'phone': '04560011%02d' % x,
- 'event_id': test_event.id,
- 'event_ticket_id': ticket_1.id,
- 'name': 'Additional Customer %d' % x,
+ {
+ 'email': 'additional.customer.%02d@test.example.com' % x,
+ 'phone': '04560011%02d' % x,
+ 'event_id': test_event.id,
+ 'event_ticket_id': ticket_1.id,
+ 'name': 'Additional Customer %d' % x,
}
for x in range(0, 2)
])
# make 20 registrations (on free ticket)
- with self.mock_mail_gateway():
+ with self.mock_datetime_and_now(self.reference_now), \
+ self.mock_mail_gateway():
self.env['event.registration'].create([
- {'create_date': now,
- 'email': 'other.customer.%02d@test.example.com' % x,
- 'phone': '04560011%02d' % x,
- 'event_id': test_event.id,
- 'event_ticket_id': ticket_2.id,
- 'name': 'Other Customer %d' % x,
+ {
+ 'email': 'other.customer.%02d@test.example.com' % x,
+ 'phone': '04560011%02d' % x,
+ 'event_id': test_event.id,
+ 'event_ticket_id': ticket_2.id,
+ 'name': 'Other Customer %d' % x,
}
for x in range(0, 20)
])
@@ -145,14 +156,15 @@ class TestEventEvent(TestEventFullCommon):
self.assertEqual(ticket_2.seats_available, 0)
# prevent registration due to event limit
- with self.assertRaises(exceptions.ValidationError):
+ with self.mock_datetime_and_now(self.reference_now), \
+ self.assertRaises(exceptions.ValidationError):
self.env['event.registration'].create([
- {'create_date': now,
- 'email': 'additional.customer.%02d@test.example.com' % x,
- 'phone': '04560011%02d' % x,
- 'event_id': test_event.id,
- 'event_ticket_id': ticket_2.id,
- 'name': 'Additional Customer %d' % x,
+ {
+ 'email': 'additional.customer.%02d@test.example.com' % x,
+ 'phone': '04560011%02d' % x,
+ 'event_id': test_event.id,
+ 'event_ticket_id': ticket_2.id,
+ 'name': 'Additional Customer %d' % x,
}
for x in range(0, 2)
])
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_mail.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_mail.py
index d3bd0c4..ba829f6 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_mail.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_mail.py
@@ -2,15 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
-from freezegun import freeze_time
+from unittest.mock import patch
-from odoo.addons.mail.tests.common import MockEmail
-from odoo.addons.sms.tests.common import MockSMS
-from odoo.addons.test_event_full.tests.common import TestWEventCommon
-from odoo.exceptions import ValidationError
-from odoo.tools import mute_logger
+from odoo.addons.test_event_full.tests.common import TestEventFullCommon, TestEventMailCommon
+from odoo.tests import tagged, users
+from odoo.tools import formataddr
-class TestTemplateRefModel(TestWEventCommon):
+
+@tagged('event_mail', 'post_install', '-at_install')
+class TestEventMailInternals(TestEventMailCommon):
def test_template_ref_delete_lines(self):
""" When deleting a template, related lines should be deleted too """
@@ -51,117 +51,303 @@ class TestTemplateRefModel(TestWEventCommon):
self.assertEqual(len(event_type.event_type_mail_ids.exists()), 0)
self.assertEqual(len(event.event_mail_ids.exists()), 0)
- def test_template_ref_model_constraint(self):
- test_cases = [
- ('mail', 'mail.template', True),
- ('mail', 'sms.template', False),
- ('sms', 'sms.template', True),
- ('sms', 'mail.template', False),
- ]
+@tagged('event_mail', 'post_install', '-at_install')
+class TestEventMailSchedule(TestEventMailCommon):
- for notification_type, template_type, valid in test_cases:
- with self.subTest(notification_type=notification_type, template_type=template_type):
- if template_type == 'mail.template':
- template = self.env[template_type].create({
- 'name': 'test template',
- 'model_id': self.env['ir.model']._get_id('event.registration'),
- })
- else:
- template = self.env[template_type].create({
- 'name': 'test template',
- 'body': 'Body Test',
- 'model_id': self.env['ir.model']._get_id('event.registration'),
- })
- if not valid:
- with self.assertRaises(ValidationError) as cm:
- self.env['event.mail'].create({
- 'event_id': self.event.id,
- 'notification_type': notification_type,
- 'interval_unit': 'now',
- 'interval_type': 'before_event',
- 'template_ref': template,
- })
- if notification_type == 'mail':
- self.assertEqual(str(cm.exception), 'The template which is referenced should be coming from mail.template model.')
- else:
- self.assertEqual(str(cm.exception), 'The template which is referenced should be coming from sms.template model.')
-
-class TestEventSmsMailSchedule(TestWEventCommon, MockEmail, MockSMS):
-
- @freeze_time('2020-07-06 12:00:00')
- @mute_logger('odoo.addons.base.models.ir_model', 'odoo.models')
def test_event_mail_before_trigger_sent_count(self):
- """ Emails are sent to both confirmed and unconfirmed attendees.
- This test checks that the count of sent emails includes the emails sent to unconfirmed ones
-
- Time in the test is frozen to simulate the following state:
-
- NOW Event Start Event End
- 12:00 13:00 14:00
- | | |
- ──────────────────────────────────────►
- | | time
- ◄─────────────────►
- 3 hours
- Trigger before event
- """
- self.sms_template_rem = self.env['sms.template'].create({
- 'name': 'Test reminder',
- 'model_id': self.env.ref('event.model_event_registration').id,
- 'body': '{{ object.event_id.organizer_id.name }} reminder',
- 'lang': '{{ object.partner_id.lang }}'
- })
- test_event = self.env['event.event'].create({
- 'name': 'TestEventMail',
- # 'user_id': self.env.ref('base.user_admin').id,
- 'auto_confirm': False,
- 'date_begin': datetime.now() + timedelta(hours=1),
- 'date_end': datetime.now() + timedelta(hours=2),
- 'event_mail_ids': [
- (0, 0, { # email 3 hours before event
- 'interval_nbr': 3,
- 'interval_unit': 'hours',
- 'interval_type': 'before_event',
- 'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')}),
- (0, 0, { # sms 3 hours before event
- 'interval_nbr': 3,
- 'interval_unit': 'hours',
- 'interval_type': 'before_event',
- 'notification_type': 'sms',
- 'template_ref': 'sms.template,%i' % self.sms_template_rem.id}),
- ]
- })
- mail_scheduler = test_event.event_mail_ids
- self.assertEqual(len(mail_scheduler), 2, 'There should be two mail schedulers. One for mail one for sms. Cannot perform test')
+ """ Emails are only sent to confirmed attendees. """
+ test_event = self.test_event
+ mail_schedulers = test_event.event_mail_ids
+ self.assertEqual(len(mail_schedulers), 6)
+ before = mail_schedulers.filtered(lambda m: m.interval_type == "before_event" and m.interval_unit == "days")
+ self.assertEqual(len(before), 2)
# Add registrations
- self.env['event.registration'].create([{
+ _dummy, _dummy, open_reg, done_reg = self.env['event.registration'].create([{
'event_id': test_event.id,
'name': 'RegistrationUnconfirmed',
'email': 'Registration@Unconfirmed.com',
+ 'phone': '1',
'state': 'draft',
}, {
'event_id': test_event.id,
'name': 'RegistrationCanceled',
'email': 'Registration@Canceled.com',
+ 'phone': '2',
'state': 'cancel',
}, {
'event_id': test_event.id,
'name': 'RegistrationConfirmed',
'email': 'Registration@Confirmed.com',
+ 'phone': '3',
'state': 'open',
+ }, {
+ 'event_id': test_event.id,
+ 'name': 'RegistrationDone',
+ 'email': 'Registration@Done.com',
+ 'phone': '4',
+ 'state': 'done',
}])
- with self.mock_mail_gateway(), self.mockSMSGateway():
- mail_scheduler.execute()
+ with self.mock_datetime_and_now(self.event_date_begin - timedelta(days=2)), \
+ self.mock_mail_gateway(), \
+ self.mockSMSGateway():
+ before.execute()
- self.assertEqual(len(self._new_mails), 2, 'Mails were not created')
- self.assertEqual(len(self._new_sms), 2, 'SMS were not created')
+ for registration in open_reg, done_reg:
+ with self.subTest(registration_state=registration.state, medium='mail'):
+ self.assertMailMailWEmails(
+ [formataddr((registration.name, registration.email.lower()))],
+ 'outgoing',
+ )
+ with self.subTest(registration_state=registration.state, medium='sms'):
+ self.assertSMS(
+ self.env['res.partner'],
+ registration.phone,
+ None,
+ )
+ self.assertEqual(len(self._new_mails), 2, 'Mails should not be sent to draft or cancel registrations')
+ self.assertEqual(len(self._new_sms), 2, 'SMS should not be sent to draft or cancel registrations')
- self.assertEqual(test_event.seats_expected, 2, 'Wrong number of expected seats (attendees)')
+ self.assertEqual(test_event.seats_taken, 2, 'Wrong number of seats_taken')
- self.assertEqual(mail_scheduler.filtered(lambda r: r.notification_type == 'mail').mail_count_done, 2,
- 'Wrong Emails Sent Count! Probably emails sent to unconfirmed attendees were not included into the Sent Count')
- self.assertEqual(mail_scheduler.filtered(lambda r: r.notification_type == 'sms').mail_count_done, 2,
- 'Wrong SMS Sent Count! Probably SMS sent to unconfirmed attendees were not included into the Sent Count')
+ for scheduler in before:
+ self.assertEqual(
+ scheduler.mail_count_done, 2,
+ 'Wrong Emails Sent Count! Probably emails sent to unconfirmed attendees were not included into the Sent Count'
+ )
+
+ @users('user_eventmanager')
+ def test_schedule_event_scalability(self):
+ """ Test scalability / iterative work on event-based schedulers """
+ test_event = self.env['event.event'].browse(self.test_event.ids)
+ registrations = self._create_registrations(test_event, 30)
+ registrations = registrations.sorted("id")
+
+ # check event-based schedulers
+ after_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_event" and s.notification_type == "mail")
+ self.assertEqual(len(after_mail), 1)
+ self.assertEqual(after_mail.mail_count_done, 0)
+ self.assertFalse(after_mail.mail_done)
+ after_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_event" and s.notification_type == "sms")
+ self.assertEqual(len(after_sms), 1)
+ self.assertEqual(after_sms.mail_count_done, 0)
+ self.assertFalse(after_sms.mail_done)
+ before_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event" and s.notification_type == "mail")
+ self.assertEqual(len(before_mail), 1)
+ self.assertEqual(before_mail.mail_count_done, 0)
+ self.assertFalse(before_mail.mail_done)
+ before_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "before_event" and s.notification_type == "sms")
+ self.assertEqual(len(before_sms), 1)
+ self.assertEqual(before_sms.mail_count_done, 0)
+ self.assertFalse(before_sms.mail_done)
+
+ # setup batch and cron limit sizes to check iterative behavior
+ batch_size, cron_limit = 5, 20
+ self.env["ir.config_parameter"].sudo().set_param("mail.batch_size", batch_size)
+ self.env["ir.config_parameter"].sudo().set_param("mail.render.cron.limit", cron_limit)
+
+ # launch before event schedulers -> all communications are sent
+ current_now = self.event_date_begin - timedelta(days=1)
+ EventMail = type(self.env['event.mail'])
+ exec_origin = EventMail._execute_event_based_for_registrations
+ with (
+ patch.object(
+ EventMail, '_execute_event_based_for_registrations', autospec=True, wraps=EventMail, side_effect=exec_origin,
+ ) as mock_exec,
+ self.mock_datetime_and_now(current_now),
+ self.mockSMSGateway(),
+ self.mock_mail_gateway(),
+ self.capture_triggers('event.event_mail_scheduler') as capture,
+ ):
+ self.event_cron_id.method_direct_trigger()
+
+ self.assertFalse(after_mail.last_registration_id)
+ self.assertEqual(after_mail.mail_count_done, 0)
+ self.assertFalse(after_mail.mail_done)
+ self.assertFalse(after_sms.last_registration_id)
+ self.assertEqual(after_sms.mail_count_done, 0)
+ self.assertFalse(after_sms.mail_done)
+ # iterative work on registrations: only 20 (cron limit) are taken into account
+ self.assertEqual(before_mail.last_registration_id, registrations[19])
+ self.assertEqual(before_mail.mail_count_done, 20)
+ self.assertFalse(before_mail.mail_done)
+ self.assertEqual(before_sms.last_registration_id, registrations[19])
+ self.assertEqual(before_sms.mail_count_done, 20)
+ self.assertFalse(before_sms.mail_done)
+ self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
+ # cron should have been triggered for the remaining registrations
+ self.assertSchedulerCronTriggers(capture, [current_now] * 2)
+
+ # relaunch to close scheduler
+ with (
+ self.mock_datetime_and_now(current_now),
+ self.mockSMSGateway(),
+ self.mock_mail_gateway(),
+ self.capture_triggers('event.event_mail_scheduler') as capture,
+ ):
+ self.event_cron_id.method_direct_trigger()
+ self.assertEqual(before_mail.last_registration_id, registrations[-1])
+ self.assertEqual(before_mail.mail_count_done, 30)
+ self.assertTrue(before_mail.mail_done)
+ self.assertEqual(before_sms.last_registration_id, registrations[-1])
+ self.assertEqual(before_sms.mail_count_done, 30)
+ self.assertTrue(before_sms.mail_done)
+ self.assertFalse(capture.records)
+
+ # launch after event schedulers -> all communications are sent
+ current_now = self.event_date_end + timedelta(hours=1)
+ with (
+ self.mock_datetime_and_now(current_now),
+ self.mockSMSGateway(),
+ self.mock_mail_gateway(),
+ self.capture_triggers('event.event_mail_scheduler') as capture,
+ ):
+ self.event_cron_id.method_direct_trigger()
+
+ # iterative work on registrations: only 20 (cron limit) are taken into account
+ self.assertEqual(after_mail.last_registration_id, registrations[19])
+ self.assertEqual(after_mail.mail_count_done, 20)
+ self.assertFalse(after_mail.mail_done)
+ self.assertEqual(after_sms.last_registration_id, registrations[19])
+ self.assertEqual(after_sms.mail_count_done, 20)
+ self.assertFalse(after_sms.mail_done)
+ self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
+ # cron should have been triggered for the remaining registrations
+ self.assertSchedulerCronTriggers(capture, [current_now] * 2)
+
+ # relaunch to close scheduler
+ with (
+ self.mock_datetime_and_now(current_now),
+ self.mockSMSGateway(),
+ self.mock_mail_gateway(),
+ self.capture_triggers('event.event_mail_scheduler') as capture,
+ ):
+ self.event_cron_id.method_direct_trigger()
+ self.assertEqual(after_mail.last_registration_id, registrations[-1])
+ self.assertEqual(after_mail.mail_count_done, 30)
+ self.assertTrue(after_mail.mail_done)
+ self.assertEqual(after_sms.last_registration_id, registrations[-1])
+ self.assertEqual(after_sms.mail_count_done, 30)
+ self.assertTrue(after_sms.mail_done)
+ self.assertFalse(capture.records)
+
+ @users('user_eventmanager')
+ def test_schedule_subscription_scalability(self):
+ """ Test scalability / iterative work on subscription-based schedulers """
+ test_event = self.env['event.event'].browse(self.test_event.ids)
+
+ sub_mail = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_sub" and s.interval_unit == "now" and s.notification_type == "mail")
+ self.assertEqual(len(sub_mail), 1)
+ self.assertEqual(sub_mail.mail_count_done, 0)
+ sub_sms = test_event.event_mail_ids.filtered(lambda s: s.interval_type == "after_sub" and s.interval_unit == "now" and s.notification_type == "sms")
+ self.assertEqual(len(sub_sms), 1)
+ self.assertEqual(sub_sms.mail_count_done, 0)
+
+ # setup batch and cron limit sizes to check iterative behavior
+ batch_size, cron_limit = 5, 20
+ self.env["ir.config_parameter"].sudo().set_param("mail.batch_size", batch_size)
+ self.env["ir.config_parameter"].sudo().set_param("mail.render.cron.limit", cron_limit)
+
+ # create registrations -> each one receives its on subscribe communication
+ EventMailRegistration = type(self.env['event.mail.registration'])
+ exec_origin = EventMailRegistration._execute_on_registrations
+ with patch.object(
+ EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin,
+ ) as mock_exec, \
+ self.mock_datetime_and_now(self.reference_now + timedelta(hours=1)), \
+ self.mockSMSGateway(), \
+ self.mock_mail_gateway(), \
+ self.capture_triggers('event.event_mail_scheduler') as capture:
+ self._create_registrations(test_event, 30)
+
+ # iterative work on registrations: only 20 (cron limit) are taken into account
+ self.assertEqual(sub_mail.mail_count_done, 20)
+ self.assertEqual(sub_sms.mail_count_done, 20)
+ self.assertEqual(mock_exec.call_count, 8, "Batch of 5 to make 20 registrations: 4 calls / scheduler")
+ # cron should have been triggered for the remaining registrations
+ self.assertSchedulerCronTriggers(capture, [self.reference_now + timedelta(hours=1)] * 2)
+
+ # iterative work on registrations, force cron to close those
+ with (
+ patch.object(
+ EventMailRegistration, '_execute_on_registrations', autospec=True, wraps=EventMailRegistration, side_effect=exec_origin,
+ ) as mock_exec,
+ self.mock_datetime_and_now(self.reference_now + timedelta(hours=1)),
+ self.mockSMSGateway(),
+ self.mock_mail_gateway(),
+ self.capture_triggers('event.event_mail_scheduler') as capture,
+ ):
+ self.event_cron_id.method_direct_trigger()
+
+ # finished sending communications
+ self.assertEqual(sub_mail.mail_count_done, 30)
+ self.assertEqual(sub_sms.mail_count_done, 30)
+ self.assertFalse(capture.records)
+ self.assertEqual(mock_exec.call_count, 4, "Batch of 5 to make 10 remaining registrations: 2 calls / scheduler")
+
+
+@tagged('event_mail', 'post_install', '-at_install')
+class TestEventSaleMail(TestEventFullCommon):
+
+ def test_event_mail_on_sale_confirmation(self):
+ """Test that a mail is sent to the customer when a sale order is confirmed."""
+ ticket = self.test_event.event_ticket_ids[0]
+ self.test_event.env.company.partner_id.email = 'test.email@test.example.com'
+ order_line_vals = {
+ "event_id": self.test_event.id,
+ "event_ticket_id": ticket.id,
+ "product_id": ticket.product_id.id,
+ "product_uom_qty": 1,
+ }
+ self.customer_so.write({"order_line": [(0, 0, order_line_vals)]})
+
+ # check sale mail configuration
+ aftersub = self.test_event.event_mail_ids.filtered(
+ lambda m: m.interval_type == "after_sub"
+ )
+ self.assertTrue(aftersub)
+ aftersub.template_ref.email_from = "{{ (object.event_id.organizer_id.email_formatted or object.event_id.user_id.email_formatted or '') }}"
+ self.assertEqual(self.test_event.organizer_id, self.test_event.env.company.partner_id)
+
+ registration = self.env["event.registration"].create(
+ {
+ **self.website_customer_data[0],
+ "partner_id": self.event_customer.id,
+ "sale_order_line_id": self.customer_so.order_line[0].id,
+ }
+ )
+ self.assertEqual(self.test_event.registration_ids, registration)
+ self.assertEqual(self.customer_so.state, "draft")
+ self.assertEqual(registration.state, "draft")
+
+ with self.mock_mail_gateway():
+ self.customer_so.action_confirm()
+ # mail send is done when writing state value, hence flushing for the test
+ registration.flush_recordset()
+ self.assertEqual(self.customer_so.state, "sale")
+ self.assertEqual(registration.state, "open")
+
+ # Ensure mails are sent to customers right after subscription
+ self.assertMailMailWRecord(
+ registration,
+ [self.event_customer.id],
+ "outgoing",
+ author=self.test_event.organizer_id,
+ fields_values={
+ "email_from": self.test_event.organizer_id.email_formatted,
+ },
+ )
+
+ def test_registration_template_body_translation(self):
+ self.env['res.lang']._activate_lang('fr_BE')
+ test_event = self.test_event
+ self.partners[0].lang = 'fr_BE'
+ self.env.ref('event.event_subscription').with_context(lang='fr_BE').body_html = 'Bonjour'
+ with self.mock_mail_gateway(mail_unlink_sent=False):
+ self.env['event.registration'].create({
+ 'event_id': test_event.id,
+ 'partner_id': self.partners[0].id
+ })
+ self.assertEqual(self._new_mails[0].body_html, "Bonjour
")
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_security.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_security.py
index e6adc58..63fb0c4 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_security.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_event_security.py
@@ -5,6 +5,7 @@ from datetime import datetime, timedelta
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
from odoo.exceptions import AccessError
+from odoo.fields import Command
from odoo.tests import tagged
from odoo.tests.common import users
from odoo.tools import mute_logger
@@ -55,7 +56,7 @@ class TestEventSecurity(TestEventFullCommon):
def test_event_access_event_registration(self):
# Event: read ok
event = self.test_event.with_user(self.env.user)
- event.read(['name', 'user_id', 'kanban_state_label'])
+ event.read(['name', 'user_id', 'kanban_state'])
# Event: read only
with self.assertRaises(AccessError):
@@ -77,7 +78,7 @@ class TestEventSecurity(TestEventFullCommon):
def test_event_access_event_user(self):
# Event
event = self.test_event.with_user(self.env.user)
- event.read(['name', 'user_id', 'kanban_state_label'])
+ event.read(['name', 'user_id', 'kanban_state'])
event.write({'name': 'New name'})
self.env['event.event'].create({
'name': 'Event',
@@ -133,7 +134,7 @@ class TestEventSecurity(TestEventFullCommon):
event_type.unlink()
# Settings access rights required to enable some features
- self.user_eventmanager.write({'groups_id': [
+ self.user_eventmanager.write({'group_ids': [
(3, self.env.ref('base.group_system').id),
(4, self.env.ref('base.group_erp_manager').id)
]})
@@ -142,6 +143,45 @@ class TestEventSecurity(TestEventFullCommon):
})
event_config.execute()
+ def test_event_question_access(self):
+ """ Check that some user groups have access to questions and answers only if they are linked to at
+ least one published event. """
+ question = self.env['event.question'].create({
+ "title": "Question",
+ "event_ids": [Command.create({
+ 'name': 'Unpublished Event',
+ 'is_published': False,
+ })]
+ })
+ answer = self.env['event.question.answer'].create({
+ "name": "Answer",
+ "question_id": question.id,
+ })
+ restricted_users = [self.user_employee, self.user_portal, self.user_public]
+ unrestricted_users = [self.user_eventmanager, self.user_eventuser]
+
+ for user in restricted_users:
+ with self.assertRaises(AccessError, msg=f'{user.name} should not have access to questions of unpublished events'):
+ question.with_user(user).read(['title'])
+ with self.assertRaises(AccessError, msg=f'{user.name} should not have access to answers of unpublished events'):
+ answer.with_user(user).read(['name'])
+
+ for user in unrestricted_users:
+ question.with_user(user).read(['title'])
+ answer.with_user(user).read(['name'])
+
+ # To check the access of user groups to questions and answers linked to at least one published event.
+ self.env['event.event'].create({
+ 'name': 'Published Event',
+ 'is_published': True,
+ 'question_ids': [Command.set(question.ids)],
+ })
+
+ # Check that all user groups have access to questions and answers linked to at least one published event.
+ for user in restricted_users + unrestricted_users:
+ question.with_user(user).read(['title'])
+ answer.with_user(user).read(['name'])
+
def test_implied_groups(self):
"""Test that the implied groups are correctly set.
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_performance.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_performance.py
index 6d4bee5..cd91e40 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_performance.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_performance.py
@@ -6,8 +6,7 @@ from freezegun import freeze_time
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
from odoo.addons.website.tests.test_performance import UtilPerf
-from odoo.tests.common import users, warmup, Form
-from odoo.tests import tagged
+from odoo.tests import Form, users, warmup, tagged
@tagged('event_performance', 'post_install', '-at_install', '-standard')
@@ -17,6 +16,9 @@ class EventPerformanceCase(TestEventFullCommon):
super(EventPerformanceCase, self).setUp()
# patch registry to simulate a ready environment
self.patch(self.env.registry, 'ready', True)
+ # we don't use mock_mail_gateway thus want to mock smtp to test the stack
+ self._mock_smtplib_connection()
+
self._flush_tracking()
def _flush_tracking(self):
@@ -52,7 +54,7 @@ class TestEventPerformance(EventPerformanceCase):
batch_size = 20
# simple without type involved + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=5368): # tef 4944 / com 4943
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=3418): # tef 3316 / com 3315
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -60,7 +62,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
- self.env['event.event'].create(event_values)
+ self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@warmup
@@ -70,7 +72,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=439): # 439
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=432): # tef 432
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -89,7 +91,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=5480): # tef 5056 / com 5055
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=3522): # tef 3420 / com 3419
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -97,7 +99,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
- self.env['event.event'].create(event_values)
+ self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@@ -107,7 +109,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, no website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=206): # tef 160 / com 160
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=108): # tef 103 / com 103
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
#
@@ -128,7 +130,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=666): # tef 565 / com 566
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=429): # tef 379 / com 380
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
#
@@ -150,7 +152,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# type and website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=692): # tef 593 / com 596
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=472): # tef 426 / com 428
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
#
@@ -168,7 +170,7 @@ class TestEventPerformance(EventPerformanceCase):
def test_event_create_single_notype(self):
""" Test a single event creation """
# simple without type involved
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=31): # 31
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=31): # tef 31
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
@@ -181,13 +183,13 @@ class TestEventPerformance(EventPerformanceCase):
def test_event_create_single_notype_website(self):
""" Test a single event creation """
# simple without type involved + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=352): # tef 327 / com 326
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=242): # tef 228 / com 234
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
website_menu=True
)
- self.env['event.event'].create([event_values])
+ self.env['event.event'].with_context(lang='en_US').create([event_values])
@users('event_user')
@warmup
@@ -196,7 +198,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=58): # 58
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=52): # tef 52
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
@@ -212,13 +214,13 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=387): # tef 362 / com 361
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=274): # tef 266 / com 265
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
event_type_id=event_type.id,
)
- self.env['event.event'].create([event_values])
+ self.env['event.event'].with_context(lang='en_US').create([event_values])
@tagged('event_performance', 'registration_performance', 'post_install', '-at_install', '-standard')
@@ -234,7 +236,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
"""
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=720): # tef only: 674? - com runbot 716 - ent runbot 719
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=638): # tef 633 / com 636
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -258,7 +260,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
"""
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=210): # tef 167 / com runbot 206
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=165): # tef 164 / com runbot 163
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -280,7 +282,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
form like) """
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=731): # tef only: 685? - com runbot 727
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=651): # tef 647 - com 649
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
@@ -301,12 +303,11 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=231): # tef only: 210? - com runbot 216
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=145): # tef 140 / com 143
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration']) as reg_form:
reg_form.event_id = event
reg_form.email = 'email.00@test.example.com'
- reg_form.mobile = '0456999999'
reg_form.name = 'My Customer'
reg_form.phone = '0456000000'
_registration = reg_form.save()
@@ -317,7 +318,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=233): # tef only: 213? - com runbot 217
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=146): # tef 141 / com 144
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration']) as reg_form:
reg_form.event_id = event
@@ -330,7 +331,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
""" Test a single registration creation using Form """
event = self.env['event.event'].browse(self.test_event.ids)
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=124): # tef 107 / com 109
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=63): # tef 61 / com 61
self.env.cr._now = self.reference_now # force create_date to check schedulers
with Form(self.env['event.registration'].with_context(event_lead_rule_skip=True)) as reg_form:
reg_form.event_id = event
@@ -344,7 +345,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# simple customer data
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=143): # tef only: 135? - com runbot 140
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=122): # tef 118 / com 120
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.customer_data[0],
@@ -358,7 +359,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# partner-based customer
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=149): # tef only: 142? - com runbot 146
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=121): # tef 117 / com 120
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
@@ -373,7 +374,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# partner-based customer
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=46): # tef 41 / com 43
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=39): # tef 38 / com 38
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
@@ -388,7 +389,7 @@ class TestRegistrationPerformance(EventPerformanceCase):
event = self.env['event.event'].browse(self.test_event.ids)
# website customer data
- with freeze_time(self.reference_now), self.assertQueryCount(event_user=151): # tef only: 142? - com runbot 146
+ with freeze_time(self.reference_now), self.assertQueryCount(event_user=126): # tef 122 / com 124
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.website_customer_data[0],
@@ -429,7 +430,6 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
])
def _test_url_open(self, url):
- url += ('?' not in url and '?' or '') + '&debug=disable-t-cache'
return self.url_open(url)
@warmup
@@ -437,7 +437,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
- with self.assertQueryCount(default=36): # tef 35
+ with self.assertQueryCount(default=35): # tef 34
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
@@ -445,7 +445,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
- with self.assertQueryCount(default=27):
+ with self.assertQueryCount(default=25):
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
@@ -453,7 +453,7 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
- with self.assertQueryCount(default=39): # tef 38
+ with self.assertQueryCount(default=43): # tef 42
self._test_url_open('/event')
@warmup
@@ -461,23 +461,22 @@ class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
- with self.assertQueryCount(default=28):
+ with self.assertQueryCount(default=39):
self._test_url_open('/event')
- # @warmup
- # def test_register_public(self):
- # with freeze_time(self.reference_now + timedelta(hours=3)): # be sure sales has started
- # self.assertTrue(self.test_event.event_registrations_started)
- # self.authenticate(None, None)
- # with self.assertQueryCount(default=99999): # tef only: 1110
- # self.browser_js(
- # '/event/%i/register' % self.test_event.id,
- # 'odoo.__DEBUG__.services["web_tour.tour"].run("wevent_performance_register")',
- # 'odoo.__DEBUG__.services["web_tour.tour"].tours.wevent_performance_register.ready',
- # login=None,
- # timeout=200,
- # )
+ @warmup
+ def test_register_public(self):
+ with freeze_time(self.reference_now + timedelta(hours=3)): # be sure sales has started
+ self.assertTrue(self.test_event.event_registrations_started)
+ self.authenticate(None, None)
+ with self.assertQueryCount(default=1197): # tef: 1197
+ self.start_tour(
+ '/event/%i/register' % self.test_event.id,
+ 'wevent_performance_register',
+ login=None,
+ timeout=200,
+ )
- # # minimal checkup, to be improved in future tests independently from performance
- # self.assertEqual(len(self.test_event.registration_ids), 3)
- # self.assertEqual(len(self.test_event.registration_ids.visitor_id), 1)
+ # minimal checkup, to be improved in future tests independently from performance
+ self.assertEqual(len(self.test_event.registration_ids), 3)
+ self.assertEqual(len(self.test_event.registration_ids.visitor_id), 1)
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_menu.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_menu.py
new file mode 100644
index 0000000..ff76318
--- /dev/null
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_menu.py
@@ -0,0 +1,41 @@
+from odoo.addons.test_event_full.tests.common import TestWEventCommon
+from odoo.tests import tagged
+from odoo.tests.common import users
+
+
+@tagged('event_online', 'post_install', '-at_install')
+class TestWEventMenu(TestWEventCommon):
+
+ @users('admin')
+ def test_seo_data(self):
+ """Test SEO data for submenus on event website page"""
+
+ self.assertFalse(self.event.website_meta_title, 'Event should initially have no meta title')
+ self.event.write({
+ 'website_meta_title': 'info',
+ })
+ self.assertTrue(self.event.website_meta_title, 'Event should have a meta title after writing')
+
+ menus = [
+ ('booth_menu_ids', 'Get a Booth'),
+ ('exhibitor_menu_ids', 'Exhibitor'),
+ ('community_menu_ids', 'Leaderboard'),
+ ('track_menu_ids', 'Talks'),
+ ('track_menu_ids', 'Agenda'),
+ ('track_proposal_menu_ids', 'Talk Proposal'),
+ ]
+
+ for menu_field, menu_name in menus:
+ menu = self.event[menu_field]
+
+ if menu_field == 'track_menu_ids':
+ menu_url = '/track' if menu_name == 'Talks' else '/agenda'
+ menu = self.event[menu_field].filtered(lambda menu: menu.menu_id.url.endswith(menu_url))
+
+ self.assertFalse(menu.website_meta_title, f"{menu_name} page should initially have no meta title")
+ menu.write({'website_meta_title': menu_name})
+
+ web_page = self.url_open(menu.menu_id.url)
+
+ self.assertTrue(menu.website_meta_title, f"{menu_name} page should have a meta title after writing")
+ self.assertIn(f"
{menu.website_meta_title}", web_page.text)
diff --git a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_register.py b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_register.py
index cc38388..58dc8fc 100644
--- a/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_register.py
+++ b/odoo-bringout-oca-ocb-test_event_full/test_event_full/tests/test_wevent_register.py
@@ -4,6 +4,7 @@
from freezegun import freeze_time
from odoo import tests
+from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.test_event_full.tests.common import TestWEventCommon
@@ -11,13 +12,9 @@ from odoo.addons.test_event_full.tests.common import TestWEventCommon
class TestWEventRegister(TestWEventCommon):
def test_register(self):
+ self.env.company.country_id = self.env.ref('base.us')
with freeze_time(self.reference_now, tick=True):
- self.browser_js(
- '/event',
- 'odoo.__DEBUG__.services["web_tour.tour"].run("wevent_register")',
- 'odoo.__DEBUG__.services["web_tour.tour"].tours.wevent_register.ready',
- login=None
- )
+ self.start_tour('/event', 'wevent_register', login=None)
new_registrations = self.event.registration_ids
visitor = new_registrations.visitor_id
@@ -40,5 +37,15 @@ class TestWEventRegister(TestWEventCommon):
self.assertEqual(visitor.display_name, "Raoulette Poiluchette")
self.assertEqual(visitor.event_registration_ids, new_registrations)
self.assertEqual(visitor.partner_id, self.env['res.partner'])
- self.assertEqual(visitor.mobile, "0456112233")
self.assertEqual(visitor.email, "raoulette@example.com")
+
+ def test_internal_user_register(self):
+ mail_new_test_user(
+ self.env,
+ name='User Internal',
+ login='user_internal',
+ email='user_internal@example.com',
+ groups='base.group_user',
+ )
+ with freeze_time(self.reference_now, tick=True):
+ self.start_tour('/event', 'wevent_register', login='user_internal')
diff --git a/odoo-bringout-oca-ocb-test_mail/README.md b/odoo-bringout-oca-ocb-test_mail/README.md
index 0ad6c7f..1e95367 100644
--- a/odoo-bringout-oca-ocb-test_mail/README.md
+++ b/odoo-bringout-oca-ocb-test_mail/README.md
@@ -12,38 +12,15 @@ pip install odoo-bringout-oca-ocb-test_mail
## Dependencies
-This addon depends on:
- mail
-- test_performance
-
-## Manifest Information
-
-- **Name**: Mail Tests
-- **Version**: 1.0
-- **Category**: Hidden
-- **License**: LGPL-3
-- **Installable**: True
+- test_orm
## Source
-Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_mail`.
+- Repository: https://github.com/OCA/OCB
+- Branch: 19.0
+- Path: addons/test_mail
## 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
+This package preserves the original LGPL-3 license.
diff --git a/odoo-bringout-oca-ocb-test_mail/pyproject.toml b/odoo-bringout-oca-ocb-test_mail/pyproject.toml
index 902e522..5a94cfa 100644
--- a/odoo-bringout-oca-ocb-test_mail/pyproject.toml
+++ b/odoo-bringout-oca-ocb-test_mail/pyproject.toml
@@ -1,13 +1,14 @@
[project]
name = "odoo-bringout-oca-ocb-test_mail"
version = "16.0.0"
-description = "Mail Tests - Mail Tests: performances and tests specific to mail"
+description = "Mail Tests -
+ Mail Tests: performances and tests specific to mail
+ "
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
- "odoo-bringout-oca-ocb-mail>=16.0.0",
- "odoo-bringout-oca-ocb-test_performance>=16.0.0",
+ "odoo-bringout-oca-ocb-mail>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@@ -17,7 +18,7 @@ classifiers = [
"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.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
diff --git a/odoo-bringout-oca-ocb-test_mail/test_mail/__manifest__.py b/odoo-bringout-oca-ocb-test_mail/test_mail/__manifest__.py
index 2a8f154..aa7e914 100644
--- a/odoo-bringout-oca-ocb-test_mail/test_mail/__manifest__.py
+++ b/odoo-bringout-oca-ocb-test_mail/test_mail/__manifest__.py
@@ -11,7 +11,7 @@ present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models. """,
'depends': [
'mail',
- 'test_performance',
+ 'test_orm',
],
'data': [
'security/ir.model.access.csv',
@@ -21,16 +21,14 @@ tests independently to functional aspects of other models. """,
'data/subtype_data.xml',
],
'assets': {
- 'web.qunit_suite_tests': [
- 'test_mail/static/tests/*',
+ 'web.assets_unit_tests': [
+ 'test_mail/static/tests/**/*',
],
- 'web.qunit_mobile_suite_tests': [
- 'test_mail/static/tests/mobile/activity_tests.js',
- ],
- 'web.tests_assets': [
- 'test_mail/static/tests/helpers/*',
+ 'web.assets_tests': [
+ 'test_mail/static/tests/tours/*',
],
},
'installable': True,
+ 'author': 'Odoo S.A.',
'license': 'LGPL-3',
}
diff --git a/odoo-bringout-oca-ocb-test_mail/test_mail/data/data.xml b/odoo-bringout-oca-ocb-test_mail/test_mail/data/data.xml
index 1e4c55f..2336076 100644
--- a/odoo-bringout-oca-ocb-test_mail/test_mail/data/data.xml
+++ b/odoo-bringout-oca-ocb-test_mail/test_mail/data/data.xml
@@ -36,5 +36,18 @@
trigger
+
+ Document
+ Document
+ 5
+ upload_file
+ mail.test.activity
+
+
+
+ Do Stuff
+ Hey Zoidberg! Get in here!
+ default
+
diff --git a/odoo-bringout-oca-ocb-test_mail/test_mail/data/mail_template_data.xml b/odoo-bringout-oca-ocb-test_mail/test_mail/data/mail_template_data.xml
index 944a1b7..5f8aab7 100644
--- a/odoo-bringout-oca-ocb-test_mail/test_mail/data/mail_template_data.xml
+++ b/odoo-bringout-oca-ocb-test_mail/test_mail/data/mail_template_data.xml
@@ -4,6 +4,7 @@
Mail Test Full: Tracking Template
Test Template
{{ object.customer_id.id }}
+
Hello
@@ -13,6 +14,7 @@
Mail Test: Template
Post on {{ object.name }}
{{ object.customer_id.id }}
+
Adding stuff on
@@ -33,6 +35,30 @@
+
+
+
+
+
+
This is another sample of an external report.
+
+
+
+
+
+
+
+
+
+
+
This is a sample of an external report for a ticket for
+ people.
+
+
+
+
+
+
Hello , this comes from .
diff --git a/odoo-bringout-oca-ocb-test_mail/test_mail/data/subtype_data.xml b/odoo-bringout-oca-ocb-test_mail/test_mail/data/subtype_data.xml
index 2880100..f3702a8 100644
--- a/odoo-bringout-oca-ocb-test_mail/test_mail/data/subtype_data.xml
+++ b/odoo-bringout-oca-ocb-test_mail/test_mail/data/subtype_data.xml
@@ -52,4 +52,13 @@
+
+
+ New ticket
+ New Ticket
+ mail.test.ticket.partner
+
+
+
+
diff --git a/odoo-bringout-oca-ocb-test_mail/test_mail/data/test_mail_data.py b/odoo-bringout-oca-ocb-test_mail/test_mail/data/test_mail_data.py
index 0c5d787..2bb5fa6 100644
--- a/odoo-bringout-oca-ocb-test_mail/test_mail/data/test_mail_data.py
+++ b/odoo-bringout-oca-ocb-test_mail/test_mail/data/test_mail_data.py
@@ -11,7 +11,7 @@ Subject: {subject}
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="----=_Part_4200734_24778174.1344608186754"
-Date: Fri, 10 Aug 2012 14:16:26 +0000
+Date: {date}
Message-ID: {msg_id}
{extra}
------=_Part_4200734_24778174.1344608186754
@@ -135,6 +135,38 @@ Message-ID: {msg_id}