"""This module holds integration tests for ``assessments`` app."""

import json

from django.utils import formats
from django.core.urlresolvers import reverse
from django.test import override_settings, Client

from waffle.models import Switch

from mtp.apps.accounts.models import Group, GroupType
from mtp.apps.accounts.constants import GROUPTYPE_JOB_PROFILE
from mtp.apps.assessments.exporters import AssessmentScoresExporter
from mtp.apps.common.tests.test_cases import run_tests_concurrently, MTPTransactionTestCase
from mtp.apps.common.tests.waffle_test_utils import override_switch
from mtp.apps.roleplays.models import Criterion, Roleplay
from mtp.apps.trainings.factories import TrainingSessionFactory
from mtp.apps.workshops.factories import (
    WorkshopFactory,
    WorkshopStepQuestionAnswerFactory,
    WorkshopStepQuestionFactory,
)
from mtp.apps.workshops.models import (
    Workshop,
    WorkshopStepQuestion,
    WorkshopStepQuestionAnswer
)

from ..models import (
    AssessableActivityPartUserScore,
    Competency,
    CompetencyTree,
    CompetencyTreeUserScore,
    CompetencyUserScore,
    Dimension,
    DimensionUserScore,
)

from .test_cases import (
    MTPComplexAssessmentTestCase,
    MTPSimpleAssessmentTestCase,
    RecordScoreTestCase,
)


