Initial commit: Test packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit 080accd21c
338 changed files with 32413 additions and 0 deletions

17
README.md Normal file
View file

@ -0,0 +1,17 @@
# Test
This repository contains OCA OCB packages for test.
## Packages Included
- odoo-bringout-oca-ocb-test_base_automation
- odoo-bringout-oca-ocb-test_crm_full
- odoo-bringout-oca-ocb-test_discuss_full
- odoo-bringout-oca-ocb-test_event_full
- odoo-bringout-oca-ocb-test_mail
- odoo-bringout-oca-ocb-test_mail_full
- odoo-bringout-oca-ocb-test_resource
- odoo-bringout-oca-ocb-test_website
- odoo-bringout-oca-ocb-test_website_modules
- odoo-bringout-oca-ocb-test_website_slides_full
- odoo-bringout-oca-ocb-test_xlsx_export

View file

@ -0,0 +1,48 @@
# Test - Base Automation
This module contains tests related to base automation. Those are
present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models.
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_base_automation
```
## Dependencies
This addon depends on:
- base_automation
## Manifest Information
- **Name**: Test - Base Automation
- **Version**: 1.0
- **Category**: Hidden
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `test_base_automation`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

View file

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Test - Base Automation',
'version': '1.0',
'category': 'Hidden',
'sequence': 9877,
'summary': 'Base Automation Tests: Ensure Flow Robustness',
'description': """This module contains tests related to base automation. Those are
present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models.""",
'depends': ['base_automation'],
'data': [
'security/ir.model.access.csv',
],
'installable': True,
'license': 'LGPL-3',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_base_automation

View file

@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from dateutil import relativedelta
from odoo import fields, models, api
class LeadTest(models.Model):
_name = "base.automation.lead.test"
_description = "Automated Rule Test"
name = fields.Char(string='Subject', required=True)
user_id = fields.Many2one('res.users', string='Responsible')
state = fields.Selection([('draft', 'New'), ('cancel', 'Cancelled'), ('open', 'In Progress'),
('pending', 'Pending'), ('done', 'Closed')],
string="Status", readonly=True, default='draft')
active = fields.Boolean(default=True)
partner_id = fields.Many2one('res.partner', string='Partner')
date_action_last = fields.Datetime(string='Last Action', readonly=True)
employee = fields.Boolean(compute='_compute_employee_deadline', store=True)
line_ids = fields.One2many('base.automation.line.test', 'lead_id')
priority = fields.Boolean()
deadline = fields.Boolean(compute='_compute_employee_deadline', store=True)
is_assigned_to_admin = fields.Boolean(string='Assigned to admin user')
@api.depends('partner_id.employee', 'priority')
def _compute_employee_deadline(self):
# this method computes two fields on purpose; don't split it
for record in self:
record.employee = record.partner_id.employee
if not record.priority:
record.deadline = False
else:
record.deadline = record.create_date + relativedelta.relativedelta(days=3)
def write(self, vals):
result = super().write(vals)
# force recomputation of field 'deadline' via 'employee': the action
# based on 'deadline' must be triggered
self.mapped('employee')
return result
class LineTest(models.Model):
_name = "base.automation.line.test"
_description = "Automated Rule Line Test"
name = fields.Char()
lead_id = fields.Many2one('base.automation.lead.test', ondelete='cascade')
user_id = fields.Many2one('res.users')
class ModelWithAccess(models.Model):
_name = "base.automation.link.test"
_description = "Automated Rule Link Test"
name = fields.Char()
linked_id = fields.Many2one('base.automation.linked.test', ondelete='cascade')
class ModelWithoutAccess(models.Model):
_name = "base.automation.linked.test"
_description = "Automated Rule Linked Test"
name = fields.Char()
another_field = fields.Char()
class Project(models.Model):
_name = _description = 'test_base_automation.project'
name = fields.Char()
task_ids = fields.One2many('test_base_automation.task', 'project_id')
class Task(models.Model):
_name = _description = 'test_base_automation.task'
name = fields.Char()
parent_id = fields.Many2one('test_base_automation.task')
project_id = fields.Many2one(
'test_base_automation.project',
compute='_compute_project_id', recursive=True, store=True, readonly=False,
)
@api.depends('parent_id.project_id')
def _compute_project_id(self):
for task in self:
if not task.project_id:
task.project_id = task.parent_id.project_id

View file

@ -0,0 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_base_automation_lead_test,access_base_automation_lead_test,model_base_automation_lead_test,base.group_system,1,1,1,1
access_base_automation_line_test,access_base_automation_line_test,model_base_automation_line_test,base.group_system,1,1,1,1
access_base_automation_link_test,access_base_automation_link_test,model_base_automation_link_test,,1,1,1,1
access_base_automation_linked_test,access_base_automation_linked_test,model_base_automation_linked_test,,1,1,1,1
access_test_base_automation_project,access_test_base_automation_project,model_test_base_automation_project,,1,1,1,1
access_test_base_automation_task,access_test_base_automation_task,model_test_base_automation_task,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_base_automation_lead_test access_base_automation_lead_test model_base_automation_lead_test base.group_system 1 1 1 1
3 access_base_automation_line_test access_base_automation_line_test model_base_automation_line_test base.group_system 1 1 1 1
4 access_base_automation_link_test access_base_automation_link_test model_base_automation_link_test 1 1 1 1
5 access_base_automation_linked_test access_base_automation_linked_test model_base_automation_linked_test 1 1 1 1
6 access_test_base_automation_project access_test_base_automation_project model_test_base_automation_project 1 1 1 1
7 access_test_base_automation_task access_test_base_automation_task model_test_base_automation_task 1 1 1 1

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_flow

View file

@ -0,0 +1,462 @@
# # -*- coding: utf-8 -*-
# # Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
import sys
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
from odoo.tests import common, tagged
from odoo.exceptions import AccessError
@tagged('post_install', '-at_install')
class BaseAutomationTest(TransactionCaseWithUserDemo):
def setUp(self):
super(BaseAutomationTest, self).setUp()
self.user_root = self.env.ref('base.user_root')
self.user_admin = self.env.ref('base.user_admin')
self.test_mail_template_automation = self.env['mail.template'].create({
'name': 'Template Automation',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'body_html': """<div>Email automation</div>""",
})
self.res_partner_1 = self.env['res.partner'].create({'name': 'My Partner'})
self.env['base.automation'].create([
{
'name': 'Base Automation: test rule on create',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_create',
'active': True,
'filter_domain': "[('state', '=', 'draft')]",
}, {
'name': 'Base Automation: test rule on write',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_write',
'active': True,
'filter_domain': "[('state', '=', 'done')]",
'filter_pre_domain': "[('state', '=', 'open')]",
}, {
'name': 'Base Automation: test rule on recompute',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_write',
'active': True,
'filter_domain': "[('employee', '=', True)]",
}, {
'name': 'Base Automation: test recursive rule',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
if 'partner_id' in env.context['old_values'][record.id]:
record.write({'state': 'draft'})""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test rule on secondary model',
'model_id': self.env.ref('test_base_automation.model_base_automation_line_test').id,
'state': 'code',
'code': "records.write({'user_id': %s})" % (self.user_demo.id),
'trigger': 'on_create',
'active': True,
}, {
'name': 'Base Automation: test rule on write check context',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
if 'user_id' in env.context['old_values'][record.id]:
record.write({'is_assigned_to_admin': (record.user_id.id == 1)})""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test rule with trigger',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__state').id)],
'state': 'code',
'code': """
record = model.browse(env.context['active_id'])
record['name'] = record.name + 'X'""",
'trigger': 'on_write',
'active': True,
}, {
'name': 'Base Automation: test send an email',
'mail_post_method': 'email',
'model_id': self.env.ref('test_base_automation.model_base_automation_lead_test').id,
'template_id': self.test_mail_template_automation.id,
'trigger_field_ids': [(4, self.env.ref('test_base_automation.field_base_automation_lead_test__deadline').id)],
'state': 'mail_post',
'code': """
record = model.browse(env.context['active_id'])
record['name'] = record.name + 'X'""",
'trigger': 'on_write',
'active': True,
'filter_domain': "[('deadline', '!=', False)]",
'filter_pre_domain': "[('deadline', '=', False)]",
}
])
def tearDown(self):
super().tearDown()
self.env['base.automation']._unregister_hook()
def create_lead(self, **kwargs):
vals = {
'name': "Lead Test",
'user_id': self.user_root.id,
}
vals.update(kwargs)
return self.env['base.automation.lead.test'].create(vals)
def test_00_check_to_state_open_pre(self):
"""
Check that a new record (with state = open) doesn't change its responsible
when there is a precondition filter which check that the state is open.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open')
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
def test_01_check_to_state_draft_post(self):
"""
Check that a new record changes its responsible when there is a postcondition
filter which check that the state is draft.
"""
lead = self.create_lead()
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on creation of Lead with state 'draft'.")
def test_02_check_from_draft_to_done_with_steps(self):
"""
A new record is created and goes from states 'open' to 'done' via the
other states (open, pending and cancel). We have a rule with:
- precondition: the record is in "open"
- postcondition: that the record is "done".
If the state goes from 'open' to 'done' the responsible is changed.
If those two conditions aren't verified, the responsible remains the same.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
# change state to pending and check that responsible has not changed
lead.write({'state': 'pending'})
self.assertEqual(lead.state, 'pending', "Lead state should be 'pending'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
# change state to done and check that responsible has not changed
lead.write({'state': 'done'})
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not chang on creation of Lead with state from 'pending' to 'done'.")
def test_03_check_from_draft_to_done_without_steps(self):
"""
A new record is created and goes from states 'open' to 'done' via the
other states (open, pending and cancel). We have a rule with:
- precondition: the record is in "open"
- postcondition: that the record is "done".
If the state goes from 'open' to 'done' the responsible is changed.
If those two conditions aren't verified, the responsible remains the same.
"""
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open', "Lead state should be 'open'")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state 'open'.")
# change state to done and check that responsible has changed
lead.write({'state': 'done'})
self.assertEqual(lead.state, 'done', "Lead state should be 'done'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead with state from 'open' to 'done'.")
def test_10_recomputed_field(self):
"""
Check that a rule is executed whenever a field is recomputed after a
change on another model.
"""
partner = self.res_partner_1
partner.write({'employee': False})
lead = self.create_lead(state='open', partner_id=partner.id)
self.assertFalse(lead.employee, "Customer field should updated to False")
self.assertEqual(lead.user_id, self.user_root, "Responsible should not change on creation of Lead with state from 'draft' to 'open'.")
# change partner, recompute on lead should trigger the rule
partner.write({'employee': True})
self.env.flush_all()
self.assertTrue(lead.employee, "Customer field should updated to True")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should be change on write of Lead when Customer becomes True.")
def test_11_recomputed_field(self):
"""
Check that a rule is executed whenever a field is recomputed and the
context contains the target field
"""
partner = self.res_partner_1
lead = self.create_lead(state='draft', partner_id=partner.id)
self.assertFalse(lead.deadline, 'There should not be a deadline defined')
# change priority and user; this triggers deadline recomputation, and
# the server action should set the boolean field to True
lead.write({'priority': True, 'user_id': self.user_root.id})
self.assertTrue(lead.deadline, 'Deadline should be defined')
self.assertTrue(lead.is_assigned_to_admin, 'Lead should be assigned to admin')
def test_11b_recomputed_field(self):
mail_automation = self.env['base.automation'].search([('name', '=', 'Base Automation: test send an email')])
send_mail_count = 0
def _patched_get_actions(*args, **kwargs):
obj = args[0]
if '__action_done' not in obj._context:
obj = obj.with_context(__action_done={})
return mail_automation.with_env(obj.env)
def _patched_send_mail(*args, **kwargs):
nonlocal send_mail_count
send_mail_count += 1
patchers = [
patch('odoo.addons.base_automation.models.base_automation.BaseAutomation._get_actions', _patched_get_actions),
patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail),
]
self.startPatcher(patchers[0])
lead = self.create_lead()
self.assertFalse(lead.priority)
self.assertFalse(lead.deadline)
self.startPatcher(patchers[1])
lead.write({'priority': True})
self.assertTrue(lead.priority)
self.assertTrue(lead.deadline)
self.assertEqual(send_mail_count, 1)
def test_12_recursive(self):
""" Check that a rule is executed recursively by a secondary change. """
lead = self.create_lead(state='open')
self.assertEqual(lead.state, 'open')
self.assertEqual(lead.user_id, self.user_root)
# change partner; this should trigger the rule that modifies the state
partner = self.res_partner_1
lead.write({'partner_id': partner.id})
self.assertEqual(lead.state, 'draft')
def test_20_direct_line(self):
"""
Check that a rule is executed after creating a line record.
"""
line = self.env['base.automation.line.test'].create({'name': "Line"})
self.assertEqual(line.user_id, self.user_demo)
def test_20_indirect_line(self):
"""
Check that creating a lead with a line executes rules on both records.
"""
lead = self.create_lead(line_ids=[(0, 0, {'name': "Line"})])
self.assertEqual(lead.state, 'draft', "Lead state should be 'draft'")
self.assertEqual(lead.user_id, self.user_demo, "Responsible should change on creation of Lead test line.")
self.assertEqual(len(lead.line_ids), 1, "New test line is not created")
self.assertEqual(lead.line_ids.user_id, self.user_demo, "Responsible should be change on creation of Lead test line.")
def test_21_trigger_fields(self):
"""
Check that the rule with trigger is executed only once per pertinent update.
"""
lead = self.create_lead(name="X")
lead.priority = True
partner1 = self.res_partner_1
lead.partner_id = partner1.id
self.assertEqual(lead.name, 'X', "No update until now.")
lead.state = 'open'
self.assertEqual(lead.name, 'XX', "One update should have happened.")
lead.state = 'done'
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
lead.state = 'done'
self.assertEqual(lead.name, 'XXX', "No update should have happened.")
lead.state = 'cancel'
self.assertEqual(lead.name, 'XXXX', "One update should have happened.")
# change the rule to trigger on partner_id
rule = self.env['base.automation'].search([('name', '=', 'Base Automation: test rule with trigger')])
rule.write({'trigger_field_ids': [(6, 0, [self.env.ref('test_base_automation.field_base_automation_lead_test__partner_id').id])]})
partner2 = self.env['res.partner'].create({'name': 'A new partner'})
lead.name = 'X'
lead.state = 'open'
self.assertEqual(lead.name, 'X', "No update should have happened.")
lead.partner_id = partner2
self.assertEqual(lead.name, 'XX', "One update should have happened.")
lead.partner_id = partner2
self.assertEqual(lead.name, 'XX', "No update should have happened.")
lead.partner_id = partner1
self.assertEqual(lead.name, 'XXX', "One update should have happened.")
def test_30_modelwithoutaccess(self):
"""
Ensure a domain on a M2O without user access doesn't fail.
We create a base automation with a filter on a model the user haven't access to
- create a group
- restrict acl to this group and set only admin in it
- create base.automation with a filter
- create a record in the restricted model in admin
- create a record in the non restricted model in demo
"""
Model = self.env['base.automation.link.test']
Comodel = self.env['base.automation.linked.test']
access = self.env.ref("test_base_automation.access_base_automation_linked_test")
access.group_id = self.env['res.groups'].create({
'name': "Access to base.automation.linked.test",
"users": [(6, 0, [self.user_admin.id,])]
})
# sanity check: user demo has no access to the comodel of 'linked_id'
with self.assertRaises(AccessError):
Comodel.with_user(self.user_demo).check_access_rights('read')
# check base automation with filter that performs Comodel.search()
self.env['base.automation'].create({
'name': 'test no access',
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
'trigger': 'on_create_or_write',
'filter_pre_domain': "[('linked_id.another_field', '=', 'something')]",
'state': 'code',
'active': True,
'code': "action = [rec.name for rec in records]"
})
Comodel.create([
{'name': 'a first record', 'another_field': 'something'},
{'name': 'another record', 'another_field': 'something different'},
])
rec1 = Model.create({'name': 'a record'})
rec1.write({'name': 'a first record'})
rec2 = Model.with_user(self.user_demo).create({'name': 'another record'})
rec2.write({'name': 'another value'})
# check base automation with filter that performs Comodel.name_search()
self.env['base.automation'].create({
'name': 'test no name access',
'model_id': self.env['ir.model']._get_id("base.automation.link.test"),
'trigger': 'on_create_or_write',
'filter_pre_domain': "[('linked_id', '=', 'whatever')]",
'state': 'code',
'active': True,
'code': "action = [rec.name for rec in records]"
})
rec3 = Model.create({'name': 'a random record'})
rec3.write({'name': 'a first record'})
rec4 = Model.with_user(self.user_demo).create({'name': 'again another record'})
rec4.write({'name': 'another value'})
@common.tagged('post_install', '-at_install')
class TestCompute(common.TransactionCase):
def test_inversion(self):
""" If a stored field B depends on A, an update to the trigger for A
should trigger the recomputaton of A, then B.
However if a search() is performed during the computation of A
??? and _order is affected ??? a flush will be triggered, forcing the
computation of B, based on the previous A.
This happens if a rule has has a non-empty filter_pre_domain, even if
it's an empty list (``'[]'`` as opposed to ``False``).
"""
company1 = self.env['res.partner'].create({
'name': "Gorofy",
'is_company': True,
})
company2 = self.env['res.partner'].create({
'name': "Awiclo",
'is_company': True
})
r = self.env['res.partner'].create({
'name': 'Bob',
'is_company': False,
'parent_id': company1.id
})
self.assertEqual(r.display_name, 'Gorofy, Bob')
r.parent_id = company2
self.assertEqual(r.display_name, 'Awiclo, Bob')
self.env['base.automation'].create({
'name': "test rule",
'filter_pre_domain': False,
'trigger': 'on_create_or_write',
'state': 'code', # no-op action
'model_id': self.env.ref('base.model_res_partner').id,
})
r.parent_id = company1
self.assertEqual(r.display_name, 'Gorofy, Bob')
self.env['base.automation'].create({
'name': "test rule",
'filter_pre_domain': '[]',
'trigger': 'on_create_or_write',
'state': 'code', # no-op action
'model_id': self.env.ref('base.model_res_partner').id,
})
r.parent_id = company2
self.assertEqual(r.display_name, 'Awiclo, Bob')
def test_recursion(self):
project = self.env['test_base_automation.project'].create({})
# this action is executed every time a task is assigned to project
self.env['base.automation'].create({
'name': 'dummy',
'model_id': self.env['ir.model']._get_id('test_base_automation.task'),
'state': 'code',
'trigger': 'on_create_or_write',
'filter_domain': repr([('project_id', '=', project.id)]),
})
# create one task in project with 10 subtasks; all the subtasks are
# automatically assigned to project, too
task = self.env['test_base_automation.task'].create({'project_id': project.id})
subtasks = task.create([{'parent_id': task.id} for _ in range(10)])
subtasks.flush_model()
# This test checks what happens when a stored recursive computed field
# is marked to compute on many records, and automated actions are
# triggered depending on that field. In this case, we trigger the
# recomputation of 'project_id' on 'subtasks' by deleting their parent
# task.
#
# An issue occurs when the domain of automated actions is evaluated by
# method search(), because the latter flushes the fields to search on,
# which are also the ones being recomputed. Combined with the fact
# that recursive fields are not computed in batch, this leads to a huge
# amount of recursive calls between the automated action and flush().
#
# The execution of task.unlink() looks like this:
# - mark 'project_id' to compute on subtasks
# - delete task
# - flush()
# - recompute 'project_id' on subtask1
# - call compute on subtask1
# - in action, search([('id', 'in', subtask1.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask2
# - call compute on subtask2
# - in action search([('id', 'in', subtask2.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask3
# - call compute on subtask3
# - in action, search([('id', 'in', subtask3.ids), ('project_id', '=', pid)])
# - flush(['id', 'project_id'])
# - recompute 'project_id' on subtask4
# ...
limit = sys.getrecursionlimit()
try:
sys.setrecursionlimit(100)
task.unlink()
finally:
sys.setrecursionlimit(limit)

View file

@ -0,0 +1,57 @@
# Test Full Crm Flow
This module is intended to test the main crm flows of Odoo, both frontend and
backend. It notably includes IAP bridges modules to test their impact.
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_crm_full
```
## Dependencies
This addon depends on:
- crm
- crm_iap_enrich
- crm_iap_mine
- crm_sms
- event_crm
- sale_crm
- website_crm
- website_crm_iap_reveal
- 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`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

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

View file

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

View file

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

View file

@ -0,0 +1,14 @@
# Dependencies
This addon depends on:
- [crm](../../odoo-bringout-oca-ocb-crm)
- [crm_iap_enrich](../../odoo-bringout-oca-ocb-crm_iap_enrich)
- [crm_iap_mine](../../odoo-bringout-oca-ocb-crm_iap_mine)
- [crm_sms](../../odoo-bringout-oca-ocb-crm_sms)
- [event_crm](../../odoo-bringout-oca-ocb-event_crm)
- [sale_crm](../../odoo-bringout-oca-ocb-sale_crm)
- [website_crm](../../odoo-bringout-oca-ocb-website_crm)
- [website_crm_iap_reveal](../../odoo-bringout-oca-ocb-website_crm_iap_reveal)
- [website_crm_partner_assign](../../odoo-bringout-oca-ocb-website_crm_partner_assign)
- [website_crm_livechat](../../odoo-bringout-oca-ocb-website_crm_livechat)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

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

View file

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

View file

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

View file

@ -0,0 +1,51 @@
[project]
name = "odoo-bringout-oca-ocb-test_crm_full"
version = "16.0.0"
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",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["test_crm_full"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Test Full Crm Flow',
'version': '1.0',
'category': 'Hidden/Tests',
'description': """
This module is intended to test the main crm flows of Odoo, both frontend and
backend. It notably includes IAP bridges modules to test their impact. """,
'depends': [
'crm',
'crm_iap_enrich',
'crm_iap_mine',
'crm_sms',
'event_crm',
'sale_crm',
'website_crm',
'website_crm_iap_reveal',
'website_crm_partner_assign',
'website_crm_livechat',
],
'license': 'LGPL-3',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_performance

View file

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
from odoo.addons.crm.tests.common import TestCrmCommon
from odoo.addons.crm_iap_mine.tests.common import MockIAPReveal # MockIAPEnrich
from odoo.addons.website.tests.test_website_visitor import MockVisitor
class TestCrmFullCommon(TestCrmCommon, MockIAPReveal, MockVisitor):
@classmethod
def setUpClass(cls):
super(TestCrmFullCommon, cls).setUpClass()
cls._init_mail_gateway()
cls._activate_multi_company()
# Context data: dates
# ------------------------------------------------------------
# Mock dates to have reproducible computed fields based on time
cls.reference_now = datetime(2022, 1, 1, 10, 0, 0)
cls.reference_today = datetime(2022, 1, 1)
# Customers
# ------------------------------------------------------------
country_be = cls.env.ref('base.be')
cls.env['res.lang']._activate_lang('fr_BE')
cls.partners = cls.env['res.partner'].create([
{'country_id': country_be.id,
'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,
'zip': '1400',
} for idx in range(0, 10)
])

View file

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
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
@tagged('crm_performance', 'post_install', '-at_install', '-standard')
class CrmPerformanceCase(TestCrmFullCommon):
def setUp(self):
super(CrmPerformanceCase, self).setUp()
# patch registry to simulate a ready environment
self.patch(self.env.registry, 'ready', True)
self._flush_tracking()
self.user_sales_leads.write({
'groups_id': [
(4, self.env.ref('event.group_event_user').id),
(4, self.env.ref('im_livechat.im_livechat_group_user').id),
]
})
def _flush_tracking(self):
""" Force the creation of tracking values notably, and ensure tests are
reproducible. """
self.env.flush_all()
self.cr.flush()
@tagged('crm_performance', 'post_install', '-at_install', '-standard')
class TestCrmPerformance(CrmPerformanceCase):
@users('user_sales_leads')
@warmup
def test_lead_create_batch_mixed(self):
""" 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')
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=194): # tcf 193 / com 194
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,
'zip': '1400',
} for idx in range(batch_size)
]
crm_values += [
{'partner_id': self.partners[idx].id,
'name': 'Test Lead %02d' % idx,
} for idx in range(batch_size)
]
_leads = self.env['crm.lead'].create(crm_values)
@users('user_sales_leads')
@warmup
def test_lead_create_form_address(self):
""" Test a single lead creation using Form """
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
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'
lead_form.zip = '1400'
_lead = lead_form.save()
@users('user_sales_leads')
@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
self.env.cr._now = self.reference_now # force create_date to check schedulers
with self.debug_mode():
# {'invisible': ['|', ('type', '=', 'opportunity'), ('is_partner_visible', '=', False)]}
# lead.is_partner_visible = bool(lead.type == 'opportunity' or lead.partner_id or is_debug_mode)
with Form(self.env['crm.lead']) as lead_form:
lead_form.partner_id = self.partners[0]
lead_form.name = 'Test Lead'
_lead = lead_form.save()
@users('user_sales_leads')
@warmup
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')
with freeze_time(self.reference_now), self.assertQueryCount(user_sales_leads=43): # tcf only: 41 - com runbot: 42
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',
'zip': '1400',
}
]
_lead = self.env['crm.lead'].create(crm_values)
@users('user_sales_leads')
@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
self.env.cr._now = self.reference_now # force create_date to check schedulers
crm_values = [
{'partner_id': self.partners[0].id,
'name': 'Test Lead',
}
]
_lead = self.env['crm.lead'].create(crm_values)

View file

@ -0,0 +1,54 @@
# Test Discuss (full)
Test of Discuss with all possible overrides installed, including feature and performance tests.
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_discuss_full
```
## Dependencies
This addon depends on:
- calendar
- crm
- crm_livechat
- hr_holidays
- im_livechat
- mail
- mail_bot
- note
- website_livechat
## Manifest Information
- **Name**: Test Discuss (full)
- **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_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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,13 @@
# Dependencies
This addon depends on:
- [calendar](../../odoo-bringout-oca-ocb-calendar)
- [crm](../../odoo-bringout-oca-ocb-crm)
- [crm_livechat](../../odoo-bringout-oca-ocb-crm_livechat)
- [hr_holidays](../../odoo-bringout-oca-ocb-hr_holidays)
- [im_livechat](../../odoo-bringout-oca-ocb-im_livechat)
- [mail](../../odoo-bringout-oca-ocb-mail)
- [mail_bot](../../odoo-bringout-oca-ocb-mail_bot)
- [note](../../odoo-bringout-oca-ocb-note)
- [website_livechat](../../odoo-bringout-oca-ocb-website_livechat)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

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

