summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/im_livechat/models/im_livechat_channel.py4
-rw-r--r--addons/im_livechat/static/src/js/im_livechat.js2
-rw-r--r--addons/website_livechat/__manifest__.py1
-rw-r--r--addons/website_livechat/controllers/__init__.py1
-rw-r--r--addons/website_livechat/controllers/test.py19
-rw-r--r--addons/website_livechat/models/im_livechat_channel.py1
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_common.js165
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_rating.js38
-rw-r--r--addons/website_livechat/static/tests/tours/website_livechat_request.js46
-rw-r--r--addons/website_livechat/tests/__init__.py6
-rw-r--r--addons/website_livechat/tests/common.py87
-rw-r--r--addons/website_livechat/tests/test_livechat_basic_flow.py113
-rw-r--r--addons/website_livechat/tests/test_livechat_request.py79
-rw-r--r--addons/website_livechat/tests/test_ui.py87
-rw-r--r--addons/website_livechat/views/assets.xml12
15 files changed, 658 insertions, 3 deletions
diff --git a/addons/im_livechat/models/im_livechat_channel.py b/addons/im_livechat/models/im_livechat_channel.py
index c9217504cdd..f97c26bd6a8 100644
--- a/addons/im_livechat/models/im_livechat_channel.py
+++ b/addons/im_livechat/models/im_livechat_channel.py
@@ -184,9 +184,9 @@ class ImLivechatChannel(models.Model):
FROM mail_channel c
LEFT OUTER JOIN mail_message_mail_channel_rel r ON c.id = r.mail_channel_id
LEFT OUTER JOIN mail_message m ON r.mail_message_id = m.id
- WHERE m.create_date > ((now() at time zone 'UTC') - interval '30 minutes')
- AND c.channel_type = 'livechat'
+ WHERE c.channel_type = 'livechat'
AND c.livechat_operator_id in %s
+ AND m.create_date > ((now() at time zone 'UTC') - interval '30 minutes')
GROUP BY c.livechat_operator_id
ORDER BY COUNT(DISTINCT c.id) asc""", (tuple(operators.mapped('partner_id').ids),))
active_channels = self.env.cr.dictfetchall()
diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js
index 86ba2e76bd3..000ac6665b9 100644
--- a/addons/im_livechat/static/src/js/im_livechat.js
+++ b/addons/im_livechat/static/src/js/im_livechat.js
@@ -482,7 +482,7 @@ var Feedback = Widget.extend({
if (reason) {
content += " \n" + reason;
}
- self.trigger('send_message', { content: content });
+ self.trigger('send_message', { content: content, isFeedback: true });
});
},
/**
diff --git a/addons/website_livechat/__manifest__.py b/addons/website_livechat/__manifest__.py
index c53d2bbbb20..10f14ca2196 100644
--- a/addons/website_livechat/__manifest__.py
+++ b/addons/website_livechat/__manifest__.py
@@ -12,6 +12,7 @@ Allow website visitors to chat with the collaborators. This module also brings a
'application': False,
'auto_install': True,
'data': [
+ 'views/assets.xml',
'views/website_livechat.xml',
'views/res_config_settings_views.xml',
'views/website_livechat_view.xml',
diff --git a/addons/website_livechat/controllers/__init__.py b/addons/website_livechat/controllers/__init__.py
index 757b12a1f17..29bc11c7183 100644
--- a/addons/website_livechat/controllers/__init__.py
+++ b/addons/website_livechat/controllers/__init__.py
@@ -1,2 +1,3 @@
# -*- coding: utf-8 -*-
from . import main
+from . import test
diff --git a/addons/website_livechat/controllers/test.py b/addons/website_livechat/controllers/test.py
new file mode 100644
index 00000000000..0ecabd66690
--- /dev/null
+++ b/addons/website_livechat/controllers/test.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.http import Controller, request, route
+
+
+class TestBusController(Controller):
+ """
+ This controller is only useful for test purpose. Bus is unavailable in test mode, but there is no way to know,
+ at client side, if we are running in test mode or not. This route can be called while running tours to mock
+ some behaviour in function of the test mode status (activated or not).
+
+ E.g. : To test the livechat and to check there is no duplicates in message displayed in the chatter,
+ in test mode, we need to mock a 'message added' notification that is normally triggered by the bus.
+ In Normal mode, the bus triggers itself the notification.
+ """
+ @route('/bus/test_mode_activated', type="json", auth="public")
+ def is_test_mode_activated(self):
+ return request.registry.in_test_mode()
diff --git a/addons/website_livechat/models/im_livechat_channel.py b/addons/website_livechat/models/im_livechat_channel.py
index 6718fd3e48b..3b805b80b7c 100644
--- a/addons/website_livechat/models/im_livechat_channel.py
+++ b/addons/website_livechat/models/im_livechat_channel.py
@@ -15,6 +15,7 @@ class ImLivechatChannel(models.Model):
if not user_id:
mail_channel_vals['anonymous_name'] = visitor_sudo.display_name + (' (%s)' % visitor_sudo.country_id.name if visitor_sudo.country_id else '')
# As chat requested by the visitor, delete the chat requested by an operator if any to avoid conflicts between two flows
+ # TODO DBE : Move this into the proper method (open or init mail channel)
chat_request_channel = self.env['mail.channel'].sudo().search([('livechat_visitor_id', '=', visitor_sudo.id), ('livechat_active', '=', True)])
for mail_channel in chat_request_channel:
mail_channel._close_livechat_session(cancel=True, speaking_with=operator.name)
diff --git a/addons/website_livechat/static/tests/tours/website_livechat_common.js b/addons/website_livechat/static/tests/tours/website_livechat_common.js
new file mode 100644
index 00000000000..446b1fa8fc9
--- /dev/null
+++ b/addons/website_livechat/static/tests/tours/website_livechat_common.js
@@ -0,0 +1,165 @@
+odoo.define('website_livechat.tour_common', function(require) {
+'use strict';
+
+var session = require('web.session');
+var LivechatButton = require('im_livechat.im_livechat').LivechatButton;
+
+/**
+ * Alter this method for test purposes.
+ *
+ * Fake the notification after sending message
+ * As bus is not available, it's necessary to add the message in the chatter + in livechat.messages
+ *
+ * Add a class to the chatter window after sendFeedback is done
+ * to force the test to wait until feedback is really done
+ * (to check afterwards if the livechat session is set to inactive)
+ *
+ * Note : this asset is loaded for tests only (rpc call done only during tests)
+ */
+LivechatButton.include({
+ _sendMessage: function (message) {
+ var self = this;
+ return this._super.apply(this, arguments).then(function () {
+ if (message.isFeedback) {
+ $('div.o_thread_window_header').addClass('feedback_sent');
+ }
+ else {
+ session.rpc('/bus/test_mode_activated', {}).then(function (in_test_mode) {
+ if (in_test_mode) {
+ var notification = [
+ self._livechat.getUUID(),
+ {
+ 'id': -1,
+ 'author_id': [0, 'Website Visitor Test'],
+ 'email_from': 'Website Visitor Test',
+ 'body': '<p>' + message.content + '</p>',
+ 'is_discussion': true,
+ 'subtype_id': [1, "Discussions"],
+ 'date': moment().format('YYYY-MM-DD HH:mm:ss'),
+ }
+ ]
+ self._handleNotification(notification);
+ }
+ });
+ }
+ });
+ },
+});
+
+/*******************************
+* Common Steps
+*******************************/
+
+var startStep = [{
+ content: "click on livechat widget",
+ trigger: "div.o_livechat_button"
+}, {
+ content: "Say hello!",
+ trigger: "input.o_composer_text_field",
+ run: "text Hello Sir!"
+}, {
+ content: "Send the message",
+ trigger: "input.o_composer_text_field",
+ run: function() {
+ $('input.o_composer_text_field').trigger($.Event('keydown', {which: $.ui.keyCode.ENTER}));
+ }
+}, {
+ content: "Verify your message has been typed",
+ trigger: "div.o_thread_message_content>p:contains('Hello Sir!')"
+}, {
+ content: "Verify there is no duplicates",
+ trigger: "body",
+ run: function () {
+ if ($("div.o_thread_message_content p:contains('Hello Sir!')").length === 1) {
+ $('body').addClass('no_duplicated_message');
+ }
+ }
+}, {
+ content: "Is your message correctly sent ?",
+ trigger: 'body.no_duplicated_message'
+}];
+
+var endDiscussionStep = [{
+ content: "Close the chatter",
+ trigger: "a.o_thread_window_close",
+ run: function() {
+ $('a.o_thread_window_close').click();
+ }
+}];
+
+var feedbackStep = [{
+ content: "Type a feedback",
+ trigger: "div.o_livechat_rating_reason > textarea",
+ run: "text ;-) This was really helpful. Thanks ;-)!"
+}, {
+ content: "Send the feedback",
+ trigger: "input[type='button'].o_rating_submit_button",
+}, {
+ content: "Check if feedback has been sent",
+ trigger: "div.o_thread_window_header.feedback_sent",
+}, {
+ content: "Thanks for your feedback",
+ trigger: "div.o_livechat_rating_box:has(div:contains('Thank you for your feedback'))",
+}];
+
+var transcriptStep = [{
+ content: "Type your email",
+ trigger: "input[id='o_email']",
+ run: "text deboul@onner.com"
+}, {
+ content: "Send the conversation to your email address",
+ trigger: "button.o_email_chat_button",
+}, {
+ content: "Type your email",
+ trigger: "div.o_livechat_email:has(strong:contains('Conversation Sent'))",
+}];
+
+var closeStep = [{
+ content: "Close the conversation with the x button",
+ trigger: "a.o_thread_window_close",
+}, {
+ content: "Check that the chat window is closed",
+ trigger: 'body',
+ run: function () {
+ if ($('div.o_livechat_button').length === 1 && !$('div.o_livechat_button').is(':visible')) {
+ $('body').addClass('tour_success');
+ }
+ }
+}, {
+ content: "Is the Test succeded ?",
+ trigger: 'body.tour_success'
+}];
+
+var goodRatingStep = [{
+ content: "Send Good Rating",
+ trigger: "div.o_livechat_rating_choices > img[data-value=10]",
+}, {
+ content: "Check if feedback has been sent",
+ trigger: "div.o_thread_window_header.feedback_sent",
+}, {
+ content: "Thanks for your feedback",
+ trigger: "div.o_livechat_rating_box:has(div:contains('Thank you for your feedback'))"
+}];
+
+var okRatingStep = [{
+ content: "Send ok Rating",
+ trigger: "div.o_livechat_rating_choices > img[data-value=5]",
+}];
+
+var sadRatingStep = [{
+ content: "Send bad Rating",
+ trigger: "div.o_livechat_rating_choices > img[data-value=1]",
+}];
+
+return {
+ 'startStep': startStep,
+ 'endDiscussionStep': endDiscussionStep,
+ 'transcriptStep': transcriptStep,
+ 'feedbackStep': feedbackStep,
+ 'closeStep': closeStep,
+ 'goodRatingStep': goodRatingStep,
+ 'okRatingStep': okRatingStep,
+ 'sadRatingStep': sadRatingStep,
+};
+
+});
diff --git a/addons/website_livechat/static/tests/tours/website_livechat_rating.js b/addons/website_livechat/static/tests/tours/website_livechat_rating.js
new file mode 100644
index 00000000000..d3339f2b02b
--- /dev/null
+++ b/addons/website_livechat/static/tests/tours/website_livechat_rating.js
@@ -0,0 +1,38 @@
+odoo.define('website_livechat.tour', function(require) {
+'use strict';
+
+var commonSteps = require("website_livechat.tour_common");
+var tour = require("web_tour.tour");
+
+tour.register('website_livechat_complete_flow_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep, commonSteps.transcriptStep, commonSteps.closeStep));
+
+tour.register('website_livechat_happy_rating_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.goodRatingStep));
+
+tour.register('website_livechat_ok_rating_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep));
+
+tour.register('website_livechat_sad_rating_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.sadRatingStep, commonSteps.feedbackStep));
+
+tour.register('website_livechat_no_rating_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep, commonSteps.endDiscussionStep, commonSteps.transcriptStep, commonSteps.closeStep));
+
+tour.register('website_livechat_no_rating_no_close_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.startStep));
+
+return {};
+});
diff --git a/addons/website_livechat/static/tests/tours/website_livechat_request.js b/addons/website_livechat/static/tests/tours/website_livechat_request.js
new file mode 100644
index 00000000000..8c4042e3821
--- /dev/null
+++ b/addons/website_livechat/static/tests/tours/website_livechat_request.js
@@ -0,0 +1,46 @@
+odoo.define('website_livechat.chat_request_tour', function(require) {
+'use strict';
+
+var commonSteps = require("website_livechat.tour_common");
+var tour = require("web_tour.tour");
+
+
+var stepWithChatRequestStep = [{
+ content: "Answer the chat request!",
+ trigger: "input.o_composer_text_field",
+ run: "text Hi ! What a coincidence! I need your help indeed."
+}, {
+ content: "Send the message",
+ trigger: "input.o_composer_text_field",
+ run: function() {
+ $('input.o_composer_text_field').trigger($.Event('keydown', {which: $.ui.keyCode.ENTER}));
+ }
+}, {
+ content: "Verify your message has been typed",
+ trigger: "div.o_thread_message_content>p:contains('Hi ! What a coincidence! I need your help indeed.')"
+}, {
+ content: "Verify there is no duplicates",
+ trigger: "body",
+ run: function () {
+ if ($("div.o_thread_message_content p:contains('Hi ! What a coincidence! I need your help indeed.')").length === 1) {
+ $('body').addClass('no_duplicated_message');
+ }
+ }
+}, {
+ content: "Is your message correctly sent ?",
+ trigger: 'body.no_duplicated_message'
+}];
+
+
+tour.register('website_livechat_chat_request_part_1_no_close_tour', {
+ test: true,
+ url: '/',
+}, [].concat(stepWithChatRequestStep));
+
+tour.register('website_livechat_chat_request_part_2_end_session_tour', {
+ test: true,
+ url: '/',
+}, [].concat(commonSteps.endDiscussionStep, commonSteps.okRatingStep, commonSteps.feedbackStep, commonSteps.transcriptStep, commonSteps.closeStep));
+
+return {};
+});
diff --git a/addons/website_livechat/tests/__init__.py b/addons/website_livechat/tests/__init__.py
new file mode 100644
index 00000000000..df6e5c815ce
--- /dev/null
+++ b/addons/website_livechat/tests/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+
+from . import common
+from . import test_ui
+from . import test_livechat_basic_flow
+from . import test_livechat_request
diff --git a/addons/website_livechat/tests/common.py b/addons/website_livechat/tests/common.py
new file mode 100644
index 00000000000..800dde98b4e
--- /dev/null
+++ b/addons/website_livechat/tests/common.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, tests
+
+
+class TestLivechatCommon(tests.TransactionCase):
+ def setUp(self):
+ super(TestLivechatCommon, self).setUp()
+ self.base_datetime = fields.Datetime.from_string("2019-11-11 21:30:00")
+
+ self.operator = self.env['res.users'].create({
+ 'name': 'Operator Michel',
+ 'login': 'operator',
+ 'email': 'operator@example.com',
+ 'password': "ideboulonate",
+ 'livechat_username': 'El Deboulonnator',
+ })
+
+ self.livechat_channel = self.env['im_livechat.channel'].create({
+ 'name': 'The basic channel',
+ 'user_ids': [(6, 0, [self.operator.id])]
+ })
+
+ self.max_sessions_per_operator = 5
+ visitor_vals = {
+ 'lang_id': self.env.ref('base.lang_en').id,
+ 'country_id': self.env.ref('base.be').id,
+ 'website_id': self.env.ref('website.default_website').id,
+ }
+ self.visitors = self.env['website.visitor'].create([{
+ 'lang_id': self.env.ref('base.lang_en').id,
+ 'country_id': self.env.ref('base.de').id,
+ 'website_id': self.env.ref('website.default_website').id,
+ 'partner_id': self.env.ref('base.user_demo').partner_id.id,
+ }] + [visitor_vals]*self.max_sessions_per_operator)
+ self.visitor_demo, self.visitor = self.visitors[0], self.visitors[1]
+
+ base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
+
+ self.open_chat_url = base_url + "/im_livechat/get_session"
+ self.open_chat_params = {'params': {
+ 'channel_id': self.livechat_channel.id,
+ 'anonymous_name': "Wrong Name"
+ }}
+
+ self.send_feedback_url = base_url + "/im_livechat/feedback"
+ self.leave_session_url = base_url + "/im_livechat/visitor_leave_session"
+
+ # override the get_available_users to return only Michel as available
+ operators = self.operator
+ def get_available_users(self):
+ return operators
+ self.patch(type(self.env['im_livechat.channel']), '_get_available_users', get_available_users)
+
+ # override the _get_visitor_from_request to return self.visitor
+ self.target_visitor = self.visitor
+ def get_visitor_from_request(self_mock, **kwargs):
+ return self.target_visitor
+ self.patch(type(self.env['website.visitor']), '_get_visitor_from_request', get_visitor_from_request)
+
+ def _send_message(self, channel, email_from, body, author_id=False):
+ # As bus is unavailable in test mode, we cannot call /mail/chat_post route to post a message.
+ # Instead, we post directly the message on the given channel.
+ channel.with_context(mail_create_nosubscribe=True) \
+ .message_post(author_id=author_id, email_from=email_from, body=body,
+ message_type='comment', subtype_id=self.env.ref('mail.mt_comment').id)
+
+ def _send_rating(self, channel, visitor, rating_value, reason=False):
+ channel_messages_count = len(channel.message_ids)
+
+ rating_to_emoji = {1: "😞", 5: "😐", 10: "😊"}
+ self.opener.post(url=self.send_feedback_url, json={'params': {
+ 'uuid': channel.uuid,
+ 'rate': rating_value,
+ 'reason': reason,
+ }})
+ res_model_id = self.env['ir.model'].sudo().search([('model', '=', channel._name)], limit=1).id
+ rating = self.env['rating.rating'].search([('res_id', '=', channel.id), ('res_model_id', '=', res_model_id)])
+ self.assertEqual(rating.rating, rating_value, "The rating is not correct.")
+
+ message = "Rating: %s" % rating_to_emoji[rating_value]
+ if reason:
+ message += " \n%s" % reason
+
+ self._send_message(channel, visitor.display_name, message, author_id=False)
+ self.assertEqual(len(channel.message_ids), channel_messages_count + 1)
diff --git a/addons/website_livechat/tests/test_livechat_basic_flow.py b/addons/website_livechat/tests/test_livechat_basic_flow.py
new file mode 100644
index 00000000000..aa3fa5a4e0e
--- /dev/null
+++ b/addons/website_livechat/tests/test_livechat_basic_flow.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import datetime
+
+from odoo import tests, _
+from odoo.addons.website_livechat.tests.common import TestLivechatCommon
+
+
+@tests.tagged('post_install', '-at_install')
+class TestLivechatBasicFlowHttpCase(tests.HttpCase, TestLivechatCommon):
+ def test_visitor_banner_history(self):
+ # create visitor history
+ self.env['website.track'].create([{
+ 'page_id': self.env.ref('website.homepage_page').id,
+ 'visitor_id': self.visitor.id,
+ 'visit_datetime': self.base_datetime,
+ }, {
+ 'page_id': self.env.ref('website.contactus_page').id,
+ 'visitor_id': self.visitor.id,
+ 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=10),
+ }, {
+ 'page_id': self.env.ref('website.aboutus_page').id,
+ 'visitor_id': self.visitor.id,
+ 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=20),
+ }, {
+ 'page_id': self.env.ref('website.contactus_page').id,
+ 'visitor_id': self.visitor.id,
+ 'visit_datetime': self.base_datetime - datetime.timedelta(minutes=30),
+ }])
+
+ handmade_history = "%s (21:10) → %s (21:20) → %s (21:30)" % (
+ self.env.ref('website.aboutus_page').name,
+ self.env.ref('website.contactus_page').name,
+ self.env.ref('website.homepage_page').name
+ )
+ history = self.env['mail.channel']._get_visitor_history(self.visitor)
+
+ self.assertEqual(history, handmade_history)
+
+ def test_livechat_username(self):
+ # Open a new live chat
+ res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params)
+ self.assertEqual(res.status_code, 200)
+ channel_1 = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1)
+
+ # Check Channel naming
+ self.assertEqual(channel_1.name, "%s, %s" % (self.visitor.display_name, self.operator.livechat_username))
+ channel_1.unlink()
+
+ # Remove livechat_username
+ self.operator.livechat_username = False
+
+ # Open a new live chat
+ res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params)
+ self.assertEqual(res.status_code, 200)
+ channel_2 = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1)
+
+ # Check Channel naming
+ self.assertEqual(channel_2.name, "%s, %s" % (self.visitor.display_name, self.operator.name))
+
+ def test_basic_flow_with_rating(self):
+ channel = self._common_basic_flow()
+
+ self._send_rating(channel, self.visitor, 5, "This deboulonnage was fine but not topitop.")
+
+ channel._close_livechat_session()
+
+ self.assertEqual(len(channel.message_ids), 4)
+ self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'has left the conversation' message.")
+ self.assertEqual(channel.message_ids[0].body, "<p>%s has left the conversation.</p>" % self.visitor.display_name)
+ self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.")
+
+ def test_basic_flow_without_rating(self):
+ channel = self._common_basic_flow()
+
+ # has left the conversation
+ channel._close_livechat_session()
+ self.assertEqual(len(channel.message_ids), 3)
+ self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the author the message.")
+ self.assertEqual(channel.message_ids[0].body, "<p>%s has left the conversation.</p>" % self.visitor.display_name)
+ self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive since visitor has left the conversation.")
+
+ def _common_basic_flow(self):
+ # Open a new live chat
+ res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params)
+ self.assertEqual(res.status_code, 200)
+
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)], limit=1)
+
+ # Check Channel and Visitor naming
+ self.assertEqual(self.visitor.display_name, "%s #%s" % (_("Website Visitor"), self.visitor.id))
+ self.assertEqual(channel.name, "%s, %s" % (self.visitor.display_name, self.operator.livechat_username))
+
+ # Post Message from visitor
+ self._send_message(channel, self.visitor.display_name, "Message from Visitor")
+
+ self.assertEqual(len(channel.message_ids), 1)
+ self.assertEqual(channel.message_ids[0].author_id.id, False, "The author of the message is not a partner.")
+ self.assertEqual(channel.message_ids[0].email_from, self.visitor.display_name, "The sender's email should be the visitor's email.")
+ self.assertEqual(channel.message_ids[0].body, "<p>Message from Visitor</p>")
+ self.assertEqual(channel.livechat_active, True, "The livechat session must be active as the visitor did not left the conversation yet.")
+
+ # Post message from operator
+ self._send_message(channel, self.operator.email, "Message from Operator", author_id=self.operator.partner_id.id)
+
+ self.assertEqual(len(channel.message_ids), 2)
+ self.assertEqual(channel.message_ids[0].author_id, self.operator.partner_id, "The author of the message should be the operator.")
+ self.assertEqual(channel.message_ids[0].email_from, self.operator.email, "The sender's email should be the operator's email.")
+ self.assertEqual(channel.message_ids[0].body, "<p>Message from Operator</p>")
+ self.assertEqual(channel.livechat_active, True, "The livechat session must be active as the visitor did not left the conversation yet.")
+
+ return channel
diff --git a/addons/website_livechat/tests/test_livechat_request.py b/addons/website_livechat/tests/test_livechat_request.py
new file mode 100644
index 00000000000..3e4025e4524
--- /dev/null
+++ b/addons/website_livechat/tests/test_livechat_request.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import tests
+from odoo.addons.website_livechat.tests.common import TestLivechatCommon
+
+
+@tests.tagged('post_install', '-at_install')
+class TestLivechatRequestHttpCase(tests.HttpCase, TestLivechatCommon):
+ def test_livechat_request_complete_flow(self):
+ self._clean_livechat_sessions()
+
+ # Send first chat request - Open chat from operator side
+ channel_1 = self._common_chat_request_flow()
+ # Visitor Rates the conversation (Good)
+ self._send_rating(channel_1, self.visitor, 10)
+
+ # Operator Re-Send a chat request
+ channel_2 = self._common_chat_request_flow()
+ # Visitor Rates the conversation (Bad)
+ self._send_rating(channel_2, self.visitor, 1, "Stop bothering me! I hate you </3 !")
+
+ def test_cancel_chat_request_on_visitor_demand(self):
+ self._clean_livechat_sessions()
+
+ self.operator_b = self.env['res.users'].create({
+ 'name': 'Operator Marc',
+ 'login': 'operator_b',
+ 'email': 'operatormarc@example.com',
+ 'password': "operatormarc",
+ 'livechat_username': "Marco'r El",
+ })
+
+ # Open Chat Request
+ self.visitor.with_user(self.operator_b).action_send_chat_request()
+ chat_request = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)])
+ self.assertEqual(chat_request.livechat_operator_id, self.operator_b.partner_id, "Operator for active livechat session must be Operator Marc")
+
+ # Click on livechatbutton at client side
+ res = self.opener.post(url=self.open_chat_url, json=self.open_chat_params)
+ self.assertEqual(res.status_code, 200)
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id),
+ ('livechat_active', '=', True)])
+
+ # Check that the chat request has been canceled.
+ chat_request.invalidate_cache()
+ self.assertEqual(chat_request.livechat_active, False, "The livechat request must be inactive as the visitor started himself a livechat session.")
+ self.assertEqual(len(channel), 1)
+ self.assertEqual(channel.livechat_operator_id, self.operator.partner_id, "Operator for active livechat session must be Michel Operator")
+
+ def _common_chat_request_flow(self):
+ self.visitor.with_user(self.operator).action_send_chat_request()
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)])
+ self.assertEqual(len(channel), 1)
+ self.assertEqual(channel.livechat_operator_id, self.operator.partner_id, "Michel Operator should be the operator of this channel.")
+ self.assertEqual(len(channel.message_ids), 0)
+
+ # Operator Sends message
+ self._send_message(channel, self.operator.email, "Hello Again !", author_id=self.operator.partner_id.id)
+ self.assertEqual(len(channel.message_ids), 1)
+
+ # Visitor Answers
+ self._send_message(channel, self.visitor.display_name, "Answer from Visitor")
+ self.assertEqual(len(channel.message_ids), 2)
+
+ # Visitor Leave the conversation
+ channel._close_livechat_session()
+ self.assertEqual(len(channel.message_ids), 3)
+ self.assertEqual(channel.message_ids[0].author_id, self.env.ref('base.partner_root'), "Odoobot must be the sender of the 'has left the conversation' message.")
+ self.assertEqual(channel.message_ids[0].body, "<p>%s has left the conversation.</p>" % self.visitor.display_name)
+ self.assertEqual(channel.livechat_active, False, "The livechat session must be inactive as the visitor sent his feedback.")
+
+ return channel
+
+ def _clean_livechat_sessions(self):
+ # clean every possible mail channel linked to the visitor
+ active_channels = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor.id), ('livechat_active', '=', True)])
+ for active_channel in active_channels:
+ active_channel._close_livechat_session()
diff --git a/addons/website_livechat/tests/test_ui.py b/addons/website_livechat/tests/test_ui.py
new file mode 100644
index 00000000000..cf8336c1c4f
--- /dev/null
+++ b/addons/website_livechat/tests/test_ui.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import tests, _
+from odoo.addons.website_livechat.tests.common import TestLivechatCommon
+
+
+@tests.tagged('post_install', '-at_install')
+class TestLivechatUI(tests.HttpCase, TestLivechatCommon):
+ def setUp(self):
+ super(TestLivechatUI, self).setUp()
+ self.visitor_tour = self.env['website.visitor'].create({
+ 'name': 'Visitor Tour',
+ 'website_id': self.env.ref('website.default_website').id,
+ })
+ self.target_visitor = self.visitor_tour
+
+ def test_complete_rating_flow_ui(self):
+ self.start_tour("/", 'website_livechat_complete_flow_tour')
+ self._check_end_of_rating_tours()
+
+ def test_happy_rating_flow_ui(self):
+ self.start_tour("/", 'website_livechat_happy_rating_tour')
+ self._check_end_of_rating_tours()
+
+ def test_ok_rating_flow_ui(self):
+ self.start_tour("/", 'website_livechat_ok_rating_tour')
+ self._check_end_of_rating_tours()
+
+ def test_bad_rating_flow_ui(self):
+ self.start_tour("/", 'website_livechat_sad_rating_tour')
+ self._check_end_of_rating_tours()
+
+ def test_no_rating_flow_ui(self):
+ self.start_tour("/", 'website_livechat_no_rating_tour')
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)])
+ self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.")
+ self.assertEqual(channel.livechat_active, False, 'Livechat must be inactive after closing the chat window.')
+
+ def test_no_rating_no_close_flow_ui(self):
+ self.start_tour("/", 'website_livechat_no_rating_no_close_tour')
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)])
+ self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.")
+ self.assertEqual(channel.livechat_active, True, 'Livechat must be active while the chat window is not closed.')
+
+ def test_empty_chat_request_flow_no_rating_no_close_ui(self):
+ # Open an empty chat request
+ self.visitor_tour.with_user(self.operator).action_send_chat_request()
+ chat_request = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)])
+
+ # Visitor ask a new livechat session before the operator start to send message in chat request session
+ self.start_tour("/", 'website_livechat_no_rating_no_close_tour')
+
+ # Visitor's session must be active (gets the priority)
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)])
+ self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.")
+ self.assertEqual(channel.livechat_active, True, 'Livechat must be active while the chat window is not closed.')
+
+ # Check that the chat request has been canceled.
+ chat_request.invalidate_cache()
+ self.assertEqual(chat_request.livechat_active, False, "The livechat request must be inactive as the visitor started himself a livechat session.")
+
+ def test_chat_request_flow_with_rating_ui(self):
+ # Open a chat request
+ self.visitor_tour.with_user(self.operator).action_send_chat_request()
+ chat_request = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)])
+
+ # Operator send a message to the visitor
+ self._send_message(chat_request, self.operator.email, "Hello my friend !", author_id=self.operator.partner_id.id)
+ self.assertEqual(len(chat_request.message_ids), 1, "Number of messages incorrect.")
+
+ # Visitor comes to the website and receives the chat request
+ self.start_tour("/", 'website_livechat_chat_request_part_1_no_close_tour')
+
+ # Check that the current session is the chat request
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id), ('livechat_active', '=', True)])
+ self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.")
+ self.assertEqual(channel, chat_request, "The active livechat session must be the chat request one.")
+
+ # Visitor reload the page and continues the chat with the operator normally
+ self.start_tour("/", 'website_livechat_chat_request_part_2_end_session_tour')
+ self._check_end_of_rating_tours()
+
+ def _check_end_of_rating_tours(self):
+ channel = self.env['mail.channel'].search([('livechat_visitor_id', '=', self.visitor_tour.id)])
+ self.assertEqual(len(channel), 1, "There can only be one channel created for 'Visitor Tour'.")
+ self.assertEqual(channel.livechat_active, False, 'Livechat must be inactive after rating.')
diff --git a/addons/website_livechat/views/assets.xml b/addons/website_livechat/views/assets.xml
new file mode 100644
index 00000000000..42a6f1bacd1
--- /dev/null
+++ b/addons/website_livechat/views/assets.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+<data>
+ <template id="assets_tests" name="Website Livechat Assets Tests" inherit_id="web.assets_tests">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/website_livechat/static/tests/tours/website_livechat_common.js"></script>
+ <script type="text/javascript" src="/website_livechat/static/tests/tours/website_livechat_rating.js"></script>
+ <script type="text/javascript" src="/website_livechat/static/tests/tours/website_livechat_request.js"></script>
+ </xpath>
+ </template>
+</data>
+</odoo>