From 190dab8144b5dd46bb2492093d0d8d3abafb19b4 Mon Sep 17 00:00:00 2001 From: David Beguin Date: Wed, 6 Nov 2019 16:27:36 +0000 Subject: [PATCH] [IMP] website_livechat : add various tests on livechat and visitors Adds Python tests and javascripts tours on livechat (website and visitor integration). Because breaking livechat every two days in rush periods (or even not) is getting quite annoying. Those tests are testing : - The client side flow (open livechat, send messages, send rating and close the livechat session) - The channel and message author naming, visitor page view history - Chat request flow (complete chat request flow, open empty operator's chat request and cancel due to visitor's new chat session) Task ID : 2079087 PR #40052 Signed-off-by: Thibault Delavallee (tde) --- .../im_livechat/models/im_livechat_channel.py | 4 +- .../im_livechat/static/src/js/im_livechat.js | 2 +- addons/website_livechat/__manifest__.py | 1 + .../website_livechat/controllers/__init__.py | 1 + addons/website_livechat/controllers/test.py | 19 ++ .../models/im_livechat_channel.py | 1 + .../tests/tours/website_livechat_common.js | 165 ++++++++++++++++++ .../tests/tours/website_livechat_rating.js | 38 ++++ .../tests/tours/website_livechat_request.js | 46 +++++ addons/website_livechat/tests/__init__.py | 6 + addons/website_livechat/tests/common.py | 87 +++++++++ .../tests/test_livechat_basic_flow.py | 113 ++++++++++++ .../tests/test_livechat_request.py | 79 +++++++++ addons/website_livechat/tests/test_ui.py | 87 +++++++++ addons/website_livechat/views/assets.xml | 12 ++ 15 files changed, 658 insertions(+), 3 deletions(-) create mode 100644 addons/website_livechat/controllers/test.py create mode 100644 addons/website_livechat/static/tests/tours/website_livechat_common.js create mode 100644 addons/website_livechat/static/tests/tours/website_livechat_rating.js create mode 100644 addons/website_livechat/static/tests/tours/website_livechat_request.js create mode 100644 addons/website_livechat/tests/__init__.py create mode 100644 addons/website_livechat/tests/common.py create mode 100644 addons/website_livechat/tests/test_livechat_basic_flow.py create mode 100644 addons/website_livechat/tests/test_livechat_request.py create mode 100644 addons/website_livechat/tests/test_ui.py create mode 100644 addons/website_livechat/views/assets.xml 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': '

' + message.content + '

', + '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, "

%s has left the conversation.

" % 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, "

%s has left the conversation.

" % 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, "

Message from Visitor

") + 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, "

Message from Operator

") + 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 %s has left the conversation.

" % 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 @@ + + + + + +