"""Test cases for the ``workshops`` models."""

import os

from mock import call, patch
from pandas import Series
from pandas.util import testing as pdt

from django.conf import settings
from django.db import transaction, IntegrityError
from django.utils.translation import override, ugettext_lazy as _

from mtp.apps.common.tests.test_cases import WithMediaOnDisk
from mtp.apps.data_export.constants import EXCEL_STYLE_CHOICES
from mtp.apps.data_export.exporters import ExcelDataExporter
from mtp.apps.workshops.models import (
    WorkshopStepQuestion,
    WorkshopStepQuestionUserAnswer
)

from ..factories import WorkshopStepQuestionUserAnswerFactory

from .test_cases import WorkshopTestCase


class WorkshopModelTestCase(WorkshopTestCase):
    """Test cases for the ``Workshop`` model."""

    def get_trainee_score(self):
        """Shortcut method to get the score fo the trainee for the current workshop"""

        try:
            # Delete the ``@cached_property`` from the workshop, that's necessary
            # to get the right parts attributes
            del self.workshop.assessing_parts
        except AttributeError:
            pass  # Do nothing, the attribute might not be set

        return self.workshop.get_trainee_session_score(
            session=self.training_session, trainee=self.trainee)

    def test_compute_challenge_score_single_choice_with_valid_answer(self):
        """Test the ``compute_challenge_score`` method with a single choice question"""

        answer = self.workshop_question.answers.filter(is_correct=True).first()

        # Add a valid user answer
        self.workshop_question.save_user_answers(
            self.trainee,
            self.training_session,
            [answer]
        )

        # The trainee got the points for this question
        self.assertEqual(self.get_trainee_score(), 1)

    def test_compute_challenge_score_single_choice_with_invalid_answer(self):
        """Test the ``compute_challenge_score`` method with a single choice question"""

        answer = self.workshop_question.answers.filter(is_correct=False).first()

        # Add a invalid user answer
        self.workshop_question.save_user_answers(
            self.trainee,
            self.training_session,
            [answer]
        )

        # The trainee got the points for this question (0)
        self.assertEqual(self.get_trainee_score(), 0)

    def test_compute_challenge_score_multiple_choice_answer_all_valid(self):
        """Test the ``compute_challenge_score`` method with a multiple choices question"""

        # The trainee found all (and only) the valid answers
        answers = self.workshop_question_mc.answers.filter(is_correct=True)

        # Add all valid answers
        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            answers
        )

        # The trainee got the points for this question (1+1)
        self.assertEqual(self.get_trainee_score(), 2)

    def test_compute_challenge_score_multiple_choice_answer_one_invalid(self):
        """Test the ``compute_challenge_score`` method with a multiple choices question

        In this test, the ``is_all_or_nothing`` flag is set to True.

        """

        # The trainee found all the answers (valid AND invalid ones)
        answers = self.workshop_question_mc.answers.all()

        # Add all answers
        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            answers
        )

        # The trainee got nothing since it's a ``all_or_nothing`` question type
        self.assertEqual(self.get_trainee_score(), 0)

    def test_compute_challenge_score_multiple_choice_answer_one_invalid_all_or_nothing_false(self):
        """Test the ``compute_challenge_score`` method with a multiple choices question

        In this test, the ``is_all_or_nothing`` flag is set to False.

        """

        # Deactivate the ``is_all_or_nothing`` flag
        training = self.workshop_question_mc.workshop.training

        with training.override_version_state(training.VERSION_STATES.DRAFT):
            self.workshop_question_mc.is_all_or_nothing = False
            self.workshop_question_mc.save()

        # The trainee found all the answers (valid AND invalid ones)
        answers = self.workshop_question_mc.answers.all()

        # Add all answers
        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            answers
        )

        # The trainee got 2 points (for the two valid answers)
        self.assertEqual(self.get_trainee_score(), 2)

    def test_media_files(self):
        """Test that the ``media_files`` property should return a list of media path"""

        self.assertEqual(
            self.workshop.media_files,
            [self.workshop_question_mc_image.image.name]
        )