class SimpleAssessmentIntegrationTestCase(MTPSimpleAssessmentTestCase):
    """Test case that tests the integration of the whole ``assessments`` application."""

    def test_all_expected_objects_have_been_created(self):
        """Check that all the objects that should be created in the
        ``setupTestData`` method of ``MTPAssessmentTestCase`` are
        actually present.

        """
        # There should be one switch...
        Switch.objects.get(name='enable_assessments')

        # ... one job profile..
        job_profile_group_type = GroupType.objects.get(name=GROUPTYPE_JOB_PROFILE)
        job_profile_group = Group.objects.get(
            name=self.JOB_PROFILE_NAME, type=job_profile_group_type
        )

        # ... assigned to a training and 3 trainees, ...
        self.assertEqual(self.training.job_profile, job_profile_group)
        for trainee in self.trainees:
            self.assertEqual(trainee.job_profile, job_profile_group)

        # ... one competency tree...
        CompetencyTree.objects.get(name=self.JOB_PROFILE_NAME)

        # ... with two competencies...
        competency_names = ['C1', 'C2']
        competencies = Competency.objects.filter(name__in=competency_names)
        self.assertEqual(competencies.count(), 2)

        # ... and four dimensions.
        dimension_names = ['C1D1', 'C1D2', 'C2D1', 'C2D2']
        dimensions = Dimension.objects.filter(name__in=dimension_names)
        self.assertEqual(dimensions.count(), 4)

        # There should be one assessing quiz...
        quiz = Workshop.objects.assessing().get()

        # ... with one question...
        questions = WorkshopStepQuestion.objects.assessing().filter(workshop=quiz)
        self.assertEqual(questions.count(), 1)

        # ... with three answers.
        answers = WorkshopStepQuestionAnswer.objects.filter(question=self.question)
        self.assertEqual(answers.count(), 3)

    def record_quizz_scores(self, session):
        """Shortcut method to record some score into the given session"""

        # The first trainee gives the first answer for the quiz question,
        # the second the second answer. and the third the third answer.
        for i in range(3):
            user_id = self.trainees[i].id
            answer_id = self.answers[i].id
            reply_url = reverse('api:quiz-save', kwargs={
                'user_uuid': user_id,
                'session_uuid': session.id,
            })
            reply_data = {'data': [
                {'question_id': self.question.id,
                 'answer_id': answer_id,
                 'text': ''}
            ]}
            response = self.client.post(reply_url, {
                'json': json.dumps(reply_data),
            })
            self.assertEqual(response.status_code, 200)

    def check_session_results(self, tree, session):
        """Shortcut method to test the session results"""

        for trainee in self.trainees:
            CompetencyTree.compute_scores(trainee, session, force_recompute=True)

        training = session.training
        business_unit_name = training.business_unit.name if training.business_unit else None
        session_date = formats.date_format(session.start_date, "SHORT_DATE_FORMAT")
        assessor = session.trainer.get_full_name()

        expected_scores = [

            # Header row.
            ['Business unit', 'Session date', 'Assessor',
             'Username', 'Full name', 'Global score', 'Certification',
             self.quiz.name,
             'C1', 'C1D1', 'C1D2',
             'C2', 'C2D1', 'C2D2'],

            # Trainee scores rows.
            [business_unit_name, session_date, assessor,
             self.trainees[0].username, self.trainees[0].get_full_name(), 8, None,
             33,
             16, 33, 0,
             0, 0, 0],

            [business_unit_name, session_date, assessor,
             self.trainees[1].username, self.trainees[1].get_full_name(), 17, None,
             67,
             34, 67, 0,
             0, 0, 0],

            [business_unit_name, session_date, assessor,
             self.trainees[2].username, self.trainees[2].get_full_name(), 25, None,
             100,
             50, 100, 0,
             0, 0, 0],
        ]

        computed_scores = AssessmentScoresExporter()._build_sessions_scores(
            [session], tree
        )

        self.assertEqual(computed_scores, expected_scores)

    @override_settings(LANGUAGE_CODE='en')
    def test_simple_assessment_scenario(self):
        """Test the simple assessment scenario.

        In this test we are going to call the API views to simulate a real
        interaction with the frontend app. Therefore we can check that the
        scores are properly recorded. We'll then test the scores computations.
        Finally, we'll check the contents of the json data sent to the debrief
        view of the frontend app.

        """

        # This is a ``unittest`` configuration parameter, allowing
        # unlimited diff sizes to be displayed in case of errors.
        # Since we are going to be testing big lists, it is important
        # to ensure a maximal readability of the test results.
        # Uncomment this line in case of test failure
        # self.maxDiff = None

        session = self.training_session

        tree = CompetencyTree.objects.get_for_session(session)

        # Record some trainee's answers for this session
        self.record_quizz_scores(session)

        # We can now check all the scores in the export.
        # Since the .csv dumping is trivial in itself, we merely
        # check the computed data.

        self.check_session_results(tree, session)

    @override_settings(LANGUAGE_CODE='en')
    def test_assessment_with_multiple_sessions(self):
        """Test the behaviour of the scoring when dealing with multiple sessions"""

        # This is a ``unittest`` configuration parameter, allowing
        # unlimited diff sizes to be displayed in case of errors.
        # Since we are going to be testing big lists, it is important
        # to ensure a maximal readability of the test results.
        # Uncomment this line in case of test failure
        # self.maxDiff = None

        # Record some scores into the existing session
        self.record_quizz_scores(self.training_session)

        # Record some scores into a new session (we want to add the trainees to)
        new_session = TrainingSessionFactory(training=self.training)
        new_session.users = self.trainees
        self.record_quizz_scores(new_session)

        # The scores are correct on the first session...
        tree = CompetencyTree.objects.get_for_session(self.training_session)
        self.check_session_results(tree, self.training_session)

        # ...And also correct on the second one
        tree = CompetencyTree.objects.get_for_session(new_session)
        self.check_session_results(tree, new_session)