View file

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

View file

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

View file

@ -0,0 +1,50 @@
[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."
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",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["test_discuss_full"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

View file

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# 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',
],
'installable': True,
'license': 'LGPL-3',
}

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_performance

View file

@ -0,0 +1,65 @@
# Test Full Event Flow
This module will test the main event flows of Odoo, both frontend and backend.
It installs sale capabilities, front-end flow, eCommerce, questions and
automatic lead generation, full Online support, ...
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_event_full
```
## Dependencies
This addon depends on:
- event
- event_booth
- event_crm
- event_crm_sale
- event_sale
- 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`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

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

View file

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

View file

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

View file

@ -0,0 +1,20 @@
# Dependencies
This addon depends on:
- [event](../../odoo-bringout-oca-ocb-event)
- [event_booth](../../odoo-bringout-oca-ocb-event_booth)
- [event_crm](../../odoo-bringout-oca-ocb-event_crm)
- [event_crm_sale](../../odoo-bringout-oca-ocb-event_crm_sale)
- [event_sale](../../odoo-bringout-oca-ocb-event_sale)
- [event_sms](../../odoo-bringout-oca-ocb-event_sms)
- [payment_demo](../../odoo-bringout-oca-ocb-payment_demo)
- [website_event_booth_sale_exhibitor](../../odoo-bringout-oca-ocb-website_event_booth_sale_exhibitor)
- [website_event_crm_questions](../../odoo-bringout-oca-ocb-website_event_crm_questions)
- [website_event_exhibitor](../../odoo-bringout-oca-ocb-website_event_exhibitor)
- [website_event_questions](../../odoo-bringout-oca-ocb-website_event_questions)
- [website_event_meet](../../odoo-bringout-oca-ocb-website_event_meet)
- [website_event_sale](../../odoo-bringout-oca-ocb-website_event_sale)
- [website_event_track](../../odoo-bringout-oca-ocb-website_event_track)
- [website_event_track_live](../../odoo-bringout-oca-ocb-website_event_track_live)
- [website_event_track_quiz](../../odoo-bringout-oca-ocb-website_event_track_quiz)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

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

