summaryrefslogtreecommitdiff
path: root/addons/survey/tests
diff options
context:
space:
mode:
authorAurélien Warnon <awa@odoo.com>2019-02-06 09:29:37 +0000
committerAurélien Warnon <awa@odoo.com>2019-02-06 10:34:40 +0000
commitafb1abbb60e5cdc4f76ab7b5a1b54b5e8a9283d5 (patch)
tree351e5ef81e76689378b77739af1adfe0b1efcd02 /addons/survey/tests
parentdba478332681dd21a39c4a183c002b1cf6e046d2 (diff)
[IMP] survey: scoring & certification
Task #1902306 Purpose ======= This commit adds certification capabilities to the survey module. A certification is a survey with the "certification" flag set to true than can be linked to a certification email template that contains a certification document (PDF). This template can be edited by the user in the technical settings to customize the certification email/document. To be able to implement this certification concept, we also have to add scoring mechanisms to surveys. survey.questions of type 'simple_choice' and 'multiple_choice' now have scores for the suggested answers. These question scores allow to compute a global score that is used to determine whether the user has successfully passed the certification or not. As additional features, we also have: - A time limit with an interface timer that limits the test to X minutes When reached, the survey is automatically submitted and unregistered (= unsubmitted) answers are not taken into account. - A limited number of attempts for the survey/certification If reached, the user can't take the survey/certification anymore The 'survey result' layout was adapted accordingly to show the success rate of participants and the correct answers to the survey questions. Specs ======= - Create a new survey : - Add a description field for the survey - Options on a survey : - Passing score : (sum of all good answers) in % - If No scoring => No passing score, no certificate - If Scoring with answers => Passing score and can see the answers (can create certificate) - If Scoring without answers => Passing score but can't review the answers at the end (can create certificate) - for the questions, if "no scoring" selected, can 't see the option "good answer" and "score" on the questions - If the 2 others options, can see the options "good answer" and "score" on the questions - all the types of questions are available for each option. - Questions - Add the option correct answer on the multiples questions (one or more good answers) - Certifications - If scoring, force "mandatory" for "mutliple choices (1or multiple answers)" - On the dashboard => visual information that this specific survey is a certification - Time Limit : The student is informed on the home screen of the survey of the time limit. The clock start when he clicks on "start survey" - Template of the certificate : send email with attachment PDF - Front-end : - Add some margin - Replace "Back to survey" with the blue-bar from the portal - Add a timer (start when the survey starts) - Add a progress bar (number of section and number of question inside the section) - Analyse of the results : - First a global graph with the number of people who''ve participated and passed the test - Stages of a survey : - Remove the stage "Permanent" - 3 stages : - draft : not on-line but can be tested (with phantom token) - In progress : on-line - closed : not on-line - Who can test a survey : the manager and the user. Add this condition to the phantom token.
Diffstat (limited to 'addons/survey/tests')
-rw-r--r--addons/survey/tests/common.py12
-rw-r--r--addons/survey/tests/test_certification_flow.py148
-rw-r--r--addons/survey/tests/test_survey_flow.py4
3 files changed, 157 insertions, 7 deletions
diff --git a/addons/survey/tests/common.py b/addons/survey/tests/common.py
index a37b7cd6b91..13b988f9ec0 100644
--- a/addons/survey/tests/common.py
+++ b/addons/survey/tests/common.py
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import re
+
from collections import Counter
from contextlib import contextmanager
from functools import partial
@@ -156,17 +158,17 @@ class SurveyCase(common.SavepointCase):
}
if qtype in ('simple_choice', 'multiple_choice'):
base_qvalues['labels_ids'] = [
- (0, 0, {'value': label['value'], 'quizz_mark': label.get('quizz_mark', 0)})
+ (0, 0, {'value': label['value'], 'answer_score': label.get('answer_score', 0)})
for label in kwargs.pop('labels')
]
elif qtype == 'matrix':
base_qvalues['matrix_subtype'] = kwargs.pop('matrix_subtype', 'simple')
base_qvalues['labels_ids'] = [
- (0, 0, {'value': label['value'], 'quizz_mark': label.get('quizz_mark', 0)})
+ (0, 0, {'value': label['value'], 'answer_score': label.get('answer_score', 0)})
for label in kwargs.pop('labels')
]
base_qvalues['labels_ids_2'] = [
- (0, 0, {'value': label['value'], 'quizz_mark': label.get('quizz_mark', 0)})
+ (0, 0, {'value': label['value'], 'answer_score': label.get('answer_score', 0)})
for label in kwargs.pop('labels_2')
]
else:
@@ -199,3 +201,7 @@ class SurveyCase(common.SavepointCase):
base_alvals[answer_fname] = answer_value
base_alvals.update(kwargs)
return self.env['survey.user_input_line'].create(base_alvals)
+
+ def _find_csrf_token(self, text):
+ csrf_token_re = re.compile("(input.+csrf_token.+value=\")([_a-zA-Z0-9]{51})", re.MULTILINE)
+ return csrf_token_re.search(text).groups()[1]
diff --git a/addons/survey/tests/test_certification_flow.py b/addons/survey/tests/test_certification_flow.py
new file mode 100644
index 00000000000..e596e34fa7d
--- /dev/null
+++ b/addons/survey/tests/test_certification_flow.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from unittest.mock import patch
+
+from odoo.addons.base.models.ir_mail_server import IrMailServer
+from odoo.addons.survey.tests import common
+from odoo.tests import tagged
+from odoo.tests.common import HttpCase
+
+
+@tagged('functional')
+class TestCertificationFlow(common.SurveyCase, HttpCase):
+ def _answer_question(self, question, answer, answer_token, csrf_token, button_submit='next'):
+ # Employee submits the question answer
+ post_data = self._format_submission_data(question, answer, {'csrf_token': csrf_token, 'token': answer_token, 'button_submit': button_submit})
+ response = self._access_submit(question.survey_id, answer_token, post_data)
+ self.assertResponse(response, 200)
+
+ # Employee is redirected on next question
+ response = self._access_page(question.survey_id, answer_token)
+ self.assertResponse(response, 200)
+
+ def _format_submission_data(self, question, answer, additional_post_data):
+ post_data = {}
+ post_data['question_id'] = question.id
+ if question.question_type == 'multiple_choice':
+ values = answer
+ for value in values:
+ key = "%s_%s_%s" % (question.survey_id.id, question.id, value)
+ post_data[key] = value
+ else:
+ key = "%s_%s" % (question.survey_id.id, question.id)
+ post_data[key] = answer
+ post_data.update(**additional_post_data)
+ return post_data
+
+ def test_flow_certificate(self):
+ # Step: survey user creates the certification
+ # --------------------------------------------------
+ with self.sudo(self.survey_user):
+ certification = self.env['survey.survey'].create({
+ 'title': 'User Certification for SO lines',
+ 'access_mode': 'public',
+ 'users_login_required': True,
+ 'questions_layout': 'page_per_question',
+ 'users_can_go_back': True,
+ 'scoring_type': 'scoring_with_answers',
+ 'passing_score': 85.0,
+ 'certificate': True,
+ 'certification_mail_template_id': self.env.ref('survey.mail_template_certification').id,
+ 'is_time_limited': True,
+ 'time_limit': 10,
+ 'stage_id': self.env['survey.stage'].search([('closed', '=', False)]).id
+ })
+
+ q01 = self._add_question(
+ None, 'When do you know it\'s the right time to use the SO line model?', 'simple_choice',
+ sequence=1,
+ constr_mandatory=True, constr_error_msg='Please select an answer', survey_id=certification.id,
+ labels=[
+ {'value': 'Please stop'},
+ {'value': 'Only on the SO form'},
+ {'value': 'Only on the Survey form'},
+ {'value': 'Easy, all the time!!!', 'is_correct': True, 'answer_score': 2.0}
+ ])
+
+ q02 = self._add_question(
+ None, 'On average, how many lines of code do you need when you use SO line widgets?', 'simple_choice',
+ sequence=2,
+ constr_mandatory=True, constr_error_msg='Please select an answer', survey_id=certification.id,
+ labels=[
+ {'value': '1'},
+ {'value': '5', 'is_correct': True, 'answer_score': 2.0},
+ {'value': '100'},
+ {'value': '1000'}
+ ])
+
+ q03 = self._add_question(
+ None, 'What do you think about SO line widgets (not rated)?', 'free_text',
+ sequence=3,
+ constr_mandatory=True, constr_error_msg='Please tell us what you think', survey_id=certification.id)
+
+ q04 = self._add_question(
+ None, 'On a scale of 1 to 10, how much do you like SO line widgets (not rated)?', 'simple_choice',
+ sequence=4,
+ constr_mandatory=True, constr_error_msg='Please tell us what you think', survey_id=certification.id,
+ labels=[
+ {'value': '-1'},
+ {'value': '0'},
+ {'value': '100'}
+ ])
+
+ q05 = self._add_question(
+ None, 'Select all the correct "types" of SO lines', 'multiple_choice',
+ sequence=5,
+ constr_mandatory=False, survey_id=certification.id,
+ labels=[
+ {'value': 'sale_order', 'is_correct': True, 'answer_score': 1.0},
+ {'value': 'survey_page', 'is_correct': True, 'answer_score': 1.0},
+ {'value': 'survey_question', 'is_correct': True, 'answer_score': 1.0},
+ {'value': 'a_future_and_yet_unknown_model', 'is_correct': True, 'answer_score': 1.0},
+ {'value': 'none', 'answer_score': -1.0}
+ ])
+
+ # Step: employee takes the certification
+ # --------------------------------------------------
+ self.authenticate('user_emp', 'user_emp')
+
+ # Employee opens start page
+ response = self._access_start(certification)
+ self.assertResponse(response, 200, [certification.title, 'Time limit for this survey', '10 minutes'])
+
+ # -> this should have generated a new user_input with a token
+ user_inputs = self.env['survey.user_input'].search([('survey_id', '=', certification.id)])
+ self.assertEqual(len(user_inputs), 1)
+ self.assertEqual(user_inputs.partner_id, self.user_emp.partner_id)
+ answer_token = user_inputs.token
+
+ # Employee begins survey with first page
+ response = self._access_page(certification, answer_token)
+ self.assertResponse(response, 200)
+ csrf_token = self._find_csrf_token(response.text)
+
+ with patch.object(IrMailServer, 'connect'):
+ self._answer_question(q01, q01.labels_ids.ids[3], answer_token, csrf_token)
+ self._answer_question(q02, q02.labels_ids.ids[1], answer_token, csrf_token)
+ self._answer_question(q03, "I think they're great!", answer_token, csrf_token)
+ self._answer_question(q04, q04.labels_ids.ids[0], answer_token, csrf_token, button_submit='previous')
+ self._answer_question(q03, "Just kidding, I don't like it...", answer_token, csrf_token)
+ self._answer_question(q04, q04.labels_ids.ids[0], answer_token, csrf_token)
+ self._answer_question(q05, [q05.labels_ids.ids[0], q05.labels_ids.ids[1], q05.labels_ids.ids[3]], answer_token, csrf_token)
+
+ user_inputs.invalidate_cache()
+ # Check that certification is successfully passed
+ self.assertEqual(user_inputs.quizz_score, 87.5)
+ self.assertTrue(user_inputs.quizz_passed)
+
+ # Check answer correction is taken into account
+ self.assertNotIn("I think they're great!", user_inputs.mapped('user_input_line_ids.value_free_text'))
+ self.assertIn("Just kidding, I don't like it...", user_inputs.mapped('user_input_line_ids.value_free_text'))
+
+ certification_email = self.env['mail.mail'].search([], limit=1, order="create_date desc")
+ # Check certification email correctly sent and contains document
+ self.assertIn("User Certification for SO lines", certification_email.subject)
+ self.assertIn("employee@example.com", certification_email.email_to)
+ self.assertEqual(len(certification_email.attachment_ids), 1)
+ self.assertEqual(certification_email.attachment_ids[0].name, 'Certification Document.html')
diff --git a/addons/survey/tests/test_survey_flow.py b/addons/survey/tests/test_survey_flow.py
index f83352800da..bda59666e3d 100644
--- a/addons/survey/tests/test_survey_flow.py
+++ b/addons/survey/tests/test_survey_flow.py
@@ -20,10 +20,6 @@ class TestSurveyFlow(common.SurveyCase, HttpCase):
def _access_submit(self, survey, token, post_data):
return self.url_open('/survey/submit/%s/%s' % (survey.access_token, token), data=post_data)
- def _find_csrf_token(self, text):
- csrf_token_re = re.compile("(input.+csrf_token.+value=\")([_a-zA-Z0-9]{51})", re.MULTILINE)
- return csrf_token_re.search(text).groups()[1]
-
def _format_submission_data(self, page, answer_data, additional_post_data):
post_data = {}
post_data['page_id'] = page.id