class CompleteAssessmentIntegrationTestCase(MTPComplexAssessmentTestCase):
    """Test case that tests the integration of the whole ``assessments``
    application.

    """

    def test_all_expected_objects_have_been_created(self):
        """Check that all the objects that should be created in the
        ``setupTestData`` method of ``MTPAssessmentTestCase`` are
        actually present.

        """
        # There shoulf be one switch...
        Switch.objects.get(name='enable_assessments')

        # ... one job profile..
        job_profile_group_type = GroupType.objects.get(name=GROUPTYPE_JOB_PROFILE)
        job_profile_group = Group.objects.get(
            name=self.JOB_PROFILE_NAME, type=job_profile_group_type
        )

        # ... assigned to a training and a trainee, ...
        self.assertEqual(self.training.job_profile, job_profile_group)
        self.assertEqual(self.trainee.job_profile, job_profile_group)

        # ... one competency tree...
        CompetencyTree.objects.get(name=self.JOB_PROFILE_NAME)

        # ... with two competencies...
        competency_names = ['Development', 'Teamwork']
        competencies = Competency.objects.filter(name__in=competency_names)
        self.assertEqual(competencies.count(), 2)

        # ... and six dimensions.
        dimension_names = [
            'Code', 'Tests', 'Documentation', 'Respect', 'Reliability', 'Autonomy'
        ]
        dimensions = Dimension.objects.filter(name__in=dimension_names)
        self.assertEqual(dimensions.count(), 6)

        # There should be one assessing quiz...
        quiz = Workshop.objects.assessing().get()

        # ... with six questions...
        questions = WorkshopStepQuestion.objects.assessing().filter(workshop=quiz)
        self.assertEqual(questions.count(), 6)

        # ... with three answers each...
        question_ids = [question.id for question in questions]
        answers = WorkshopStepQuestionAnswer.objects.filter(question__id__in=question_ids)
        single_choice_answers = answers.filter(question__is_multiple_choices=False)
        multiple_choice_answers = answers.filter(question__is_multiple_choices=True)
        self.assertEqual(answers.count(), 18)

        # ... half of which are single-choice...
        self.assertEqual(single_choice_answers.count(), 9)

        # ... and the other half of which are multiple-choices.
        self.assertEqual(multiple_choice_answers.count(), 9)

        # Finally, there should be one asessing roleplay...
        roleplay = Roleplay.objects.assessing().get()

        # ... with six criteria...
        criteria = Criterion.objects.assessing().filter(step__roleplay=roleplay)
        self.assertEqual(criteria.count(), 6)

        # ... four of which are quantitative...
        self.assertEqual(criteria.quantitative().count(), 4)

        # and the two others of which are qualitative.
        self.assertEqual(criteria.qualitative().count(), 2)

    @override_settings(LANGUAGE_CODE='en')
    def test_full_assessment_scenario(self):
        """Test the whole assessment scenario.

        In this test we are going to call the API views to simulate a real
        interaction with the frontend app. Therefore we can check that the
        scores are properly recorded. We'll then test the scores computations.
        Finally, we'll check the contents of the json data sent to the debrief
        view of the frontend app.

        """

        # This is a ``unittest`` configuration parameter, allowing
        # unlimited diff sizes to be displayed in case of errors.
        # Since we are going to be testing big lists, it is important
        # to ensure a maximal readability of the test results.
        # Uncomment this line in case of test failure
        # self.maxDiff = None

        trainee = self.trainee
        session = self.training_session

        # First, the trainee answers the quiz questions.
        quiz_reply_url = reverse('api:quiz-save', kwargs={
            'user_uuid': trainee.id,
            'session_uuid': session.id,
        })

        # Due to a shi^W suboptimal legacy API, it is currently
        # impossible to transmit the answers to multiple-choice questions
        # in one single call.
        quiz_reply_data = [
            {'data': [
                # 1st question: Trainee chose the 1st answer.
                {'question_id': self.quiz_questions[0].id,
                 'text': '',
                 'answer_id': self.quiz_answers[0].id},

                # 2nd question: Trainee chose the 2nd answer.
                {'question_id': self.quiz_questions[1].id,
                 'text': '',
                 'answer_id': self.quiz_answers[4].id},

                # 3rd question: Trainee chose the 3rd answer.
                {'question_id': self.quiz_questions[2].id,
                 'text': '',
                 'answer_id': self.quiz_answers[8].id},

                # 4th question: Trainee chose only the 1st answer.
                {'question_id': self.quiz_questions[3].id,
                 'text': '',
                 'answer_id': self.quiz_answers[9].id},

                # 5th question: Trainee chose the 2nd answer.
                {'question_id': self.quiz_questions[4].id,
                 'text': '',
                 'answer_id': self.quiz_answers[13].id},

                # 6th question: Trainee chose the 1st answer.
                {'question_id': self.quiz_questions[5].id,
                 'text': '',
                 'answer_id': self.quiz_answers[15].id},
            ]},

            {'data': [
                # 5th question: Trainee change his mind and choose the 2nd and 3rd answer now...
                {'question_id': self.quiz_questions[4].id,
                 'text': '',
                 'answer_id': self.quiz_answers[13].id},
                {'question_id': self.quiz_questions[4].id,
                 'text': '',
                 'answer_id': self.quiz_answers[14].id},

                # 6th question: Trainee change his mind and choose all answers now...
                {'question_id': self.quiz_questions[5].id,
                 'text': '',
                 'answer_id': self.quiz_answers[15].id},
                {'question_id': self.quiz_questions[5].id,
                 'text': '',
                 'answer_id': self.quiz_answers[16].id},
                {'question_id': self.quiz_questions[5].id,
                 'text': '',
                 'answer_id': self.quiz_answers[17].id},
            ]},
        ]

        # We send all the necessary requests.
        for reply_data in quiz_reply_data:
            response = self.client.post(quiz_reply_url, {
                'json': json.dumps(reply_data),
            })
            self.assertEqual(response.status_code, 200)

        # Then, the trainer rates them on the roleplay criteria.
        roleplay_reply_url = reverse('api:roleplay-ratings-list', kwargs={
            'roleplay_uuid': self.roleplay.id,
            'session_uuid': session.id,
            'user_uuid': trainee.id,
        })
        roleplay_reply_data = {
            'ratings': [
                {
                    'criterion': self.roleplay_criteria[0].id,
                    'score': 2,  # 'E' <=> 45%
                },
                {
                    'criterion': self.roleplay_criteria[1].id,
                    'score': 3,  # 'D' <=> 60%
                },
                {
                    'criterion': self.roleplay_criteria[2].id,
                    'text': "Awesome!",
                },
                {
                    'criterion': self.roleplay_criteria[3].id,
                    'score': 4,  # 'C' <=> 75%
                },
                {
                    'criterion': self.roleplay_criteria[4].id,
                    'score': 5,  # 'B' <=> 90%
                },
                {
                    'criterion': self.roleplay_criteria[5].id,
                    'text': "Terrible!",
                },
            ]
        }
        response = self.client.post(roleplay_reply_url, {
            'ratings': json.dumps(roleplay_reply_data)
        })
        self.assertEqual(response.status_code, 200)

        # We can now check that their assessment scores are
        # properly computed and recorded.
        CompetencyTree.compute_scores(trainee=trainee, session=session)

        # Check the computed activity part scores.
        expected_scores = [
            {'activity_part_id': self.quiz_questions[0].id, 'score': 0},
            {'activity_part_id': self.quiz_questions[1].id, 'score': 80},
            {'activity_part_id': self.quiz_questions[2].id, 'score': 100},
            {'activity_part_id': self.quiz_questions[3].id, 'score': 30},
            {'activity_part_id': self.quiz_questions[4].id, 'score': 69},
            {'activity_part_id': self.quiz_questions[5].id, 'score': 100},
            {'activity_part_id': self.roleplay_criteria[0].id, 'score': 45},
            {'activity_part_id': self.roleplay_criteria[1].id, 'score': 60},
            {'activity_part_id': self.roleplay_criteria[3].id, 'score': 75},
            {'activity_part_id': self.roleplay_criteria[4].id, 'score': 90},
        ]

        score_class = AssessableActivityPartUserScore
        computed_scores = list(score_class.objects.filter(user=trainee).values(
            'activity_part_id', 'score'
        ))

        self.assertCountEqual(computed_scores, expected_scores)

        # Check the computed dimension scores.
        expected_scores = [
            {'dimension_id': self.dimensions[0].id, 'score': 18},
            {'dimension_id': self.dimensions[1].id, 'score': 72},
            {'dimension_id': self.dimensions[2].id, 'score': 100},
            {'dimension_id': self.dimensions[3].id, 'score': 48},
            {'dimension_id': self.dimensions[4].id, 'score': 77},
            {'dimension_id': self.dimensions[5].id, 'score': 100},
        ]

        score_class = DimensionUserScore
        computed_scores = score_class.objects.filter(user=trainee).values(
            'dimension_id', 'score'
        )

        self.assertCountEqual(computed_scores, expected_scores)

        # Check the computed competency scores.
        expected_scores = [
            {'competency_id': self.competencies[0].id, 'score': 57},
            {'competency_id': self.competencies[1].id, 'score': 66},
        ]

        score_class = CompetencyUserScore
        computed_scores = score_class.objects.filter(user=trainee).values(
            'competency_id', 'score'
        )

        self.assertCountEqual(computed_scores, expected_scores)

        # Finally check the computed tree score.
        expected_scores = [
            {'tree_id': self.competency_tree.id, 'score': 62},
        ]

        score_class = CompetencyTreeUserScore
        computed_scores = score_class.objects.filter(user=trainee).values(
            'tree_id', 'score'
        )

        self.assertCountEqual(computed_scores, expected_scores)

        # We can now check that the proper data is transmitted
        # to the assessment debrief view.
        debrief_url = reverse('api:assessment_debrief', kwargs={
            'session_id': self.training_session.id,
            'trainee_id': self.trainee.id,
        })
        response = self.client.get(debrief_url)
        self.assertEqual(response.status_code, 200)

        expected_data = {
            'score': -1,
            'graphs': [
                {
                    'title': 'Competencies map',
                    'data': [
                        # Comp. 1 = "Dev"
                        {'name': '1', 'description': '1 - Development', 'score': 57},
                        # Comp. 2 = "Team"
                        {'name': '2', 'description': '2 - Teamwork', 'score': 66},
                    ]
                },
                {
                    'title': 'Dimensions map for competency %s' % self.competencies[0].name,
                    'data': [
                        {'name': '1', 'description': '1 - Code', 'score': 18},
                        {'name': '2', 'description': '2 - Tests', 'score': 72},
                        {'name': '3', 'description': '3 - Documentation', 'score': 100},
                    ]
                },
                {
                    'title': 'Dimensions map for competency %s' % self.competencies[1].name,
                    'data': [
                        {'name': '1', 'description': '1 - Respect', 'score': 48},
                        {'name': '2', 'description': '2 - Reliability', 'score': 77},
                        {'name': '3', 'description': '3 - Autonomy', 'score': 100},
                    ]
                },
            ],
            'activities': [
                {
                    'type': 'roleplay',
                    'name': self.roleplay.name,
                    'description': self.roleplay.description,
                    'score': -1,
                    'steps': [
                        {
                            'name': self.roleplay_steps[0].name,
                            'description': self.roleplay_steps[0].description,
                            'criteria': [
                                {
                                    'type': 'QL',
                                    'name': self.roleplay_criteria[2].name,
                                    'description': self.roleplay_criteria[2].description,
                                    'score': -1,
                                    'text': 'Awesome!',
                                }
                            ],
                        },
                        {
                            'name': self.roleplay_steps[1].name,
                            'description': self.roleplay_steps[1].description,
                            'criteria': [
                                {
                                    'type': 'QL',
                                    'name': self.roleplay_criteria[5].name,
                                    'description': self.roleplay_criteria[5].description,
                                    'score': -1,
                                    'text': 'Terrible!',
                                }
                            ],
                        }
                    ],
                }
            ],
            'suggestions': [],
        }

        received_data = json.loads(response.content.decode('utf-8'))

        self.assertEqual(received_data, expected_data)

        # Next, let's check the certification status for our trainee.
        # All their relevant scores are above 50% but below 75%, so they
        # should have earned the "Level 2" status.
        expected_status_name = 'Level 2'
        computed_status_name = self._compute_certification_status_name()

        self.assertEqual(computed_status_name, expected_status_name)

        # Next, let's check the lesson recommendations for our trainee.
        expected_recommendations = [
            ("Web development 101", 'Mandatory'),
            ("Best practices for today's developers", 'Recommended'),
            ("Understanding diversity in a development team", 'Recommended'),
        ]
        computed_recommendations = self._compute_recommendations()

        self.assertEqual(computed_recommendations, expected_recommendations)

        training = session.training
        business_unit_name = training.business_unit.name if training.business_unit else None
        session_date = formats.date_format(session.start_date, "SHORT_DATE_FORMAT")
        assessor = session.trainer.get_full_name()

        # We can finally check the exported results. Since the .csv dumping
        # is trivial in itself, we merely check the computed data.
        expected_results = [

            # Header row.
            ['Business unit', 'Session date', 'Assessor',
             'Username', 'Full name', 'Global score', 'Certification',
             self.quiz.name, self.roleplay.name,
             'Development', 'Code', 'Tests', 'Documentation',
             'Teamwork', 'Respect', 'Reliability', 'Autonomy',
             "Best practices for today's developers",
             "Understanding diversity in a development team",
             "Web development 101"],

            # Trainee scores row.
            [business_unit_name, session_date, assessor,
             self.trainees[0].username, self.trainees[0].get_full_name(), 62, 'Level 2',
             73, 68,
             57, 18, 72, 100,
             66, 48, 77, 100,
             'Recommended',
             'Recommended',
             'Mandatory'],

            [business_unit_name, session_date, assessor,
             self.trainees[1].username, self.trainees[1].get_full_name(), 0, None,
             0, 0,
             0, 0, 0, 0,
             0, 0, 0, 0,
             None,
             None,
             None],

            [business_unit_name, session_date, assessor,
             self.trainees[2].username, self.trainees[2].get_full_name(), 0, None,
             0, 0,
             0, 0, 0, 0,
             0, 0, 0, 0,
             None,
             None,
             None],

        ]

        # We close the session to export its assessments scores
        with override_settings(INSTANCE_IS_MASTER=True):
            session.close()

        computed_results = AssessmentScoresExporter()._build_sessions_scores(
            [session], self.competency_tree, self.curriculum
        )

        self.assertEqual(computed_results, expected_results)

        self._test_scores_export_with_multiple_sessions(session)

    def _test_scores_export_with_multiple_sessions(self, session):
        """Test the scores export with multiple sessions.

        Notes
        -----
        For simplification, we use the same session to test the multiple sessions scores export.

        """

        training = session.training
        business_unit_name = training.business_unit.name if training.business_unit else None
        session_date = formats.date_format(session.start_date, "SHORT_DATE_FORMAT")
        assessor = session.trainer.get_full_name()

        expected_header = [
            ['Business unit', 'Session date', 'Assessor',
             'Username', 'Full name', 'Global score', 'Certification',
             self.quiz.name, self.roleplay.name,
             'Development', 'Code', 'Tests', 'Documentation',
             'Teamwork', 'Respect', 'Reliability', 'Autonomy',
             "Best practices for today's developers",
             "Understanding diversity in a development team",
             "Web development 101"],
        ]

        trainees_session_scores = [
            [business_unit_name, session_date, assessor,
             self.trainees[0].username, self.trainees[0].get_full_name(), 62, 'Level 2',
             73, 68,
             57, 18, 72, 100,
             66, 48, 77, 100,
             'Recommended',
             'Recommended',
             'Mandatory'],

            [business_unit_name, session_date, assessor,
             self.trainees[1].username, self.trainees[1].get_full_name(), 0, None,
             0, 0,
             0, 0, 0, 0,
             0, 0, 0, 0,
             None,
             None,
             None],

            [business_unit_name, session_date, assessor,
             self.trainees[2].username, self.trainees[2].get_full_name(), 0, None,
             0, 0,
             0, 0, 0, 0,
             0, 0, 0, 0,
             None,
             None,
             None],
        ]

        # We export 2 time the same session, so we expect 2 time the same trainees scores
        expected_results = expected_header + trainees_session_scores * 2

        computed_results = AssessmentScoresExporter()._build_sessions_scores(
            [session, session], self.competency_tree, self.curriculum
        )

        self.assertEqual(computed_results, expected_results)


