Initial commit: Mail packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:51 +02:00
commit 4e53507711
1948 changed files with 751201 additions and 0 deletions

View file

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import chatbot_common
from . import test_chatbot_form_ui
from . import test_chatbot_internals
from . import test_get_mail_channel
from . import test_im_livechat_report
from . import test_im_livechat_support_page
from . import test_message

View file

@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import common
class ChatbotCase(common.TransactionCase):
@classmethod
def setUpClass(cls):
super(ChatbotCase, cls).setUpClass()
cls.chatbot_script = cls.env['chatbot.script'].create({
'title': 'Testing Bot',
})
ChatbotScriptStep = cls.env['chatbot.script.step'].sudo()
[
cls.step_hello,
cls.step_welcome,
cls.step_dispatch,
] = ChatbotScriptStep.create([{
'step_type': 'text',
'message': "Hello! I'm a bot!",
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'text',
'message': "I help lost visitors find their way.",
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'question_selection',
'message': "How can I help you?",
'chatbot_script_id': cls.chatbot_script.id,
}])
[
cls.step_dispatch_buy_software,
cls.step_dispatch_pricing,
cls.step_dispatch_operator,
] = cls.env['chatbot.script.answer'].sudo().create([{
'name': 'I want to buy the software',
'script_step_id': cls.step_dispatch.id,
}, {
'name': 'Pricing Question',
'script_step_id': cls.step_dispatch.id,
}, {
'name': "I want to speak with an operator",
'script_step_id': cls.step_dispatch.id,
}])
[
cls.step_pricing_contact_us,
cls.step_email,
cls.step_email_validated,
cls.step_forward_operator,
cls.step_no_one_available,
cls.step_no_operator_dispatch,
] = ChatbotScriptStep.create([{
'step_type': 'text',
'message': 'For any pricing question, feel free ton contact us at pricing@mycompany.com',
'triggering_answer_ids': [(4, cls.step_dispatch_pricing.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'question_email',
'message': 'Can you give us your email please?',
'triggering_answer_ids': [(4, cls.step_dispatch_buy_software.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'text',
'message': 'Your email is validated, thank you!',
'triggering_answer_ids': [(4, cls.step_dispatch_buy_software.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'forward_operator',
'message': 'I will transfer you to a human.',
'triggering_answer_ids': [(4, cls.step_dispatch_operator.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'text',
'message': 'Sorry, you will have to stay with me for a while',
'triggering_answer_ids': [(4, cls.step_dispatch_operator.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'question_selection',
'message': 'So... What can I do to help you?',
'triggering_answer_ids': [(4, cls.step_dispatch_operator.id)],
'chatbot_script_id': cls.chatbot_script.id,
}])
cls.step_no_operator_just_leaving = cls.env['chatbot.script.answer'].sudo().create({
'name': 'I will be leaving then',
'script_step_id': cls.step_no_operator_dispatch.id,
})
[
cls.step_just_leaving,
cls.step_pricing_thank_you,
cls.step_ask_website,
cls.step_ask_feedback,
cls.step_goodbye,
] = ChatbotScriptStep.create([{
'step_type': 'text',
'message': "Ok, I'm sorry I was not able to help you",
'triggering_answer_ids': [(4, cls.step_no_operator_just_leaving.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'text',
'message': 'We will reach back to you as soon as we can!',
'triggering_answer_ids': [(4, cls.step_dispatch_pricing.id)],
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'free_input_single',
'message': 'Would you mind providing your website address?',
'sequence': 97, # makes it easier for other modules to add steps before this one
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'free_input_multi',
'message': 'Great, do you want to leave any feedback for us to improve?',
'sequence': 98, # makes it easier for other modules to add steps before this one
'chatbot_script_id': cls.chatbot_script.id,
}, {
'step_type': 'text',
'message': "Ok bye!",
'sequence': 99, # makes it easier for other modules to add steps before this one
'chatbot_script_id': cls.chatbot_script.id,
}])
cls.livechat_channel = cls.env['im_livechat.channel'].create({
'name': 'Test Channel',
'rule_ids': [(0, 0, {
'chatbot_script_id': cls.chatbot_script.id,
})]
})
@classmethod
def _post_answer_and_trigger_next_step(cls, mail_channel, answer, chatbot_script_answer=False):
mail_message = mail_channel.message_post(body=answer)
if chatbot_script_answer:
cls.env['chatbot.message'].search([
('mail_message_id', '=', mail_message.id)
], limit=1).user_script_answer_id = chatbot_script_answer.id
# sudo: chatbot.script.step - members of a channel can access the current chatbot step
next_step = mail_channel.chatbot_current_step_id.sudo()._process_answer(mail_channel, mail_message.body)
next_step._process_step(mail_channel)

View file

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests.common import TransactionCase
class TestImLivechatCommon(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.operators = cls.env['res.users'].create([{
'name': 'Michel',
'login': 'michel',
'livechat_username': "Michel Operator",
'email': 'michel@example.com',
}, {
'name': 'Paul',
'login': 'paul'
}, {
'name': 'Pierre',
'login': 'pierre'
}, {
'name': 'Jean',
'login': 'jean'
}, {
'name': 'Georges',
'login': 'georges'
}])
cls.visitor_user = cls.env['res.users'].create({
'name': 'Rajesh',
'login': 'rajesh',
'country_id': cls.env.ref('base.in').id,
'email': 'rajesh@example.com',
})
cls.livechat_channel = cls.env['im_livechat.channel'].create({
'name': 'The channel',
'user_ids': [(6, 0, cls.operators.ids)]
})
def setUp(self):
super().setUp()
def get_available_users(_):
return self.operators
self.patch(type(self.env['im_livechat.channel']), '_get_available_users', get_available_users)

View file

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import tests
from odoo.addons.base.tests.common import HttpCaseWithUserDemo
@tests.tagged('post_install', '-at_install')
class TestLivechatChatbotFormUI(HttpCaseWithUserDemo):
def test_chatbot_steps_sequence_ui(self):
""" As sequences are *critical* for the chatbot_script script, let us a run a little tour that
creates a few steps, then verify sequences are properly applied. """
self.start_tour(
'/web',
'im_livechat_chatbot_steps_sequence_tour',
login='admin',
step_delay=1000
)
chatbot_script = self.env['chatbot.script'].search([('title', '=', 'Test Chatbot Sequence')])
self.assertEqual(len(chatbot_script.script_step_ids), 3)
self.assertEqual(chatbot_script.script_step_ids[0].message, 'Step 1')
self.assertEqual(chatbot_script.script_step_ids[0].sequence, 0)
self.assertEqual(chatbot_script.script_step_ids[1].message, 'Step 2')
self.assertEqual(chatbot_script.script_step_ids[1].sequence, 1)
self.assertEqual(chatbot_script.script_step_ids[2].message, 'Step 3')
self.assertEqual(chatbot_script.script_step_ids[2].sequence, 2)
def test_chatbot_steps_sequence_with_move_ui(self):
""" Same as above, with more steps and a drag&drop within the tour.
It is important to test those separately, as we want proper sequences even if we don't
move records around. """
self.start_tour(
'/web',
'im_livechat_chatbot_steps_sequence_with_move_tour',
login='admin',
step_delay=1000
)
chatbot_script = self.env['chatbot.script'].search([('title', '=', 'Test Chatbot Sequence')])
self.assertEqual(len(chatbot_script.script_step_ids), 6)
# during the test, we create the steps normally and then move 'Step 5'
# in second position -> check order is correct
self.assertEqual(chatbot_script.script_step_ids[0].message, 'Step 1')
self.assertEqual(chatbot_script.script_step_ids[0].sequence, 0)
self.assertEqual(chatbot_script.script_step_ids[1].message, 'Step 5')
self.assertEqual(chatbot_script.script_step_ids[1].sequence, 1)
self.assertEqual(chatbot_script.script_step_ids[2].message, 'Step 2')
self.assertEqual(chatbot_script.script_step_ids[2].sequence, 2)
self.assertEqual(chatbot_script.script_step_ids[3].message, 'Step 3')
self.assertEqual(chatbot_script.script_step_ids[3].sequence, 3)
self.assertEqual(chatbot_script.script_step_ids[4].message, 'Step 4')
self.assertEqual(chatbot_script.script_step_ids[4].sequence, 4)
self.assertEqual(chatbot_script.script_step_ids[5].message, 'Step 6')
self.assertEqual(chatbot_script.script_step_ids[5].sequence, 5)

View file

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.im_livechat.tests import chatbot_common
from odoo.exceptions import ValidationError
class ChatbotCase(chatbot_common.ChatbotCase):
def test_chatbot_duplicate(self):
""" In this test we make sure that 'triggering_answer_ids' are correctly duplicated and
reference the answers from the copied script steps.
See chatbot.script#copy for more details. """
chatbot_copy = self.chatbot_script.copy()
step_pricing_contact_us_copy = chatbot_copy.script_step_ids.filtered(
lambda step: 'For any pricing question, feel free ton contact us at pricing@mycompany.com' in step.message)
self.assertNotEqual(step_pricing_contact_us_copy, self.step_pricing_contact_us)
self.assertEqual(len(step_pricing_contact_us_copy.triggering_answer_ids), 1)
self.assertEqual(step_pricing_contact_us_copy.triggering_answer_ids.name, 'Pricing Question')
self.assertNotEqual(step_pricing_contact_us_copy.triggering_answer_ids, self.step_dispatch_pricing)
step_email_copy = chatbot_copy.script_step_ids.filtered(
lambda step: 'Can you give us your email please' in step.message)
self.assertNotEqual(step_email_copy, self.step_email)
self.assertEqual(len(step_email_copy.triggering_answer_ids), 1)
self.assertEqual(step_email_copy.triggering_answer_ids.name, 'I want to buy the software')
self.assertNotEqual(step_email_copy.triggering_answer_ids, self.step_dispatch_buy_software)
def test_chatbot_is_forward_operator_child(self):
self.assertEqual([step.is_forward_operator_child for step in self.chatbot_script.script_step_ids],
[False, False, False, False, False, False, False, True, True, True, False, False, False, False],
"Steps 'step_no_one_available', 'step_no_operator_dispatch', 'step_just_leaving'"
"should be flagged as forward operator child.")
self.step_no_operator_dispatch.write({'triggering_answer_ids': [(6, 0, [self.step_dispatch_pricing.id])]})
self.chatbot_script.script_step_ids.invalidate_recordset(['is_forward_operator_child'])
self.assertEqual([step.is_forward_operator_child for step in self.chatbot_script.script_step_ids],
[False, False, False, False, False, False, False, True, False, False, False, False, False, False],
"Only step 'step_no_one_available' should be flagged as forward operator child.")
def test_chatbot_steps(self):
channel_info = self.livechat_channel._open_livechat_mail_channel(
anonymous_name='Test Visitor', chatbot_script=self.chatbot_script)
mail_channel = self.env['mail.channel'].browse(channel_info['id'])
self.assertEqual(mail_channel.chatbot_current_step_id, self.step_dispatch)
self._post_answer_and_trigger_next_step(
mail_channel,
self.step_dispatch_buy_software.name,
chatbot_script_answer=self.step_dispatch_buy_software
)
self.assertEqual(mail_channel.chatbot_current_step_id, self.step_email)
with self.assertRaises(ValidationError, msg="Should raise an error since it's not a valid email"):
self._post_answer_and_trigger_next_step(mail_channel, 'test')
self._post_answer_and_trigger_next_step(mail_channel, 'test@example.com')
self.assertEqual(mail_channel.chatbot_current_step_id, self.step_email_validated)
def test_chatbot_steps_sequence(self):
""" Ensure sequence is correct when creating chatbots and adding steps to an existing one.
See chatbot.script.step#create for more details. """
chatbot_1, chatbot_2 = self.env['chatbot.script'].create([{
'title': 'Chatbot 1',
'script_step_ids': [
(0, 0, {'step_type': 'text', 'message': '1'}),
(0, 0, {'step_type': 'text', 'message': '2'}),
(0, 0, {'step_type': 'text', 'message': '3'}),
(0, 0, {'step_type': 'text', 'message': '4'}),
(0, 0, {'step_type': 'text', 'message': '5'}),
]
}, {
'title': 'Chatbot 2',
'script_step_ids': [
(0, 0, {'step_type': 'text', 'message': '1'}),
(0, 0, {'step_type': 'text', 'message': '2'}),
(0, 0, {'step_type': 'text', 'message': '3'}),
]
}])
self.assertEqual([0, 1, 2, 3, 4], chatbot_1.script_step_ids.mapped('sequence'))
self.assertEqual([0, 1, 2], chatbot_2.script_step_ids.mapped('sequence'))
chatbot_1.write({'script_step_ids': [
(0, 0, {'step_type': 'text', 'message': '6'}),
(0, 0, {'step_type': 'text', 'message': '7'}),
]})
self.assertEqual([0, 1, 2, 3, 4, 5, 6], chatbot_1.script_step_ids.mapped('sequence'))
def test_chatbot_welcome_steps(self):
""" see '_get_welcome_steps' for more details. """
welcome_steps = self.chatbot_script._get_welcome_steps()
self.assertEqual(len(welcome_steps), 3)
self.assertEqual(welcome_steps, self.chatbot_script.script_step_ids[:3])
self.chatbot_script.script_step_ids[:2].unlink()
welcome_steps = self.chatbot_script._get_welcome_steps()
self.assertEqual(len(welcome_steps), 1)
self.assertEqual(welcome_steps, self.chatbot_script.script_step_ids[0])

View file

@ -0,0 +1,165 @@
# -*- 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 import fields
from odoo.addons.im_livechat.tests.common import TestImLivechatCommon
class TestGetMailChannel(TestImLivechatCommon):
def test_get_mail_channel(self):
"""For a livechat with 5 available operators, we open 5 channels 5 times (25 channels total).
For every 5 channels opening, we check that all operators were assigned.
"""
for i in range(5):
mail_channels = self._open_livechat_mail_channel()
channel_operators = [channel_info['operator_pid'] for channel_info in mail_channels]
channel_operator_ids = [channel_operator[0] for channel_operator in channel_operators]
self.assertTrue(all(partner_id in channel_operator_ids for partner_id in self.operators.mapped('partner_id').ids))
def test_channel_get_livechat_visitor_info(self):
belgium = self.env.ref('base.be')
public_user = self.env.ref('base.public_user')
test_user = self.env['res.users'].create({'name': 'Roger', 'login': 'roger', 'country_id': belgium.id})
# ensure visitor info are correct with anonymous
operator = self.operators[0]
channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='Visitor 22', previous_operator_id=operator.partner_id.id, country_id=belgium.id)
self.assertEqual(channel_info['channel']['anonymous_name'], "Visitor 22")
self.assertEqual(channel_info['channel']['anonymous_country'], {'code': 'BE', 'id': belgium.id, 'name': 'Belgium'})
# ensure member info are hidden (in particular email and real name when livechat username is present)
# shape of channelMembers is [('insert', data...)], [0][1] accesses the data
self.assertEqual(sorted(map(lambda m: m['persona']['partner'], channel_info['channel']['channelMembers'][0][1]), key=lambda m: m['id']), sorted([{
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
'is_public': False,
'user_livechat_username': 'Michel Operator',
}, {
'active': False,
'id': public_user.partner_id.id,
'is_public': True,
'name': 'Public user',
}], key=lambda m: m['id']))
# ensure visitor info are correct with real user
channel_info = self.livechat_channel.with_user(test_user)._open_livechat_mail_channel(anonymous_name='whatever', previous_operator_id=operator.partner_id.id, user_id=test_user.id)
self.assertFalse(channel_info['channel']['anonymous_name'])
self.assertEqual(channel_info['channel']['anonymous_country'], [('clear',)])
self.assertEqual(channel_info['channel']['channelMembers'], [('insert', [
{
'channel': {'id': channel_info['id']},
'id': self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', '=', operator.partner_id.id)]).id,
'persona': {
'partner': {
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
'is_public': False,
'user_livechat_username': 'Michel Operator',
},
},
},
{
'channel': {'id': channel_info['id']},
'id': self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', '=', test_user.partner_id.id)]).id,
'persona': {
'partner': {
'active': True,
'country': {
'code': 'BE',
'id': belgium.id,
'name': 'Belgium',
},
'id': test_user.partner_id.id,
'is_public': False,
'name': 'Roger',
},
},
},
])])
# ensure visitor info are correct when operator is testing themselves
operator = self.operators[0]
channel_info = self.livechat_channel.with_user(operator)._open_livechat_mail_channel(anonymous_name='whatever', previous_operator_id=operator.partner_id.id, user_id=operator.id)
self.assertEqual(channel_info['operator_pid'], (operator.partner_id.id, "Michel Operator"))
self.assertFalse(channel_info['channel']['anonymous_name'])
self.assertEqual(channel_info['channel']['anonymous_country'], [('clear',)])
self.assertEqual(channel_info['channel']['channelMembers'], [('insert', [
{
'channel': {'id': channel_info['id']},
'id': self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', '=', operator.partner_id.id)]).id,
'persona': {
'partner': {
'active': True,
'country': [('clear',)],
'id': operator.partner_id.id,
'is_public': False,
'user_livechat_username': 'Michel Operator',
},
},
},
])])
def _open_livechat_mail_channel(self):
mail_channels = []
for i in range(5):
mail_channel = self.livechat_channel._open_livechat_mail_channel('Anonymous')
mail_channels.append(mail_channel)
# send a message to mark this channel as 'active'
self.env['mail.channel'].browse(mail_channel['id']).message_post(body='cc')
return mail_channels
def test_channel_not_pinned_for_operator_before_first_message(self):
public_user = self.env.ref('base.public_user')
channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='whatever')
operator_channel_member = self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)])
self.assertEqual(len(operator_channel_member), 1, "operator should be member of channel")
self.assertFalse(operator_channel_member.is_pinned, "channel should not be pinned for operator initially")
self.env['mail.channel'].browse(channel_info['id']).message_post(body='cc')
self.assertTrue(operator_channel_member.is_pinned, "channel should be pinned for operator after visitor sent a message")
self.assertIn(channel_info['id'], operator_channel_member.partner_id._get_channels_as_member().ids, "channel should be fetched by operator on new page")
def test_operator_livechat_username(self):
"""Ensures the operator livechat_username is returned by `_channel_fetch_message`, which is
the method called by the public route displaying chat history."""
public_user = self.env.ref('base.public_user')
operator = self.operators[0]
operator.write({
'email': 'michel@example.com',
'livechat_username': 'Michel at your service',
})
channel_info = self.livechat_channel.with_user(public_user).sudo()._open_livechat_mail_channel(anonymous_name='whatever')
channel = self.env['mail.channel'].browse(channel_info['id'])
channel.with_user(operator).message_post(body='Hello', message_type='comment', subtype_xmlid='mail.mt_comment')
message_formats = channel.with_user(public_user).sudo()._channel_fetch_message()
self.assertEqual(len(message_formats), 1)
self.assertEqual(message_formats[0]['author']['id'], operator.partner_id.id)
self.assertEqual(message_formats[0]['author']['user_livechat_username'], operator.livechat_username)
self.assertFalse(message_formats[0].get('email_from'), "should not send email_from to livechat user")
def test_read_channel_unpined_for_operator_after_one_day(self):
public_user = self.env.ref('base.public_user')
channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='visitor')
member_of_operator = self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)])
message = self.env['mail.channel'].browse(channel_info['id']).message_post(body='cc')
member_of_operator.channel_id.with_user(self.operators.filtered(
lambda operator: operator.partner_id == member_of_operator.partner_id
))._channel_seen(message.id)
with freeze_time(fields.Datetime.to_string(fields.datetime.now() + timedelta(days=1))):
member_of_operator._gc_unpin_livechat_sessions()
self.assertFalse(member_of_operator.is_pinned, "read channel should be unpinned after one day")
def test_unread_channel_not_unpined_for_operator_after_autovacuum(self):
public_user = self.env.ref('base.public_user')
channel_info = self.livechat_channel.with_user(public_user)._open_livechat_mail_channel(anonymous_name='visitor')
member_of_operator = self.env['mail.channel.member'].search([('channel_id', '=', channel_info['id']), ('partner_id', 'in', self.operators.partner_id.ids)])
self.env['mail.channel'].browse(channel_info['id']).message_post(body='cc')
with freeze_time(fields.Datetime.to_string(fields.datetime.now() + timedelta(days=1))):
member_of_operator._gc_unpin_livechat_sessions()
self.assertTrue(member_of_operator.is_pinned, "unread channel should not be unpinned after autovacuum")

View file

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest.mock import patch
from odoo.addons.im_livechat.tests.common import TestImLivechatCommon
class TestImLivechatReport(TestImLivechatCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['mail.channel'].search([('livechat_channel_id', '!=', False)]).unlink()
with patch.object(type(cls.env['im_livechat.channel']), '_get_available_users', lambda _: cls.operators):
channel_id = cls.livechat_channel._open_livechat_mail_channel('Anonymous')['id']
channel = cls.env['mail.channel'].browse(channel_id)
cls.operator = channel.livechat_operator_id
cls._create_message(channel, cls.visitor_user.partner_id, '2023-03-17 06:05:54')
cls._create_message(channel, cls.operator, '2023-03-17 08:15:54')
cls._create_message(channel, cls.operator, '2023-03-17 08:45:54')
# message with the same record id, but with a different model
# should not be taken into account for statistics
partner_message = cls._create_message(channel, cls.operator, '2023-03-17 05:05:54')
partner_message |= cls._create_message(channel, cls.operator, '2023-03-17 09:15:54')
partner_message.model = 'res.partner'
cls.env['mail.message'].flush_model()
def test_im_livechat_report_channel(self):
report = self.env['im_livechat.report.channel'].search([('livechat_channel_id', '=', self.livechat_channel.id)])
self.assertEqual(len(report), 1, 'Should have one channel report for this live channel')
# We have those messages, ordered by creation;
# 05:05:54: wrong model
# 06:05:54: visitor message
# 08:15:54: operator first answer
# 08:45:54: operator second answer
# 09:15:54: wrong model
# So the duration of the session is: (08:45:54 - 06:05:54) = 2h40 = 9600 seconds
# The time to answer of this session is: (08:15:54 - 06:05:54) = 2h10 = 7800 seconds
self.assertEqual(int(report.time_to_answer), 7800)
self.assertEqual(int(report.duration), 9600)
def test_im_livechat_report_operator(self):
result = self.env['im_livechat.report.operator'].read_group([], ['time_to_answer:avg', 'duration:avg'], [])
self.assertEqual(len(result), 1)
self.assertEqual(int(result[0]['time_to_answer']), 7800)
self.assertEqual(int(result[0]['duration']), 9600)
@classmethod
def _create_message(cls, channel, author, date):
with patch.object(cls.env.cr, 'now', lambda: date):
return channel.message_post(author_id=author.id, body=f'Message {date}')

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
from odoo.tests import HttpCase
@odoo.tests.tagged('-at_install', 'post_install')
class TestImLivechatSupportPage(HttpCase):
def test_load_modules(self):
"""Checks that all javascript modules load correctly on the livechat support page"""
# Give some time to the assets to load to prevent fetch
# interrupt errors then ensures all the assets are loaded.
check_js_modules = """
setTimeout(() => {
const { missing, failed, unloaded } = odoo.__DEBUG__.jsModules;
if ([missing, failed, unloaded].some(arr => arr.length)) {
console.error("Couldn't load all JS modules.", JSON.stringify({ missing, failed, unloaded }));
} else {
console.log("test successful");
}
Object.assign(console, {
log: () => {},
error: () => {},
warn: () => {},
});
}, 1000);
"""
self.browser_js("/im_livechat/support/1", code=check_js_modules, ready="odoo.__DEBUG__.didLogInfo")

View file

@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.tests.common import users, tagged
from odoo.addons.im_livechat.tests.chatbot_common import ChatbotCase
@tagged('post_install', '-at_install')
class TestImLivechatMessage(ChatbotCase):
def setUp(self):
super().setUp()
self.users = self.env['res.users'].create([
{
'email': 'e.e@example.com',
'groups_id': [Command.link(self.env.ref('base.group_user').id)],
'login': 'emp',
'name': 'Ernest Employee',
'notification_type': 'inbox',
'odoobot_state': 'disabled',
'signature': '--\nErnest',
},
{'name': 'test1', 'login': 'test1', 'email': 'test1@example.com'},
])
@users('emp')
def test_chatbot_message_format(self):
user = self.env.user
channel_info = self.livechat_channel.with_user(user)._open_livechat_mail_channel(
anonymous_name='Test Chatbot',
previous_operator_id=self.chatbot_script.operator_partner_id.id,
chatbot_script=self.chatbot_script,
user_id=user.id
)
mail_channel = self.env['mail.channel'].browse(channel_info['id'])
self._post_answer_and_trigger_next_step(
mail_channel,
self.step_dispatch_buy_software.name,
chatbot_script_answer=self.step_dispatch_buy_software
)
chatbot_message = mail_channel.chatbot_message_ids.mail_message_id[-1:]
self.assertEqual(chatbot_message.message_format(), [{
'id': chatbot_message.id,
'body': '<p>Can you give us your email please?</p>',
'date': chatbot_message.date,
'message_type': 'comment',
'subtype_id': (self.env.ref('mail.mt_comment').id, 'Discussions'),
'subject': False,
'model': 'mail.channel',
'res_id': mail_channel.id,
'record_name': 'Testing Bot',
'starred_partner_ids': [],
'author': {
'id': self.chatbot_script.operator_partner_id.id,
'name': 'Testing Bot'
},
'guestAuthor': [('clear',)],
'notifications': [],
'attachment_ids': [],
'trackingValues': [],
'linkPreviews': [],
'messageReactionGroups': [],
'chatbot_script_step_id': self.step_email.id,
'needaction_partner_ids': [],
'history_partner_ids': [],
'is_note': False,
'is_discussion': True,
'subtype_description': False,
'is_notification': False,
'recipients': [],
'module_icon': '/mail/static/description/icon.png',
'sms_ids': []
}])
@users('emp')
def test_message_format(self):
im_livechat_channel = self.env['im_livechat.channel'].sudo().create({'name': 'support', 'user_ids': [Command.link(self.users[0].id)]})
self.users[0].im_status = 'online' # make available for livechat (ignore leave)
channel_livechat_1 = self.env['mail.channel'].browse(im_livechat_channel._open_livechat_mail_channel(anonymous_name='anon 1', previous_operator_id=self.users[0].partner_id.id, user_id=self.users[1].id, country_id=self.env.ref('base.in').id)['id'])
record_rating = self.env['rating.rating'].create({
'res_model_id': self.env['ir.model']._get('mail.channel').id,
'res_id': channel_livechat_1.id,
'parent_res_model_id': self.env['ir.model']._get('im_livechat.channel').id,
'parent_res_id': im_livechat_channel.id,
'rated_partner_id': self.users[0].partner_id.id,
'partner_id': self.users[1].partner_id.id,
'rating': 5,
'consumed': True,
})
message = channel_livechat_1.message_post(
author_id=record_rating.partner_id.id,
body="<img src='%s' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
% (record_rating.rating_image_url, record_rating.rating, record_rating.feedback),
rating_id=record_rating.id,
)
self.assertEqual(message.message_format(), [{
'attachment_ids': [],
'author': {
'id': self.users[1].partner_id.id,
'name': "test1",
},
'body': message.body,
'date': message.date,
'guestAuthor': [('clear',)],
'history_partner_ids': [],
'id': message.id,
'is_discussion': False,
'is_note': True,
'is_notification': False,
'linkPreviews': [],
'message_type': 'notification',
'messageReactionGroups': [],
'model': 'mail.channel',
'module_icon': '/mail/static/description/icon.png',
'needaction_partner_ids': [],
'notifications': [],
'rating': {
'id': record_rating.id,
'ratingImageUrl': record_rating.rating_image_url,
'ratingText': record_rating.rating_text,
},
'recipients': [],
'record_name': "test1 Ernest Employee",
'res_id': channel_livechat_1.id,
'sms_ids': [],
'starred_partner_ids': [],
'subject': False,
'subtype_description': False,
'subtype_id': (self.env.ref('mail.mt_note').id, 'Note'),
'trackingValues': [],
}])