View file

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

View file

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

View file

@ -0,0 +1,57 @@
[project]
name = "odoo-bringout-oca-ocb-test_event_full"
version = "16.0.0"
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",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["test_event_full"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

View file

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Test Full Event Flow',
'version': '1.0',
'category': 'Hidden/Tests',
'description': """
This module will test the main event flows of Odoo, both frontend and backend.
It installs sale capabilities, front-end flow, eCommerce, questions and
automatic lead generation, full Online support, ...
""",
'depends': [
'event',
'event_booth',
'event_crm',
'event_crm_sale',
'event_sale',
'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',
],
'data': [
# 'data/event_type_data.xml', # uncomment to reproduce test tour
'data/ir_actions_report_data.xml',
'views/event_registration_templates_reports.xml',
],
'assets': {
'web.assets_tests': [
'test_event_full/static/**/*',
],
},
'license': 'LGPL-3',
}

View file

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="0">
<record id="event_booth_category_data_1" model="event.booth.category">
<field name="description" type="html"><p>Standard</p></field>
<field name="name">Standard</field>
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
</record>
<record id="event_booth_category_data_2" model="event.booth.category">
<field name="description" type="html"><p>Premium</p></field>
<field name="name">Premium</field>
<field name="product_id" ref="event_booth_sale.product_product_event_booth"/>
<field name="price">90</field>
</record>
<record id="event_type_data_full" model="event.type">
<field name="auto_confirm" eval="True"/>
<field name="default_timezone">Europe/Paris</field>
<field name="event_type_booth_ids" eval="[
(5, 0),
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_1'),
'name': 'Standard Booth',
}
),
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_1'),
'name': 'Standard Booth 2',
}
),
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_2'),
'name': 'Premium Booth',
}
),
(0, 0, {'booth_category_id': ref('test_event_full.event_booth_category_data_2'),
'name': 'Premium Booth 2',
}
)]"/>
<field name="event_type_mail_ids" eval="[
(5, 0),
(0, 0, {'interval_unit': 'now',
'interval_type': 'after_sub',
'notification_type': 'mail',
'template_ref': 'mail.template,%i' % ref('event.event_subscription'),
}
),
(0, 0, {'interval_nbr': 1,
'interval_unit': 'days',
'interval_type': 'before_event',
'notification_type': 'mail',
'template_ref': 'mail.template,%i' % ref('event.event_reminder'),
}
),
(0, 0, {'interval_nbr': 1,
'interval_unit': 'days',
'interval_type': 'after_event',
'notification_type': 'sms',
'template_ref': 'sms.template,%i' % ref('event_sms.sms_template_data_event_reminder'),
}
)]"/>
<field name="event_type_ticket_ids" eval="[
(5, 0),
(0, 0, {'description': 'Ticket1 Description',
'name': 'Ticket1',
'product_id': ref('event_sale.product_product_event'),
'seats_max': 10,
}
),
(0, 0, {'description': 'Ticket2 Description',
'name': 'Ticket2',
'product_id': ref('event_sale.product_product_event'),
'price': 45,
}
)]"/>
<field name="has_seats_limitation" eval="True"/>
<field name="name">Test Type</field>
<field name="note" type="html"><p>Template note</p></field>
<field name="question_ids" eval="[(5, 0)]"/>
<field name="seats_max">30</field>
<field name="tag_ids" eval="[(5, 0)]"/>
<field name="ticket_instructions" type="html"><p>Ticket Instructions</p></field>
<field name="website_menu" eval="True"/>
</record>
<record id="event_question_type_full_1" model="event.question">
<field name="question_type">simple_choice</field>
<field name="once_per_order" eval="False"/>
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
<field name="title">Question1</field>
</record>
<record id="event_question_type_full_1_answer_1" model="event.question.answer">
<field name="name">Q1-Answer1</field>
<field name="sequence">1</field>
<field name="question_id" ref="test_event_full.event_question_type_full_1"/>
</record>
<record id="event_question_type_full_1_answer_2" model="event.question.answer">
<field name="name">Q1-Answer2</field>
<field name="sequence">2</field>
<field name="question_id" ref="test_event_full.event_question_type_full_1"/>
</record>
<record id="event_question_type_full_2" model="event.question">
<field name="question_type">simple_choice</field>
<field name="once_per_order" eval="False"/>
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
<field name="title">Question2</field>
</record>
<record id="event_question_type_full_2_answer_1" model="event.question.answer">
<field name="name">Q2-Answer1</field>
<field name="sequence">1</field>
<field name="question_id" ref="test_event_full.event_question_type_full_2"/>
</record>
<record id="event_question_type_full_2_answer_2" model="event.question.answer">
<field name="name">Q2-Answer2</field>
<field name="sequence">2</field>
<field name="question_id" ref="test_event_full.event_question_type_full_2"/>
</record>
<record id="event_question_type_full_3" model="event.question">
<field name="question_type">text_box</field>
<field name="once_per_order" eval="True"/>
<field name="event_type_id" ref="test_event_full.event_type_data_full"/>
<field name="title">Question3</field>
</record>
</data></odoo>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="event_registration_report_test" model="ir.actions.report">
<field name="name">Test Report</field>
<field name="model">event.registration</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">test_event_full.event_registration_template_report</field>
<field name="report_file">test_event_full.event_registration_template_report</field>
<field name="print_report_name">'Badge - %s - %s' % ((object.event_id.name or 'Event').replace('/',''), (object.name or '').replace('/',''))</field>
<field name="binding_model_id" ref="event.model_event_registration"/>
<field name="binding_type">report</field>
</record>
</odoo>