class WorkshopStepQuestionModelTestCase(WorkshopTestCase):
    """Test cases for the ``WorkshopStepQuestion`` model."""

    @classmethod
    def setUpTestData(cls):
        """Set up the test data"""

        super().setUpTestData()

        cls.valid_answers = cls.workshop_question_mc.answers.filter(is_correct=True)
        cls.invalid_answers = cls.workshop_question_mc.answers.filter(is_correct=False)

    def question_is_valid(self):
        """Shortcut method to test the validity of the question"""

        return self.workshop_question_mc.is_valid_for_trainee(
            self.trainee, self.training_session)

    def test_is_valid_for_trainee_with_all_or_nothing(self):
        """Test the ``is_valid_for_trainee`` method when ``is_all_or_nothing`` flag is active."""

        # Add a valid user answer
        self.add_user_answers([self.valid_answers.first()])

        # Since the question is a multiple choice one, with the ``is_all_or_nothing`` flag,
        # the question is not valid yet
        self.assertFalse(self.question_is_valid())

        # Add another valid user answer
        self.add_user_answers([self.valid_answers.last()])

        # Since the trainee got all the good answers, the question is valid now
        self.assertTrue(self.question_is_valid())

        # Add an invalid answer
        self.add_user_answers([self.invalid_answers.first()])

        # The question should now be invalid
        self.assertFalse(self.question_is_valid())

    def test_is_valid_for_trainee_without_all_or_nothing(self):
        """Test the ``is_valid_for_trainee`` method when ``is_all_or_nothing`` is not active."""

        # Deactivate the ``is_all_or_nothing`` flag
        training = self.workshop_question_mc.workshop.training
        with training.override_version_state(training.VERSION_STATES.DRAFT):

            self.workshop_question_mc.is_all_or_nothing = False
            self.workshop_question_mc.save()

        # Add an invalid answer
        self.add_user_answers([self.invalid_answers.first()])

        # The question should be invalid
        self.assertFalse(self.question_is_valid())

        # Add a valid answer
        self.add_user_answers([self.valid_answers.first()])

        # With at last one good answer, the question is valid
        self.assertTrue(self.question_is_valid())

        # Soft-delete the trainee's answer
        WorkshopStepQuestionUserAnswer.objects.get(
            answer=self.valid_answers.first(),
            user=self.trainee
        ).remove()

        # The question is not valid anymore
        self.assertFalse(self.question_is_valid())

    def test_save_user_answers(self):
        """Test the ``save_user_answers``method."""

        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            self.answers,
        )

        user_answers_count = WorkshopStepQuestionUserAnswer.objects.filter(
            user=self.trainee,
            training_session=self.training_session,
            question=self.workshop_question_mc,
        ).count()
        self.assertEqual(len(self.answers), user_answers_count)

    def test_reset_user_answers(self):
        """Test the ``reset_user_answers``method."""

        # We save answers for trainee and trainee1
        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            self.answers,
        )

        self.workshop_question_mc.save_user_answers(
            self.trainees[1],
            self.training_session,
            self.answers,
        )

        # We reset answers of trainee
        self.workshop_question_mc.reset_user_answers(
            self.trainee,
            self.training_session
        )

        # The trainee should have 0 registered answers
        trainee_answers_count = WorkshopStepQuestionUserAnswer.objects.non_deleted().filter(
            user=self.trainee,
            training_session=self.training_session,
            question=self.workshop_question_mc,
        ).count()
        self.assertEqual(trainee_answers_count, 0)

        # The trainee1 should keep this registered answers
        trainee1_answers_count = WorkshopStepQuestionUserAnswer.objects.non_deleted().filter(
            user=self.trainees[1],
            training_session=self.training_session,
            question=self.workshop_question_mc,
        ).count()
        self.assertEqual(trainee1_answers_count, len(self.answers))

    def test_is_open_property(self):
        """Test that the ``is_open`` property is return correct values"""

        # Since we got multiple answers set on the question, it's not an "open" one
        self.assertTrue(self.workshop_question.answers.non_deleted().exists())
        self.assertFalse(self.workshop_question.is_open)

        # We delete the answers, so the question is now an "open" one
        self.workshop_question.answers.non_deleted().delete()
        self.assertFalse(self.workshop_question.answers.non_deleted().exists())

        del self.workshop_question.is_open
        self.assertTrue(self.workshop_question.is_open)

    def test_get_training_session_average_score(self):
        """Test that the ``get_training_session_average_score`` method"""

        # The average must be 0.0, since we got no answers yet
        # 3 queries since the recompute is done
        with self.assertNumQueries(3):
            self.assertEqual(
                self.workshop_question_mc.get_session_average_score(
                    session=self.training_session
                ),
                0.0
            )

        # The trainee answer all the valid answers
        self.workshop_question_mc.save_user_answers(
            self.trainee,
            self.training_session,
            self.valid_answers
        )

        # Now, only 1 query
        with self.assertNumQueries(1):
            self.assertEqual(
                self.workshop_question_mc.get_session_average_score(
                    session=self.training_session
                ),
                2.0
            )

        # Add right answers for another user
        self.training_session.users.add(self.trainees[1])
        self.workshop_question_mc.save_user_answers(
            self.trainees[1],
            self.training_session,
            self.valid_answers
        )

        self.assertEqual(
            self.workshop_question_mc.get_session_average_score(
                session=self.training_session
            ),
            2.0  # The average of 2 and 2 is 2 (no shit)
        )

        # Add wrong answers for another user
        self.training_session.users.add(self.trainees[2])
        self.workshop_question_mc.save_user_answers(
            self.trainees[2],
            self.training_session,
            self.invalid_answers
        )

        self.assertEqual(
            round(self.workshop_question_mc.get_session_average_score(
                session=self.training_session
            ), 1),
            1.3  # The average of 2 and 2 and 0 is 1.3
        )

        # If we remove the user from the session, the method still work
        self.training_session.users.remove(self.trainee, self.trainees[1], self.trainees[2])
        self.assertEqual(self.training_session.users.count(), 0)

        self.workshop_question_mc.get_session_average_score(
            session=self.training_session
        )

    def test_get_trainee_session_score_should_compute_if_score_is_none(self):
        """Test that the ``get_trainee_session_score`` should compute if the score is None"""

        # First call to the score, this should compute
        with patch.object(WorkshopStepQuestion, 'compute_trainee_session_score') as mocked:
            self.workshop_question.get_trainee_session_score(
                self.trainee,
                self.training_session
            )

        mocked.assert_called_once_with(self.trainee, self.training_session)

        # Second call, the score is already computed
        with patch.object(WorkshopStepQuestion, 'compute_trainee_session_score') as mocked:
            self.workshop_question.get_trainee_session_score(
                self.trainee,
                self.training_session
            )

        mocked.assert_not_called()

    def test_update_existing_open_question(self):
        """The answer of an open question can be updated"""

        # answer a first time
        self.workshop_open_question.save_user_answers(
            user=self.trainee,
            session=self.training_session,
            answers=['foo'],
        )

        answers = self.workshop_open_question.user_answers.all()
        self.assertEqual(len(answers), 1)
        first_answer = answers.first()
        self.assertEqual(first_answer.text, 'foo')

        # answer a second time
        self.workshop_open_question.save_user_answers(
            user=self.trainee,
            session=self.training_session,
            answers=['bar'],
        )

        answers = self.workshop_open_question.user_answers.all()
        self.assertEqual(len(answers), 1)
        second_answer = answers.first()
        self.assertEqual(second_answer.id, first_answer.id)
        self.assertEqual(second_answer.text, 'bar')

    def test_max_points_yield_property_should_return_the_sum_of_answers_points(self):
        """Test that the ``max_points_yield`` should return the sum of answers points"""

        # The question is a multiple choices one, the maximum number of points is the sum
        # of the points of all the answers
        self.assertEqual(
            sum(
                self.workshop_question_mc.answers.non_deleted().values_list(
                    'points',
                    flat=True
                )
            ),
            self.workshop_question_mc.max_points_yield
        )

        # Soft-delete the first answer, the property still work
        with self.training.override_version_state(self.training.VERSION_STATES.DRAFT):
            self.workshop_question_mc.answers.first().remove()

        del self.workshop_question_mc.max_points_yield

        self.assertEqual(
            sum(
                self.workshop_question_mc.answers.non_deleted().values_list(
                    'points',
                    flat=True
                )
            ),
            self.workshop_question_mc.max_points_yield
        )

        # For a single choice question, the property is now equal to the max of the answers points
        self.assertEqual(
            max(
                self.workshop_question.answers.non_deleted().values_list(
                    'points',
                    flat=True
                )
            ),
            self.workshop_question.max_points_yield
        )


