19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import itertools
import random
from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import fields
from odoo.addons.mail.tests import common as mail_test
class TestDigestCommon(mail_test.MailCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.company_1 = cls.env.company
cls.company_2 = cls.env['res.company'].create({'name': 'Digest Company 2'})
context = {
'start_datetime': datetime.now() - relativedelta(days=1),
'end_datetime': datetime.now() + relativedelta(days=1),
}
cls.all_digests = cls.env['digest.digest'].with_context(context).create([{
'name': 'Digest 1',
'company_id': cls.env.company.id,
'kpi_mail_message_total': True,
'kpi_res_users_connected': True,
'periodicity': 'daily',
}, {
'name': 'Digest 2',
'company_id': cls.company_2.id,
}, {
'name': 'Digest 3',
'company_id': False,
}])
cls.digest_1, cls.digest_2, cls.digest_3 = cls.all_digests
@classmethod
def _setup_messages(cls):
""" Remove all existing messages, then create a bunch of them on random
partners with the correct types in correct time-bucket:
- 3 in the previous 24h
- 5 more in the 6 days before that for a total of 8 in the previous week
- 7 more in the 20 days before *that* (because digest doc lies and is
based around weeks and months not days), for a total of 15 in the
previous month
"""
# regular employee can't necessarily access "private" addresses
partners = cls.env['res.partner'].search([])
messages = cls.env['mail.message']
counter = itertools.count()
now = fields.Datetime.now()
for count, (low, high) in [
(3, (0 * 24, 1 * 24)),
(5, (1 * 24, 7 * 24)),
(7, (7 * 24, 27 * 24)),
]:
for __ in range(count):
create_date = now - relativedelta(hours=random.randint(low + 1, high - 1))
messages += random.choice(partners).message_post(
author_id=cls.partner_admin.id,
body=f"Awesome Partner! ({next(counter)})",
email_from=cls.partner_admin.email_formatted,
message_type='comment',
subtype_xmlid='mail.mt_comment',
# adjust top and bottom by 1h to avoid overlapping with the
# range limit and dropping out of the digest's selection thing
create_date=create_date,
)
cls.env.flush_all()

View file

@ -1,9 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import itertools
import random
from ast import literal_eval
from contextlib import contextmanager
from freezegun import freeze_time
@ -11,17 +8,18 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from lxml import html
from unittest.mock import patch
from werkzeug.urls import url_encode, url_join
from werkzeug.urls import url_encode
from odoo import fields, SUPERUSER_ID
from odoo import SUPERUSER_ID
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
from odoo.addons.mail.tests import common as mail_test
from odoo.addons.digest.tests.common import TestDigestCommon
from odoo.addons.mail.tests.common import MailCommon
from odoo.tests import tagged
from odoo.tests.common import users
from odoo.tools import mute_logger
from odoo.tools import mute_logger, urls
class TestDigest(mail_test.MailCommon):
class TestDigest(TestDigestCommon):
@contextmanager
def mock_datetime_and_now(self, mock_dt):
@ -35,13 +33,12 @@ class TestDigest(mail_test.MailCommon):
@classmethod
def setUpClass(cls):
super(TestDigest, cls).setUpClass()
cls._activate_multi_company()
cls.reference_datetime = datetime(2024, 2, 13, 13, 30, 0)
# clean messages
cls.env['mail.message'].search([
('subtype_id', '=', cls.env.ref('mail.mt_comment').id),
('message_type', 'in', ['comment', 'email']),
('message_type', 'in', ('comment', 'email', 'email_outgoing')),
]).unlink()
cls._setup_messages()
@ -68,46 +65,13 @@ class TestDigest(mail_test.MailCommon):
}
])
@classmethod
def _setup_messages(cls):
""" Remove all existing messages, then create a bunch of them on random
partners with the correct types in correct time-bucket:
- 3 in the previous 24h
- 5 more in the 6 days before that for a total of 8 in the previous week
- 7 more in the 20 days before *that* (because digest doc lies and is
based around weeks and months not days), for a total of 15 in the
previous month
"""
# regular employee can't necessarily access "private" addresses
partners = cls.env['res.partner'].search([('type', '!=', 'private')])
messages = cls.env['mail.message']
counter = itertools.count()
now = fields.Datetime.now()
for count, (low, high) in [(3, (0 * 24, 1 * 24)),
(5, (1 * 24, 7 * 24)),
(7, (7 * 24, 27 * 24)),
]:
for _ in range(count):
create_date = now - relativedelta(hours=random.randint(low + 1, high - 1))
messages += random.choice(partners).message_post(
author_id=cls.partner_admin.id,
body=f"Awesome Partner! ({next(counter)})",
email_from=cls.partner_admin.email_formatted,
message_type='comment',
subtype_xmlid='mail.mt_comment',
# adjust top and bottom by 1h to avoid overlapping with the
# range limit and dropping out of the digest's selection thing
create_date=create_date,
)
cls.env.flush_all()
@classmethod
def _setup_logs_for_users(cls, res_users, log_dt):
with cls.mock_datetime_and_now(cls, log_dt):
for user in res_users:
cls.env['res.users.log'].with_user(user).create({})
cls.env['res.users.log'].with_user(SUPERUSER_ID).create({
'create_uid': user.id,
})
@users('admin')
def test_assert_initial_values(self):
@ -124,9 +88,27 @@ class TestDigest(mail_test.MailCommon):
self.assertEqual(test_digest_2.periodicity, 'weekly')
self.assertEqual(test_digest_2.user_ids, self.user_admin + self.user_employee)
@users('admin')
def test_digest_kpi_res_users_connected_value(self):
self.env['res.users.log'].with_user(SUPERUSER_ID).search([]).unlink()
# Sanity check
initial_values = self.all_digests.mapped('kpi_res_users_connected_value')
self.assertEqual(initial_values, [0, 0, 0])
self.env['res.users'].with_user(self.user_employee)._update_last_login()
self.env['res.users'].with_user(self.user_admin)._update_last_login()
self.all_digests.invalidate_recordset()
self.assertEqual(self.digest_1.kpi_res_users_connected_value, 2)
self.assertEqual(self.digest_2.kpi_res_users_connected_value, 0,
msg='This KPI is in an other company')
self.assertEqual(self.digest_3.kpi_res_users_connected_value, 2,
msg='This KPI has no company, should take the current one')
@users('admin')
def test_digest_numbers(self):
digest = self.env['digest.digest'].browse(self.test_digest.ids)
digest = self.env['digest.digest'].browse(self.digest_1.ids)
digest._action_subscribe_users(self.user_employee)
# digest creates its mails in auto_delete mode so we need to capture
@ -142,7 +124,7 @@ class TestDigest(mail_test.MailCommon):
self.assertEqual(mail.email_from, self.company_admin.email_formatted)
self.assertEqual(mail.state, 'outgoing', 'Mail should use the queue')
kpi_message_values = html.fromstring(mail.body_html).xpath('//div[@data-field="kpi_mail_message_total"]//*[hasclass("kpi_value")]/text()')
kpi_message_values = html.fromstring(mail.body_html).xpath('//table[@data-field="kpi_mail_message_total"]//*[hasclass("kpi_value")]/text()')
self.assertEqual(
[t.strip() for t in kpi_message_values],
['3', '8', '15']
@ -150,7 +132,7 @@ class TestDigest(mail_test.MailCommon):
@users('admin')
def test_digest_subscribe(self):
digest_user = self.test_digest.with_user(self.user_employee)
digest_user = self.digest_1.with_user(self.user_employee)
self.assertFalse(digest_user.is_subscribed)
# subscribe a user so at least one mail gets sent
@ -160,8 +142,6 @@ class TestDigest(mail_test.MailCommon):
"check the user was subscribed as action_subscribe will silently "
"ignore subs of non-employees"
)
# unsubscribe
digest_user.action_unsubscribe()
self.assertFalse(digest_user.is_subscribed)
@ -180,7 +160,7 @@ class TestDigest(mail_test.MailCommon):
""",
})
with self.mock_mail_gateway():
self.test_digest._action_send_to_user(self.user_employee)
self.digest_1._action_send_to_user(self.user_employee)
self.assertEqual(len(self._new_mails), 1, "A new Email should have been created")
sent_mail_body = html.fromstring(self._new_mails.body_html)
values_to_check = [
@ -237,7 +217,7 @@ class TestDigest(mail_test.MailCommon):
@users('admin')
def test_digest_tone_down_wlogs(self):
digest = self.env['digest.digest'].browse(self.test_digest.ids)
digest = self.env['digest.digest'].browse(self.digest_1.ids)
digest._action_subscribe_users(self.user_employee)
for logs, (periodicity, run_date), (exp_periodicity, exp_run_date, msg) in zip(
@ -304,8 +284,7 @@ class TestDigest(mail_test.MailCommon):
'periodicity': periodicity,
})
for log_user, log_dt in logs:
with self.mock_datetime_and_now(log_dt):
self.env['res.users.log'].with_user(log_user).create({})
self._setup_logs_for_users(log_user, log_dt)
with self.mock_datetime_and_now(self.reference_datetime), \
self.mock_mail_gateway():
@ -313,11 +292,11 @@ class TestDigest(mail_test.MailCommon):
self.assertEqual(digest.next_run_date, exp_run_date)
self.assertEqual(digest.periodicity, exp_periodicity)
self.env['res.users.log'].sudo().search([]).unlink()
self.env['res.users.log'].with_user(SUPERUSER_ID).search([]).unlink()
@tagged("digest", "mail_mail", "-at_install", "post_install")
class TestUnsubscribe(mail_test.MailCommon, HttpCaseWithUserDemo):
class TestUnsubscribe(MailCommon, HttpCaseWithUserDemo):
def setUp(self):
super(TestUnsubscribe, self).setUp()
@ -347,7 +326,7 @@ class TestUnsubscribe(mail_test.MailCommon, HttpCaseWithUserDemo):
headers = literal_eval(mail.headers)
unsubscribe_url = headers.get("List-Unsubscribe", "").strip("<>")
self.assertTrue(unsubscribe_url)
self.opener.post(unsubscribe_url)
self.url_open(unsubscribe_url, method='POST')
self.assertFalse(digest.user_ids, "Users should have been unsubscribed from digest")
@ -407,9 +386,5 @@ class TestUnsubscribe(mail_test.MailCommon, HttpCaseWithUserDemo):
else:
unsubscribe_route = "unsubscribe"
url = url_join(self.base_url, f'digest/{self.test_digest.id}/{unsubscribe_route}?{url_encode(url_params)}')
if method == 'GET':
return self.opener.get(url, timeout=10, allow_redirects=True)
if method == 'POST':
return self.opener.post(url, timeout=10, allow_redirects=True)
raise Exception(f'Invalid method {method}')
url = urls.urljoin(self.base_url, f'digest/{self.test_digest.id}/{unsubscribe_route}?{url_encode(url_params)}')
return self.url_open(url, timeout=10, allow_redirects=True, method=method)