View file

@ -0,0 +1,85 @@
odoo.define('test_event_full.tour.performance', function (require) {
"use strict";
var tour = require('web_tour.tour');
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: "Select 1 unit of 'Ticket2' ticket type",
trigger: '#o_wevent_tickets_collapse .row.o_wevent_ticket_selector[name="Ticket2"] select',
run: 'text 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: "Validate attendees details",
extra_trigger: "input[name='1-name'], input[name='2-name'], input[name='3-name']",
trigger: 'button:contains("Continue")',
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 () {}
}];
tour.register('wevent_performance_register', {
test: true
}, [].concat(
registerSteps,
)
);
});

View file

@ -0,0 +1,174 @@
odoo.define('test_event_full.tour.register', function (require) {
"use strict";
var tour = require('web_tour.tour');
/**
* TALKS STEPS
*/
var discoverTalkSteps = function (talkName, fromList, reminderOn, toggleReminder) {
var steps;
if (fromList) {
steps = [{
content: 'Go on "' + talkName + '" talk in List',
trigger: 'a:contains("' + talkName + '")',
}];
}
else {
steps = [{
content: 'Click on Live Track',
trigger: 'article span:contains("' + talkName + '")',
run: 'click',
}];
}
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
}]);
}
}
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.");
},
}, {
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() {},
}];
/**
* MAIN STEPS
*/
var initTourSteps = function (eventName) {
return [{
content: 'Go on "' + eventName + '" page',
trigger: 'a[href*="/event"]:contains("' + eventName + '"):first',
}];
};
var browseTalksSteps = [{
content: 'Browse Talks',
trigger: 'a:contains("Talks")',
}, {
content: 'Check we are on the talk list page',
trigger: 'h1:contains("Book your talks")',
run: function () {} // check
}];
var browseMeetSteps = [{
content: 'Browse Meet',
trigger: 'a:contains("Community")',
}, {
content: 'Check we are on the community page',
trigger: 'span:contains("Join a room")',
run: function () {} // check
}];
tour.register('wevent_register', {
url: '/event',
test: true
}, [].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'),
registerSteps,
)
);
});

View file

@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common
from . import test_event_crm
from . import test_event_event
from . import test_event_mail
from . import test_event_security
from . import test_performance
from . import test_wevent_register
from . import test_event_discount

View file