class WorkshopStepQuestionUserAnswerModelTestCase(WorkshopTestCase):
    """Test case for ``WorkshopStepQuestionUserAnswer`` model."""

    def test_uniqueness(self):
        """Check we can't have 2 user answers for the same user, session, question and answer."""

        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainee,
            training_session=self.training_session,
            question=self.workshop_question_mc,
            answer=self.answers[0],
        )

        with transaction.atomic():
            with self.assertRaises(IntegrityError):
                WorkshopStepQuestionUserAnswerFactory(
                    user=self.trainee,
                    training_session=self.training_session,
                    question=self.workshop_question_mc,
                    answer=self.answers[0],
                )


class WorkshopStepQuestionAnswerTestCase(WorkshopTestCase):
    """Test cases for the ``WorkshopStepQuestionAnswer`` model"""

    def test_answer_is_selected(self):
        """Test the ``is_selected`` method"""

        first_answer = self.workshop_question_mc.answers.first()

        # The trainee didn't select this answer
        self.assertFalse(
            first_answer.is_selected(
                trainee=self.trainee,
                training_session=self.training_session
            )
        )

        # Mark this answer as "selected"
        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainee,
            training_session=self.training_session,
            question=self.workshop_question_mc,
            answer=first_answer
        )

        # The trainee did select our answer
        self.assertTrue(
            first_answer.is_selected(
                trainee=self.trainee,
                training_session=self.training_session
            )
        )

    def test_session_rate(self):
        """Test the ``session_rate`` method"""

        first_answer = self.workshop_question_mc.answers.first()
        last_answer = self.workshop_question_mc.answers.last()

        # The rate on the answer must be 0, since we don't have any user answers yet
        self.assertEqual(
            first_answer.session_rate(self.training_session),
            0
        )

        # The trainee select the answer
        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainee,
            training_session=self.training_session,
            question=self.workshop_question_mc,
            answer=first_answer
        )

        # The rate on the answer must be 100 (the trainee is alone in this session)
        self.assertEqual(
            first_answer.session_rate(self.training_session),
            100
        )

        # If we add another trainee, without an answer, the rate must still be 100
        self.training_session.users.add(self.trainees[2])
        self.assertEqual(
            first_answer.session_rate(self.training_session),
            100
        )

        # The second trainee select the last answer
        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainees[2],
            training_session=self.training_session,
            question=self.workshop_question_mc,
            answer=last_answer
        )

        # The rates are correct on both the answers
        self.assertEqual(
            first_answer.session_rate(self.training_session),
            50
        )
        self.assertEqual(
            last_answer.session_rate(self.training_session),
            50
        )