class ConcurrentQuizScoreRecord(MTPTransactionTestCase):
    """Test the recording of several quiz score in same time."""

    @classmethod
    def setUpTestData(cls):
        """Create a quiz, a question and an answer."""

        super().setUpTestData()

        workshop = WorkshopFactory(is_assessing=True)
        cls.question = WorkshopStepQuestionFactory(workshop=workshop)
        cls.answer = WorkshopStepQuestionAnswerFactory(question=cls.question)

    def setUp(self):
        """Prepare the url to call."""

        super().setUp()

        self.url = reverse('api:quiz-save', kwargs={
            'user_uuid': self.trainee.id,
            'session_uuid': self.training_session.id,
        })

    def test_quiz_scores_recording(self):
        """Test the quiz scores recording through multiple threads."""

        @run_tests_concurrently(3)
        def run_test():
            """Function with the tests to be run many times in parallel"""
            reply_data = {
                'data': [
                    {
                        'question_id': self.question.id,
                        'answer_id': self.answer.id,
                        'text': ''
                    },
                ],
            }

            client = Client()
            with override_switch('enable_assessments', active=True):
                response = client.post(self.url, {
                    'json': json.dumps(reply_data),
                })

            # We shouldn't have a server error
            self.assertNotEqual(response.status_code, 500)

        run_test()