@ -0,0 +1,462 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta, time
from odoo.addons.base.tests.common import HttpCaseWithUserDemo, HttpCaseWithUserPortal
from odoo.addons.event_crm.tests.common import EventCrmCase
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.sales_team.tests.common import TestSalesCommon
from odoo.addons.website.tests.test_website_visitor import MockVisitor
class TestEventFullCommon(EventCrmCase, TestSalesCommon, MockVisitor):
@classmethod
def setUpClass(cls):
super(TestEventFullCommon, cls).setUpClass()
cls._init_mail_gateway()
# Context data: dates
# ------------------------------------------------------------
# Mock dates to have reproducible computed fields based on time
cls.reference_now = datetime(2021, 12, 1, 10, 0, 0)
cls.reference_today = datetime(2021, 12, 1)
# Users and contacts
# ------------------------------------------------------------
cls.admin_user = cls.env.ref('base.user_admin')
cls.admin_user.write({
'country_id': cls.env.ref('base.be').id,
'login': 'admin',
'notification_type': 'inbox',
})
cls.company_admin = cls.admin_user.company_id
# set country in order to format Belgian numbers
cls.company_admin.write({
'country_id': cls.env.ref('base.be').id,
})
cls.event_user = mail_new_test_user(
cls.env,
company_id=cls.company_admin.id,
company_ids=[(4, cls.company_admin.id)],
country_id=cls.env.ref('base.be').id,
groups='base.group_user,base.group_partner_manager,event.group_event_user',
email='e.e@example.com',
login='event_user',
name='Ernest Employee',
notification_type='inbox',
signature='--\nErnest',
)
cls.customer = cls.env['res.partner'].create({
'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
cls.customer_so = cls.env['sale.order'].with_user(cls.user_sales_salesman).create({
'partner_id': cls.event_customer.id,
})
# Side records for event main records
# ------------------------------------------------------------
cls.ticket_product = cls.env['product.product'].create({
'description_sale': 'Ticket Product Description',
'detailed_type': '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',
'list_price': 20,
'name': 'Test Booth Product',
'standard_price': 60.0,
})
cls.tag_categories = cls.env['event.tag.category'].create([
{'is_published': True, 'name': 'Published Category'},
{'is_published': False, 'name': 'Unpublished Category'},
])
cls.tags = cls.env['event.tag'].create([
{'category_id': cls.tag_categories[0].id, 'name': 'PubTag1'},
{'category_id': cls.tag_categories[0].id, 'color': 0, 'name': 'PubTag2'},
{'category_id': cls.tag_categories[1].id, 'name': 'UnpubTag1'},
])
cls.event_booth_categories = cls.env['event.booth.category'].create([
{'description': '<p>Standard</p>',
'name': 'Standard',
'product_id': cls.booth_product.id,
},
{'description': '<p>Premium</p>',
'name': 'Premium',
'product_id': cls.booth_product.id,
'price': 90,
}
])
cls.sponsor_types = cls.env['event.sponsor.type'].create([
{'name': 'GigaTop',
'sequence': 1,
}
])
cls.sponsor_partners = cls.env['res.partner'].create([
{'country_id': cls.env.ref('base.be').id,
'email': 'event.sponsor@example.com',
'name': 'EventSponsor',
'phone': '04856112233',
}
])
# Event type
# ------------------------------------------------------------
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})
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,
'name': 'Standard Booth',
}
),
(0, 0, {'booth_category_id': cls.event_booth_categories[0].id,
'name': 'Standard Booth 2',
}
),
(0, 0, {'booth_category_id': cls.event_booth_categories[1].id,
'name': 'Premium Booth',
}
),
(0, 0, {'booth_category_id': cls.event_booth_categories[1].id,
'name': 'Premium Booth 2',
}
),
],
'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'),
}
),
],
'event_type_ticket_ids': [
(0, 0, {'description': 'Ticket1 Description',
'name': 'Ticket1',
'product_id': cls.ticket_product.id,
'seats_max': 10,
}
),
(0, 0, {'description': 'Ticket2 Description',
'name': 'Ticket2',
'product_id': cls.ticket_product.id,
'price': 45,
}
)
],
'has_seats_limitation': True,
'name': 'Test Type',
'note': '<p>Template note</p>',
'question_ids': [
(0, 0, {'answer_ids':
[(0, 0, {'name': 'Q1-Answer1'}),
(0, 0, {'name': 'Q1-Answer2'}),
],
'question_type': 'simple_choice',
'once_per_order': False,
'title': 'Question1',
}
),
(0, 0, {'answer_ids':
[(0, 0, {'name': 'Q2-Answer1'}),
(0, 0, {'name': 'Q2-Answer2'}),
],
'question_type': 'simple_choice',
'once_per_order': False,
'title': 'Question2',
}
),
(0, 0, {'question_type': 'text_box',
'once_per_order': True,
'title': 'Question3',
}
),
],
'seats_max': 30,
'tag_ids': [(4, tag.id) for tag in cls.tags],
'ticket_instructions': '<p>Ticket Instructions</p>',
'website_menu': True,
})
# Stages
cls.stage_def = cls.env['event.stage'].create({
'name': 'First Stage',
'sequence': 0,
})
# Event data
# ------------------------------------------------------------
cls.event_base_vals = {
'name': 'Test Event',
'date_begin': cls.reference_now + timedelta(days=1),
'date_end': cls.reference_now + timedelta(days=5),
'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,
})
# 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')
ticket_1.start_sale_datetime = cls.reference_now + timedelta(hours=1)
ticket_2.start_sale_datetime = cls.reference_now + timedelta(hours=2)
# Website data
# ------------------------------------------------------------
cls.website = cls.env['website'].search([
('company_id', '=', cls.company_admin.id)
], limit=1)
cls.customer_data = [
{'email': 'customer.email.%02d@test.example.com' % x,
'name': 'My Customer %02d' % x,
'mobile': '04569999%02d' % x,
'partner_id': False,
'phone': '04560000%02d' % x,
} for x 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,
'partner_id': cls.env.ref('base.public_partner').id,
'phone': '04560000%02d' % x,
'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,
}), (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,
}), (0, 0, {
'question_id': cls.test_event.question_ids[2].id,
'value_text_box': 'CustomerAnswer%s' % x,
})
],
} for x 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)
])
def assertLeadConvertion(self, rule, registrations, partner=None, **expected):
super(TestEventFullCommon, self).assertLeadConvertion(rule, registrations, partner=partner, **expected)
lead = self.env['crm.lead'].sudo().search([
('registration_ids', 'in', registrations.ids),
('event_lead_rule_id', '=', rule.id)
])
for registration in registrations:
if not registration.registration_answer_ids:
continue
for answer in registration.registration_answer_ids:
self.assertIn(answer.question_id.title, lead.description)
if answer.question_type == 'simple_choice':
self.assertIn(answer.value_answer_id.name, lead.description)
else:
self.assertIn(answer.value_text_box, lead.description) # better: check multi line
class TestWEventCommon(HttpCaseWithUserDemo, HttpCaseWithUserPortal, MockVisitor):
def setUp(self):
super(TestWEventCommon, self).setUp()
# Context data: dates
# ------------------------------------------------------------
# Mock dates to have reproducible computed fields based on time
self.reference_now = datetime(2021, 12, 1, 10, 0, 0)
self.reference_today = datetime(2021, 12, 1)
self.event_product = self.env['product.product'].create({
'name': 'Test Event Registration',
'default_code': 'EVENT_REG',
'description_sale': 'Mighty Description',
'list_price': 10,
'standard_price': 30.0,
'detailed_type': 'event',
})
self.event_tag_category_1 = self.env['event.tag.category'].create({
'name': 'Type',
'sequence': 2,
})
self.event_tag_category_1_tag_1 = self.env['event.tag'].create({
'name': 'Online',
'sequence': 10,
'category_id': self.event_tag_category_1.id,
'color': 8,
})
self.env['event.event'].search(
[('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,
'tag_ids': [(4, self.event_tag_category_1_tag_1.id)],
# event if 8-18 in Europe/Brussels (DST) (first day: begins at 7, last day: ends at 17)
'date_tz': 'Europe/Brussels',
'date_begin': datetime.combine(self.reference_now, time(5, 0)) - timedelta(days=1),
'date_end': datetime.combine(self.reference_now, time(15, 0)) + timedelta(days=1),
# ticket informations
'event_ticket_ids': [
(0, 0, {
'name': 'Standard',
'product_id': self.event_product.id,
'price': 0,
}), (0, 0, {
'name': 'VIP',
'product_id': self.event_product.id,
'seats_max': 10,
})
],
# activate menus
'is_published': True,
'website_menu': True,
'website_track': True,
'website_track_proposal': True,
'exhibitor_menu': True,
'community_menu': True,
})
self.event_customer = self.env['res.partner'].create({
'name': 'Constantin Customer',
'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',
'email': 'brandon.freeman55@example.com',
'phone': '(355)-687-3262',
})
# ------------------------------------------------------------
# QUESTIONS
# ------------------------------------------------------------
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,
'once_per_order': False,
'answer_ids': [
(0, 0, {'name': 'Consumers'}),
(0, 0, {'name': 'Sales'}),
(0, 0, {'name': 'Research'}),
],
})
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,
'once_per_order': True,
})
# ------------------------------------------------------------
# TRACKS
# ------------------------------------------------------------
self.track_0 = self.env['event.track'].create({
'name': 'What This Event Is All About',
'event_id': self.event.id,
'stage_id': self.env.ref('website_event_track.event_track_stage3').id,
'date': self.reference_now + timedelta(hours=1),
'duration': 2,
'is_published': True,
'wishlisted_by_default': True,
'user_id': self.user_admin.id,
'partner_id': self.event_speaker.id,
})
self.track_1 = self.env['event.track'].create({
'name': 'Live Testimonial',
'event_id': self.event.id,
'stage_id': self.env.ref('website_event_track.event_track_stage3').id,
'date': self.reference_now - timedelta(minutes=30),
'duration': 0.75,
'is_published': True,
'user_id': self.user_admin.id,
'partner_id': self.event_speaker.id,
})
self.track_2 = self.env['event.track'].create({
'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),
'duration': 0.75,
'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,
})
self.env.flush_all()