class WorkshopResultsTestCase(WithMediaOnDisk, WorkshopTestCase):
    """Test case for the results related method on the ``Workshop`` model"""

    @staticmethod
    def get_user_index_name(user):
        """Shortcut method to get the user index name

        Parameters
        ----------
        user: MTPUser
            The user we want the index name for

        Returns
        -------
        str

        """

        return '%(last_name)s %(first_name)s (%(username)s)' % {
            'last_name': user.last_name,
            'first_name': user.first_name,
            'username': user.username
        }

    @override('en')
    def check_data_frame_values(self, data_frame, scores, mean_points, max_points):
        """Shortcut method to check the content of the provided data frame

        Parameters
        ----------
        data_frame: pandas.DataFrame
            The data frame we want to check
        scores: list(dict)
            The list of expected scores by trainee
        mean_points: list
            The list of expected values for the "Mean" index
        max_points: list
            The list of expected values for the "Maximum points" index

        """

        for trainee, score in scores.items():
            self.assertEqual(list(data_frame.loc[trainee].values), score)

        self.assertEqual(list(data_frame.loc[str(_('Mean'))].values), mean_points)
        self.assertEqual(list(data_frame.loc[str(_('Maximum points'))].values), max_points)

    @override('en')
    def test_workshop_get_data_results_name(self):
        """Test "sheet_name" key from the ``get_data_results`` of the ``Workshop`` model"""

        sheet_name = self.workshop.get_results_excel_data(self.training_session)['sheet_name']
        self.assertEqual(sheet_name, str(_('Global scores')))

    def test_workshop_get_data_results_header_styles(self):
        """Test "header_styles" key from the ``get_data_results`` of the ``Workshop`` model"""

        # The style for the "Total" column + one style for each "assessing part"
        header_styles = self.workshop.get_results_excel_data(self.training_session)['header_styles']
        self.assertEqual(
            header_styles,
            [EXCEL_STYLE_CHOICES.HEADER] + [
                EXCEL_STYLE_CHOICES.WORKSHOP_STEP
            ] * self.workshop.assessing_parts.non_deleted().count()
        )

    @override('en')
    def test_workshop_get_data_results_data(self):
        """Test the "data" key from the ``get_data_results`` of the ``Workshop`` model"""

        data_frame = self.workshop.get_results_excel_data(self.training_session)['data']

        # Check the indexes of the data frame
        indexes = [
            self.get_user_index_name(self.trainee),
            str(_('Mean')),
            str(_('Maximum points'))
        ]
        self.assertEqual(list(data_frame.index.values), indexes)

        # Check the columns of the data frame
        columns = [
            '%d - %s' % (index, step.title)
            for index, step in enumerate(self.workshop.assessing_parts.non_deleted(), start=1)
        ]
        columns.insert(0, str(_('Total')))
        self.assertEqual(list(data_frame.columns.values), columns)

        # Check the values
        self.check_data_frame_values(
            data_frame,
            {
                self.get_user_index_name(self.trainee): [0.0, 0.0, 0.0, 0.0]
            },
            [0.0, 0.0, 0.0, 0.0],
            [3.0, 1.0, 2.0, 0.0]
        )

        # New trainee
        self.training_session.users.add(self.trainees[1])

        data_frame = self.workshop.get_results_excel_data(self.training_session)['data']
        self.check_data_frame_values(
            data_frame,
            {
                self.get_user_index_name(self.trainee): [0.0, 0.0, 0.0, 0.0],
                self.get_user_index_name(self.trainees[1]): [0.0, 0.0, 0.0, 0.0]
            },
            [0.0, 0.0, 0.0, 0.0],
            [3.0, 1.0, 2.0, 0.0]
        )

        # The first trainee answer correctly to the first question
        self.workshop_question.save_user_answers(
            self.trainee,
            self.training_session,
            self.workshop_question.answers.filter(is_correct=True)
        )

        data_frame = self.workshop.get_results_excel_data(self.training_session)['data']
        self.check_data_frame_values(
            data_frame,
            {
                self.get_user_index_name(self.trainee): [1.0, 1.0, 0.0, 0.0],
                self.get_user_index_name(self.trainees[1]): [0.0, 0.0, 0.0, 0.0]
            },
            [0.5, 0.5, 0.0, 0.0],
            [3.0, 1.0, 2.0, 0.0]
        )

        # The second trainee do the same
        self.workshop_question.save_user_answers(
            self.trainees[1],
            self.training_session,
            self.workshop_question.answers.filter(is_correct=True)
        )

        data_frame = self.workshop.get_results_excel_data(self.training_session)['data']
        self.check_data_frame_values(
            data_frame,
            {
                self.get_user_index_name(self.trainee): [1.0, 1.0, 0.0, 0.0],
                self.get_user_index_name(self.trainees[1]): [1.0, 1.0, 0.0, 0.0]
            },
            [1.0, 1.0, 0.0, 0.0],
            [3.0, 1.0, 2.0, 0.0]
        )

    def test_workshop_get_data_results_with_same_step_title(self):
        """Test that we should still be allowed to export if two questions got the same title"""

        with self.training.override_version_state(self.training.VERSION_STATES.DRAFT):
            first_step = self.workshop.assessing_parts.non_deleted()[0]
            second_step = self.workshop.assessing_parts.non_deleted()[1]

            # Make the title of the second step the same as the first step
            second_step.title = first_step.title
            second_step.save()

        # This will raise if the test is not a success
        self.workshop.get_results_excel_data(self.training_session, detailed=True)

    def test_workshop_get_results_excel_data(self):
        """Test to check calling the ``get_results_excel_data`` with the "detailed" flag"""

        with patch.object(WorkshopStepQuestion, 'get_results_excel_data') as mocked:
            self.workshop.get_results_excel_data(self.training_session)

        # Without the "detailed" flag, the mock should be not be called
        mocked.assert_not_called()

        return_value = [(Series(), EXCEL_STYLE_CHOICES.DEFAULT)]

        # Now with the "detailed" flag
        with patch.object(WorkshopStepQuestion,
                          'get_results_excel_data',
                          return_value=return_value) as mocked:
            self.workshop.get_results_excel_data(self.training_session, detailed=True)

        # One call for each of the assessing parts of the workshop
        calls = [call(self.training_session)] * self.workshop.assessing_parts.non_deleted().count()

        mocked.assert_has_calls(calls, any_order=True)

    def test_export_excel_from_data_results(self):
        """Test that exporting an excel file from the ``get_data_results`` should work"""

        path = os.path.join(settings.MEDIA_ROOT, 'test.xlsx')
        exporter = ExcelDataExporter(path)
        results = self.workshop.get_results_excel_data(self.training_session)

        exporter.add_sheet(**results)
        exporter.save()

        self.assertTrue(os.path.isfile(path))

    def test_export_excel_from_data_results_with_details(self):
        """Test exporting an excel file from the ``get_results_excel_data`` with details"""

        path = os.path.join(settings.MEDIA_ROOT, 'test.xlsx')
        exporter = ExcelDataExporter(path)
        results = self.workshop.get_results_excel_data(self.training_session, detailed=True)

        exporter.add_sheet(**results)
        exporter.save()

        self.assertTrue(os.path.isfile(path))