class MultipleQuestionWithAllOrNothingActivatedTestCase(RecordScoreTestCase):
    """Test the score record when an user responds to a question which is assessing with the
    `all_or_nothing` option activated.

    """

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        training = cls.question.workshop.training
        with training.override_version_state(training.VERSION_STATES.DRAFT):

            # The `all_or_nothing` option is available only with multiple choices questions.
            cls.question.is_multiple_choices = True
            cls.question.is_all_or_nothing = True
            cls.question.save()

    def test_with_only_good_user_answers(self):
        """Test the score record with only good user answers."""

        good_answer1 = self.good_answers[0]
        good_answer2 = self.good_answers[1]

        score = self._record_user_answer([good_answer1])
        self.assertEqual(score, 0)

        score = self._record_user_answer([good_answer1, good_answer2])
        self.assertEqual(score, good_answer1.points + good_answer2.points)

    def test_with_a_good_user_answer_followed_by_a_bad(self):
        """Test the score record with a good user answer followed by a bad."""

        good_answer = self.good_answers[0]
        bad_answer = self.bad_answers[0]

        score = self._record_user_answer([good_answer])
        self.assertEqual(score, 0)

        score = self._record_user_answer([good_answer, bad_answer])
        self.assertEqual(score, 0)

    def test_with_a_bad_user_answer_followed_by_a_good(self):
        """Test the score record with a bad user answer followed by a good."""

        bad_answer = self.bad_answers[0]
        good_answer = self.good_answers[0]

        score = self._record_user_answer([bad_answer])
        self.assertEqual(score, 0)

        score = self._record_user_answer([bad_answer, good_answer])
        self.assertEqual(score, 0)