View file

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
# 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
class TestEventCrm(TestEventFullCommon):
@classmethod
def setUpClass(cls):
super(TestEventCrm, cls).setUpClass()
cls.TICKET1_COUNT, cls.TICKET2_COUNT = 3, 1
ticket1 = cls.test_event.event_ticket_ids[0]
ticket2 = cls.test_event.event_ticket_ids[1]
(cls.test_rule_attendee + cls.test_rule_order).write({'event_id': cls.test_event.id})
# PREPARE SO DATA
# ------------------------------------------------------------
# adding some tickets to SO
cls.customer_so.write({
'order_line': [
(0, 0, {
'event_id': cls.test_event.id,
'event_ticket_id': ticket1.id,
'product_id': ticket1.product_id.id,
'product_uom_qty': cls.TICKET1_COUNT,
}), (0, 0, {
'event_id': cls.test_event.id,
'event_ticket_id': ticket2.id,
'product_id': ticket2.product_id.id,
'product_uom_qty': cls.TICKET2_COUNT,
'price_unit': 50,
})
]
})
@users('user_sales_salesman')
def test_event_crm_sale_customer(self):
""" Test a SO with a real customer set on it, check partner propagation
as well as group-based lead update. """
customer_so = self.env['sale.order'].browse(self.customer_so.id)
# adding some tickets to SO
t1_reg_vals = [
dict(customer_data,
partner_id=customer_so.partner_id.id,
sale_order_line_id=customer_so.order_line[0].id)
for customer_data in self.website_customer_data[:self.TICKET1_COUNT]
]
t1_registrations = self.env['event.registration'].create(t1_reg_vals)
# check effect: registrations, leads
self.assertEqual(self.test_event.registration_ids, t1_registrations)
self.assertEqual(len(self.test_rule_order.lead_ids), 1)
self.assertEqual(self.test_rule_order_done.lead_ids, self.env['crm.lead'])
# check lead converted based on registrations
self.assertLeadConvertion(self.test_rule_order, t1_registrations, partner=customer_so.partner_id)
# SO is confirmed -> missing registrations should be automatically added
# and added to the lead as part of the same group
customer_so.action_confirm()
self.assertEqual(customer_so.state, 'sale')
self.assertEqual(len(self.test_event.registration_ids), self.TICKET1_COUNT + self.TICKET2_COUNT)
self.assertEqual(len(self.test_rule_order.lead_ids), 1) # no new lead created
self.assertEqual(self.test_rule_order_done.lead_ids, self.env['crm.lead']) # this one still not triggered
# check existing lead has been updated with new registrations
self.assertLeadConvertion(self.test_rule_order, self.test_event.registration_ids, partner=customer_so.partner_id)
# Confirm registrations -> trigger the "DONE" rule, one new lead linked to all
# event registrations created in this test as all belong to the same SO
self.test_event.registration_ids.write({'state': 'done'})
self.assertLeadConvertion(self.test_rule_order_done, self.test_event.registration_ids, partner=customer_so.partner_id)
@users('user_sales_salesman')
def test_event_crm_sale_mixed_group(self):
""" Test a mixed sale order line creation. This should not happen in a customer
use case but should be supported by the code. """
public_partner = self.env.ref('base.public_partner')
public_so = self.env['sale.order'].create({
'partner_id': public_partner.id,
'order_line': [
(0, 0, {
'event_id': self.test_event.id,
'event_ticket_id': self.test_event.event_ticket_ids[0].id,
'product_id': self.test_event.event_ticket_ids[0].product_id.id,
'product_uom_qty': 2,
})
]
})
customer_so = self.env['sale.order'].browse(self.customer_so.id)
# make a multi-SO create
mixed_reg_vals = [
dict(self.website_customer_data[0],
partner_id=customer_so.partner_id.id,
sale_order_line_id=customer_so.order_line[0].id),
dict(self.website_customer_data[1],
partner_id=customer_so.partner_id.id,
sale_order_line_id=customer_so.order_line[0].id),
dict(self.website_customer_data[2],
partner_id=public_so.partner_id.id,
sale_order_line_id=public_so.order_line[0].id),
dict(self.website_customer_data[3],
partner_id=public_so.partner_id.id,
sale_order_line_id=public_so.order_line[0].id),
]
self.env['event.registration'].create(mixed_reg_vals)
public_regs = self.test_event.registration_ids.filtered(lambda reg: reg.sale_order_id == public_so)
self.assertEqual(len(public_regs), 2)
customer_regs = self.test_event.registration_ids.filtered(lambda reg: reg.sale_order_id == customer_so)
self.assertEqual(len(customer_regs), 2)
self.assertLeadConvertion(self.test_rule_order, public_regs, partner=None)
self.assertLeadConvertion(self.test_rule_order, customer_regs, partner=customer_so.partner_id)
@users('user_sales_salesman')
def test_event_crm_sale_public(self):
""" Test a SO with a public partner on it, then updated when SO is confirmed.
This somehow simulates a simplified website_event_sale flow. """
public_partner = self.env.ref('base.public_partner')
customer_so = self.env['sale.order'].browse(self.customer_so.id)
customer_so.write({
'partner_id': public_partner.id,
})
# adding some tickets to SO
t1_reg_vals = [
dict(customer_data,
partner_id=public_partner.id,
sale_order_line_id=customer_so.order_line[0].id)
for customer_data in self.website_customer_data[:self.TICKET1_COUNT]
]
t1_registrations = self.env['event.registration'].create(t1_reg_vals)
self.assertEqual(self.test_event.registration_ids, t1_registrations)
# check lead converted based on registrations
self.assertLeadConvertion(self.test_rule_order, t1_registrations, partner=None)
# SO is confirmed -> missing registrations should be automatically added
# BUT as public user -> no email -> not taken into account by rule
customer_so.action_confirm()
self.assertEqual(customer_so.state, 'sale')
self.assertEqual(len(self.test_event.registration_ids), self.TICKET1_COUNT + self.TICKET2_COUNT)
self.assertLeadConvertion(self.test_rule_order, t1_registrations, partner=None)
# SO has a customer set -> main contact of lead is updated accordingly
customer_so.write({'partner_id': self.event_customer.id})
self.assertLeadConvertion(self.test_rule_order, t1_registrations, partner=self.event_customer)
def test_event_update_lead(self):
"""Make sure that we update leads without issues when question's answer is added to an event attendee."""
self.env['event.lead.rule'].search([]).write({'active': False})
self.env['event.lead.rule'].create({
'name': 'test_event_lead_rule',
'lead_creation_basis': 'attendee',
'lead_creation_trigger': 'create',
'event_registration_filter': [['partner_id', '!=', False]],
'lead_type': 'lead',
})
event_registration = self.env['event.registration'].create({
'name': 'Event Registration without answers added at first',
'event_id': self.test_event.id,
'partner_id': self.event_customer.id,
})
event_registration.write({
'registration_answer_ids': [(0, 0, {
'question_id': self.test_event.question_ids[1].id,
'value_answer_id': self.test_event.question_ids[1].answer_ids[0].id,
})]
})
self.assertIn(self.test_event.question_ids[1].answer_ids[0].name, event_registration.lead_ids[0].description,
"lead description not updated with the answer to the question")

View file

@ -0,0 +1,78 @@
# 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.")

View file

@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from freezegun import freeze_time
from odoo import Command, exceptions
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
from odoo.tests.common import users
class TestEventEvent(TestEventFullCommon):
@users('event_user')
def test_event_create_wtype(self):
""" Test a single event creation with a type defining all sub records. """
event_type = self.env['event.type'].browse(self.test_event_type.ids)
event_values = dict(
self.event_base_vals,
event_type_id=event_type.id,
)
event = self.env['event.event'].create([event_values])
event.write({
'event_ticket_ids': [
Command.update(
event.event_ticket_ids[0].id,
{'start_sale_datetime': self.reference_now + timedelta(hours=1)},
),
Command.update(
event.event_ticket_ids[1].id,
{'start_sale_datetime': self.reference_now + timedelta(hours=2)},
)
],
})
# 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.note, '<p>Template note</p>')
self.assertTrue(event.register_menu)
self.assertEqual(len(event.question_ids), 3)
self.assertTrue(event.seats_limited)
self.assertEqual(event.seats_max, 30)
self.assertEqual(event.stage_id, self.stage_def)
self.assertEqual(event.tag_ids, self.tags)
self.assertTrue(event.website_menu)
# check time dependent computation: before event
with freeze_time(self.reference_now):
self.assertFalse(event.is_finished)
self.assertFalse(event.is_ongoing)
self.assertFalse(event.event_registrations_started)
# check time dependent computation: registrations started
with freeze_time(self.reference_now + timedelta(hours=1)):
event.invalidate_model(['is_finished', 'is_ongoing', 'event_registrations_started'])
self.assertFalse(event.is_finished)
self.assertFalse(event.is_ongoing)
self.assertTrue(event.event_registrations_started)
# check time dependent computation: during event
with freeze_time(self.reference_now + timedelta(days=1, hours=1)):
event.invalidate_model(['is_finished', 'is_ongoing', 'event_registrations_started'])
self.assertFalse(event.is_finished)
self.assertTrue(event.is_ongoing)
self.assertTrue(event.event_registrations_started)
@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')
# check initial data
self.assertTrue(test_event.event_registrations_started)
self.assertEqual(test_event.seats_available, 30)
self.assertEqual(ticket_1.seats_available, 10)
self.assertTrue(ticket_1.sale_available)
self.assertEqual(ticket_2.seats_available, 0)
self.assertFalse(ticket_2.sale_available)
# make 9 registrations (let 1 on ticket)
with 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,
}
for x in range(0, 9)
])
# generated emails from scheduler
self.assertEqual(len(self._new_mails), 9)
# event and ticket seats update
self.assertEqual(len(test_event.registration_ids), 9)
self.assertEqual(test_event.seats_available, 21)
self.assertEqual(ticket_1.seats_available, 1)
self.assertEqual(ticket_2.seats_available, 0)
# prevent registration due to ticket limit
with 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,
}
for x in range(0, 2)
])
# make 20 registrations (on free ticket)
with 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,
}
for x in range(0, 20)
])
# event and ticket seats update
self.assertEqual(len(test_event.registration_ids), 29)
self.assertEqual(test_event.seats_available, 1)
self.assertEqual(ticket_1.seats_available, 1)
self.assertEqual(ticket_2.seats_available, 0)
# prevent registration due to event limit
with 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,
}
for x in range(0, 2)
])