class WorkshopStepQuestionResultsTestCase(WorkshopTestCase):
    """Test case for the results related method on the ``WorkshopStepQuestion`` model"""

    def get_series_and_styles(self, question):
        """Shortcut method to get the data and the styles as two distinct lists

        Parameters
        ----------
        question: WorkshopStepQuestion
            The workshop question we want the results for

        Returns
        -------
        tuple
            A tuple with two entries:
            - series_list: A list of pandas Series
            - styles: A list of ``EXCEL_STYLE_CHOICES``

        """

        results = question.get_results_excel_data(self.training_session)
        series_list, styles = map(list, zip(*results))  # pylint: disable=bad-builtin

        return series_list, styles

    def check_series(self, series_to_check, expected_series):
        """Shortcut method to check that two list of series are equals

        Parameters
        ----------
        series_to_check: list
            A list of ``pandas.Series`` we to check
        expected_series: list
            A list of ``pandas.Series`` to check against

        """

        for index, series in enumerate(series_to_check):
            with self.subTest(series.name):
                pdt.assert_series_equal(series, expected_series[index])

    def test_question_get_results_excel_data(self):
        """Test the ``get_results_excel_data`` method on a standard question"""

        # One trainee, question not answered
        series_list, styles = self.get_series_and_styles(self.workshop_question)

        expected_series = [
            Series([''], name=answer.label)
            for answer in self.workshop_question.answers.non_deleted()
        ]

        expected_styles = []
        for answer in self.workshop_question.answers.non_deleted():
            if answer.is_correct:
                expected_styles.append(EXCEL_STYLE_CHOICES.WORKSHOP_STEP_ANSWER_VALID)
            else:
                expected_styles.append(EXCEL_STYLE_CHOICES.DEFAULT)

        self.check_series(series_list, expected_series)
        self.assertEqual(styles, expected_styles)

        # With another trainee...
        self.training_session.users.add(self.trainees[1])

        series_list, styles = self.get_series_and_styles(self.workshop_question)

        expected_series = [
            Series(['', ''], name=answer.label)
            for answer in self.workshop_question.answers.non_deleted()
        ]

        self.check_series(series_list, expected_series)

        # First trainee answer the first question
        first_answer = self.workshop_question.answers.non_deleted().first()
        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainee,
            answer=first_answer,
            question=self.workshop_question,
            training_session=self.training_session,
        )

        series_list, styles = self.get_series_and_styles(self.workshop_question)

        # The answered answer is correctly flagged in the series with the "x"
        expected_series[0] = Series(['x', ''], name=first_answer.label)
        self.check_series(series_list, expected_series)

    @override('en')
    def test_open_question_get_results_excel_data(self):
        """Test the ``get_results_excel_data`` method on an open question"""

        # One trainee, question not answered
        series_list, styles = self.get_series_and_styles(self.workshop_open_question)

        pdt.assert_series_equal(series_list[0], Series([''], name=str(_('Verbatim'))))
        self.assertEqual(styles[0], EXCEL_STYLE_CHOICES.DEFAULT)

        # One trainee, question answered
        WorkshopStepQuestionUserAnswerFactory(
            user=self.trainee,
            answer=None,
            question=self.workshop_open_question,
            training_session=self.training_session,
            text='Test'
        )
        series_list, styles = self.get_series_and_styles(self.workshop_open_question)

        pdt.assert_series_equal(series_list[0], Series(['Test'], name=str(_('Verbatim'))))
        self.assertEqual(styles[0], EXCEL_STYLE_CHOICES.DEFAULT)

        # Another trainee join the session
        self.training_session.users.add(self.trainees[1])
        series_list, styles = self.get_series_and_styles(self.workshop_open_question)

        pdt.assert_series_equal(series_list[0], Series(['Test', ''], name=str(_('Verbatim'))))