class MultipleQuestionWithAllOrNothingDeactivatedTestCase(RecordScoreTestCase):
    """Test the score record when an user responds to a question which is assessing with the
    `all_or_nothing` option deactivated.

    """

    @classmethod
    def setUpTestData(cls):
        super().setUpTestData()

        training = cls.question.workshop.training
        with training.override_version_state(training.VERSION_STATES.DRAFT):

            cls.question.is_multiple_choices = True
            cls.question.is_all_or_nothing = False
            cls.question.save()

    def test_with_only_good_user_answers(self):
        """Test the score record with only good user answers."""

        good_answer1 = self.good_answers[0]
        good_answer2 = self.good_answers[1]

        score = self._record_user_answer([good_answer1])
        self.assertEqual(score, good_answer1.points)

        score = self._record_user_answer([good_answer1, good_answer2])
        self.assertEqual(score, good_answer1.points + good_answer2.points)

    def test_with_a_good_user_answer_followed_by_a_bad(self):
        """Test the score record with a good user answer followed by a bad."""

        good_answer = self.good_answers[0]
        bad_answer = self.bad_answers[0]

        score = self._record_user_answer([good_answer])
        self.assertEqual(score, good_answer.points)

        score = self._record_user_answer([good_answer, bad_answer])
        self.assertEqual(score, good_answer.points + bad_answer.points)

    def test_with_a_bad_user_answer_followed_by_a_good(self):
        """Test the score record with a bad user answer followed by a good."""

        bad_answer = self.bad_answers[0]
        good_answer = self.good_answers[0]

        score = self._record_user_answer([bad_answer])
        self.assertEqual(score, bad_answer.points)

        score = self._record_user_answer([bad_answer, good_answer])
        self.assertEqual(score, bad_answer.points + good_answer.points)