View file

@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from freezegun import freeze_time
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
class TestTemplateRefModel(TestWEventCommon):
def test_template_ref_delete_lines(self):
""" When deleting a template, related lines should be deleted too """
event_type = self.env['event.type'].create({
'name': 'Event Type',
'default_timezone': 'Europe/Brussels',
'event_type_mail_ids': [
(0, 0, {
'interval_unit': 'now',
'interval_type': 'after_sub',
'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_subscription')}),
(0, 0, {
'interval_unit': 'now',
'interval_type': 'after_sub',
'notification_type': 'sms',
'template_ref': 'sms.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event_sms.sms_template_data_event_registration')}),
],
})
template_mail = event_type.event_type_mail_ids[0].template_ref
template_sms = event_type.event_type_mail_ids[1].template_ref
event = self.env['event.event'].create({
'name': 'event mail template removed',
'event_type_id': event_type.id,
'date_begin': datetime(2020, 2, 1, 8, 30, 0),
'date_end': datetime(2020, 2, 4, 18, 45, 0),
'date_tz': 'Europe/Brussels',
})
self.assertEqual(len(event_type.event_type_mail_ids), 2)
self.assertEqual(len(event.event_mail_ids), 2)
template_mail.unlink()
self.assertEqual(len(event_type.event_type_mail_ids.exists()), 1)
self.assertEqual(len(event.event_mail_ids.exists()), 1)
template_sms.unlink()
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),
]
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')
# Add registrations
self.env['event.registration'].create([{
'event_id': test_event.id,
'name': 'RegistrationUnconfirmed',
'email': 'Registration@Unconfirmed.com',
'state': 'draft',
}, {
'event_id': test_event.id,
'name': 'RegistrationCanceled',
'email': 'Registration@Canceled.com',
'state': 'cancel',
}, {
'event_id': test_event.id,
'name': 'RegistrationConfirmed',
'email': 'Registration@Confirmed.com',
'state': 'open',
}])
with self.mock_mail_gateway(), self.mockSMSGateway():
mail_scheduler.execute()
self.assertEqual(len(self._new_mails), 2, 'Mails were not created')
self.assertEqual(len(self._new_sms), 2, 'SMS were not created')
self.assertEqual(test_event.seats_expected, 2, 'Wrong number of expected seats (attendees)')
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')

View file

@ -0,0 +1,213 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.addons.test_event_full.tests.common import TestEventFullCommon
from odoo.exceptions import AccessError
from odoo.tests import tagged
from odoo.tests.common import users
from odoo.tools import mute_logger
@tagged('security')
class TestEventSecurity(TestEventFullCommon):
@users('user_employee')
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_model')
def test_event_access_employee(self):
# Event: read ok
event = self.test_event.with_user(self.env.user)
event.read(['name'])
# Event: read only
with self.assertRaises(AccessError):
self.env['event.event'].create({
'name': 'TestEvent',
'date_begin': datetime.now() + timedelta(days=-1),
'date_end': datetime.now() + timedelta(days=1),
'seats_limited': True,
'seats_max': 10,
})
with self.assertRaises(AccessError):
event.write({
'name': 'TestEvent Modified',
})
# Event Type
with self.assertRaises(AccessError):
self.test_event_type.with_user(self.env.user).read(['name'])
with self.assertRaises(AccessError):
self.test_event_type.with_user(self.env.user).write({'name': 'Test Write'})
# Event Stage
with self.assertRaises(AccessError):
self.env['event.stage'].create({
'name': 'TestStage',
})
# Event Registration
with self.assertRaises(AccessError):
self.env['event.registration'].search([])
@users('user_eventregistrationdesk')
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_model')
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 only
with self.assertRaises(AccessError):
event.name = 'Test'
with self.assertRaises(AccessError):
event.unlink()
# Event Registration
registration = self.env['event.registration'].create({
'event_id': event.id,
})
self.assertEqual(registration.event_id.name, event.name, 'Registration users should be able to read')
registration.name = 'Test write'
with self.assertRaises(AccessError):
registration.unlink()
@users('user_eventuser')
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_model')
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.write({'name': 'New name'})
self.env['event.event'].create({
'name': 'Event',
'date_begin': datetime.now() + timedelta(days=-1),
'date_end': datetime.now() + timedelta(days=1),
})
# Event: cannot unlink
with self.assertRaises(AccessError):
event.unlink()
# Event Type
with self.assertRaises(AccessError):
self.env['event.type'].create({
'name': 'ManagerEventType',
'event_type_mail_ids': [(5, 0), (0, 0, {
'interval_nbr': 1, 'interval_unit': 'days', 'interval_type': 'before_event',
'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')})]
})
@users('user_eventmanager')
@mute_logger('odoo.models.unlink', 'odoo.addons.base.models.ir_model')
def test_event_access_event_manager(self):
# Event Type
event_type = self.env['event.type'].create({
'name': 'ManagerEventType',
'event_type_mail_ids': [(5, 0), (0, 0, {
'interval_nbr': 1, 'interval_unit': 'days', 'interval_type': 'before_event',
'template_ref': 'mail.template,%i' % self.env['ir.model.data']._xmlid_to_res_id('event.event_reminder')})]
})
event_type.write({'name': 'New Name'})
# Event
event = self.env['event.event'].create({
'name': 'ManagerEvent',
'date_begin': datetime.now() + timedelta(days=-1),
'date_end': datetime.now() + timedelta(days=1),
})
event.write({'name': 'New Event Name'})
# Event Stage
stage = self.env['event.stage'].create({'name': 'test'})
stage.write({'name': 'ManagerTest'})
event.write({'stage_id': stage.id})
# Event Registration
registration = self.env['event.registration'].create({'event_id': event.id, 'name': 'Myself'})
registration.write({'name': 'Myself2'})
registration.unlink()
event.unlink()
stage.unlink()
event_type.unlink()
# Settings access rights required to enable some features
self.user_eventmanager.write({'groups_id': [
(3, self.env.ref('base.group_system').id),
(4, self.env.ref('base.group_erp_manager').id)
]})
with self.assertRaises(AccessError):
event_config = self.env['res.config.settings'].with_user(self.user_eventmanager).create({
})
event_config.execute()
def test_implied_groups(self):
"""Test that the implied groups are correctly set.
- Event Manager imply Event User
- Event User imply Registration user
"""
# Event Manager
self.assertTrue(
self.user_eventmanager.has_group('event.group_event_user'),
'The event manager group must imply the event user group')
self.assertTrue(
self.user_eventmanager.has_group('event.group_event_registration_desk'),
'The event manager group must imply the registration user group')
# Event User
self.assertTrue(
self.user_eventuser.has_group('event.group_event_registration_desk'),
'The event user group must imply the event user group')
self.assertFalse(
self.user_eventuser.has_group('event.group_event_manager'),
'The event user group must not imply the event user group')
# Registration User
self.assertFalse(
self.user_eventregistrationdesk.has_group('event.group_event_manager'),
'The event registration group must not imply the event user manager')
self.assertFalse(
self.user_eventregistrationdesk.has_group('event.group_event_user'),
'The event registration group must not imply the event user group')
def test_multi_companies(self):
"""Test ACLs with multi company. """
company_1 = self.env.ref("base.main_company")
company_2 = self.env['res.company'].create({'name': 'Company 2'})
user_company_1 = self.user_eventuser
event_company_1, event_company_2 = self.env['event.event'].create([
{
'name': 'Event Company 1',
'date_begin': datetime.now() + timedelta(days=-1),
'date_end': datetime.now() + timedelta(days=1),
'company_id': company_1.id,
}, {
'name': 'Event Company 2',
'date_begin': datetime.now() + timedelta(days=-1),
'date_end': datetime.now() + timedelta(days=1),
'company_id': company_2.id,
}
])
registration_company_1, registration_company_2 = self.env['event.registration'].create([
{
'name': 'Registration Company 1',
'event_id': event_company_1.id,
'company_id': company_1.id,
}, {
'name': 'Registration Company 2',
'event_id': event_company_2.id,
'company_id': company_2.id,
}
])
result = self.env['event.event'].with_user(user_company_1).search([])
self.assertIn(event_company_1, result, 'You must be able to read the events in your company')
self.assertNotIn(event_company_2, result, 'You must not be able to read events outside of your company')
result = self.env['event.registration'].with_user(user_company_1).search([])
self.assertIn(registration_company_1, result, 'You must be able to read the registrations in your company')
self.assertNotIn(registration_company_2, result, 'You must not be able to read registrations outside of your company')

View file

@ -0,0 +1,483 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
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
@tagged('event_performance', 'post_install', '-at_install', '-standard')
class EventPerformanceCase(TestEventFullCommon):
def setUp(self):
super(EventPerformanceCase, self).setUp()
# patch registry to simulate a ready environment
self.patch(self.env.registry, 'ready', True)
self._flush_tracking()
def _flush_tracking(self):
""" Force the creation of tracking values notably, and ensure tests are
reproducible. """
self.env.flush_all()
self.cr.flush()
@tagged('event_performance', 'post_install', '-at_install', '-standard')
class TestEventPerformance(EventPerformanceCase):
@users('event_user')
@warmup
def test_event_create_batch_notype(self):
""" Test multiple event creation (import) """
batch_size = 20
# simple without type involved
with freeze_time(self.reference_now), self.assertQueryCount(event_user=335): # tef 335
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
website_menu=False)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
@users('event_user')
@warmup
def test_event_create_batch_notype_website(self):
""" Test multiple event creation (import) """
batch_size = 20
# simple without type involved + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=5368): # tef 4944 / com 4943
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
website_menu=True
)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
@users('event_user')
@warmup
def test_event_create_batch_wtype(self):
""" Test multiple event creation (import) """
batch_size = 20
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
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,
website_menu=False,
)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
@users('event_user')
@warmup
def test_event_create_batch_wtype_website(self):
""" Test multiple event creation (import) """
batch_size = 20
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
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,
)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
@users('event_user')
@warmup
def test_event_create_form_notype(self):
""" Test a single event creation using Form """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = False
if has_social:
event_form.social_menu = False
_event = event_form.save()
@users('event_user')
@warmup
def test_event_create_form_notype_website(self):
""" Test a single event creation using Form """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.website_menu = True
if has_social:
event_form.social_menu = False
_event = event_form.save()
@users('event_user')
@warmup
def test_event_create_form_type_website(self):
""" Test a single event creation using Form """
event_type = self.env['event.type'].browse(self.test_event_type.ids)
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
with self.debug_mode():
with Form(self.env['event.event']) as event_form:
event_form.name = 'Test Event'
event_form.date_begin = self.reference_now + timedelta(days=1)
event_form.date_end = self.reference_now + timedelta(days=5)
event_form.event_type_id = event_type
if has_social:
event_form.social_menu = False
@users('event_user')
@warmup
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
website_menu=False
)
self.env['event.event'].create([event_values])
@users('event_user')
@warmup
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
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])
@users('event_user')
@warmup
def test_event_create_single_wtype(self):
""" Test a single event creation """
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
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,
website_menu=False
)
self.env['event.event'].create([event_values])
@users('event_user')
@warmup
def test_event_create_single_wtype_website(self):
""" Test a single event creation """
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
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])
@tagged('event_performance', 'registration_performance', 'post_install', '-at_install', '-standard')
class TestRegistrationPerformance(EventPerformanceCase):
@users('event_user')
@warmup
def test_registration_create_batch(self):
""" Test multiple registrations creation (batch of 10 without partner
and batch of 10 with partner)
# TODO: with self.profile(collectors=['sql']) as _profile:
"""
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
event_id=event.id)
for reg_data in self.customer_data
]
registration_values += [
{'event_id': event.id,
'partner_id': partner.id,
} for partner in self.partners
]
_registrations = self.env['event.registration'].create(registration_values)
@users('event_user')
@warmup
def test_registration_create_batch_nolead(self):
""" Test multiple registrations creation (batch of 10 without partner
and batch of 10 with partner)
# TODO: with self.profile(collectors=['sql']) as _profile:
"""
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
event_id=event.id)
for reg_data in self.customer_data
]
registration_values += [
{'event_id': event.id,
'partner_id': partner.id,
} for partner in self.partners
]
_registrations = self.env['event.registration'].with_context(event_lead_rule_skip=True).create(registration_values)
@users('event_user')
@warmup
def test_registration_create_batch_website(self):
""" Test multiple registrations creation (batch of 10 without partner
and batch of 10 with partner) with some additional informations (register
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = [
dict(reg_data,
event_id=event.id)
for reg_data in self.website_customer_data
]
registration_values += [
{'event_id': event.id,
'partner_id': partner.id,
'registration_answer_ids': self.website_customer_data[0]['registration_answer_ids'],
} for partner in self.partners
]
_registrations = self.env['event.registration'].create(registration_values)
@users('event_user')
@warmup
def test_registration_create_form_customer(self):
""" 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
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()
@users('event_user')
@warmup
def test_registration_create_form_partner(self):
""" 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
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.partner_id = self.partners[0]
_registration = reg_form.save()
@users('event_user')
@warmup
def test_registration_create_form_partner_nolead(self):
""" 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
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
reg_form.partner_id = self.partners[0]
_registration = reg_form.save()
@users('event_user')
@warmup
def test_registration_create_single_customer(self):
""" Test a single registration creation """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.customer_data[0],
event_id=event.id)
_registration = self.env['event.registration'].create([registration_values])
@users('event_user')
@warmup
def test_registration_create_single_partner(self):
""" Test a single registration creation """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
'partner_id': self.partners[0].id,
}
_registration = self.env['event.registration'].create([registration_values])
@users('event_user')
@warmup
def test_registration_create_single_partner_nolead(self):
""" Test a single registration creation """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = {
'event_id': event.id,
'partner_id': self.partners[0].id,
}
_registration = self.env['event.registration'].with_context(event_lead_rule_skip=True).create([registration_values])
@users('event_user')
@warmup
def test_registration_create_single_website(self):
""" Test a single registration creation """
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
self.env.cr._now = self.reference_now # force create_date to check schedulers
registration_values = dict(
self.website_customer_data[0],
event_id=event.id)
_registration = self.env['event.registration'].create([registration_values])
@tagged('event_performance', 'event_online', 'post_install', '-at_install', '-standard')
class TestOnlineEventPerformance(EventPerformanceCase, UtilPerf):
@classmethod
def setUpClass(cls):
super(TestOnlineEventPerformance, cls).setUpClass()
# if website_livechat is installed, disable it
if 'channel_id' in cls.env['website']:
cls.env['website'].search([]).channel_id = False
cash_journal = cls.env['account.journal'].create({
'name': 'Cash - Test',
'type': 'cash',
'code': 'CASH - Test'
})
cls.env['payment.provider'].search([('code', '=', 'test')]).write({
'journal_id': cash_journal.id,
'state': 'test'
})
# clean even page to make it reproducible
cls.env['event.event'].search([('name', '!=', 'Test Event')]).write({'active': False})
# create noise for events
cls.noise_events = cls.env['event.event'].create([
{'name': 'Event %02d' % idx,
'date_begin': cls.reference_now + timedelta(days=(-2 + int(idx/10))),
'date_end': cls.reference_now + timedelta(days=5),
'is_published': True,
}
for idx in range(0, 50)
])
def _test_url_open(self, url):
url += ('?' not in url and '?' or '') + '&debug=disable-t-cache'
return self.url_open(url)
@warmup
def test_event_page_event_manager(self):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=36): # tef 35
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
def test_event_page_public(self):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
with self.assertQueryCount(default=27):
self._test_url_open('/event/%i' % self.test_event.id)
@warmup
def test_events_browse_event_manager(self):
# website customer data
with freeze_time(self.reference_now):
self.authenticate('user_eventmanager', 'user_eventmanager')
with self.assertQueryCount(default=39): # tef 38
self._test_url_open('/event')
@warmup
def test_events_browse_public(self):
# website customer data
with freeze_time(self.reference_now):
self.authenticate(None, None)
with self.assertQueryCount(default=28):
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,
# )
# # 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)

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from freezegun import freeze_time
from odoo import tests
from odoo.addons.test_event_full.tests.common import TestWEventCommon
@tests.common.tagged('event_online', 'post_install', '-at_install')
class TestWEventRegister(TestWEventCommon):
def test_register(self):
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
)
new_registrations = self.event.registration_ids
visitor = new_registrations.visitor_id
# check registration content
self.assertEqual(len(new_registrations), 2)
self.assertEqual(
set(new_registrations.mapped("name")),
set(["Raoulette Poiluchette", "Michel Tractopelle"])
)
self.assertEqual(
set(new_registrations.mapped("phone")),
set(["0456112233", "0456332211"])
)
self.assertEqual(
set(new_registrations.mapped("email")),
set(["raoulette@example.com", "michel@example.com"])
)
# check visitor stored information
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")

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="event_registration_template_report">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="registration">
<t t-call="web.external_layout">
<div class="page">
<p>This is a sample of an external report.</p>
</div>
</t>
</t>
</t>
</template>
</odoo>

View file

@ -0,0 +1,49 @@
# Mail Tests
This module contains tests related to mail. Those are
present in a separate module as it contains models used only to perform
tests independently to functional aspects of other models.
## Installation
```bash
pip install odoo-bringout-oca-ocb-test_mail
```
## Dependencies
This addon depends on:
- mail
- test_performance
## Manifest Information
- **Name**: Mail Tests
- **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_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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
# Dependencies
This addon depends on:
- [mail](../../odoo-bringout-oca-ocb-mail)
- test_performance

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more