"""
Tests for sequence module.
"""
# pylint: disable=no-member


import ast
import json
from datetime import datetime, timedelta
from unittest.mock import Mock, patch

import ddt
from django.test import RequestFactory
from django.test.utils import override_settings
from django.utils.timezone import now
from freezegun import freeze_time
from web_fragments.fragment import Fragment

from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from xmodule.seq_module import TIMED_EXAM_GATING_WAFFLE_FLAG, SequenceBlock
from xmodule.tests import get_test_system
from xmodule.tests.helpers import StubUserService
from xmodule.tests.xml import XModuleXmlImportTest
from xmodule.tests.xml import factories as xml
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW

TODAY = now()
DUE_DATE = TODAY + timedelta(days=7)
PAST_DUE_BEFORE_END_DATE = TODAY + timedelta(days=14)
COURSE_END_DATE = TODAY + timedelta(days=21)


@ddt.ddt
class SequenceBlockTestCase(XModuleXmlImportTest):
    """
    Base class for tests of Sequence Module.
    """

    def setUp(self):
        super().setUp()

        course_xml = self._set_up_course_xml()
        self.course = self.process_xml(course_xml)
        self._set_up_module_system(self.course)

        for chapter_index in range(len(self.course.get_children())):
            chapter = self._set_up_block(self.course, chapter_index)
            setattr(self, f'chapter_{chapter_index + 1}', chapter)

            for sequence_index in range(len(chapter.get_children())):
                sequence = self._set_up_block(chapter, sequence_index)
                setattr(self, f'sequence_{chapter_index + 1}_{sequence_index + 1}', sequence)

    @staticmethod
    def _set_up_course_xml():
        """
        Sets up and returns XML course structure.
        """
        course = xml.CourseFactory.build(end=str(COURSE_END_DATE))

        chapter_1 = xml.ChapterFactory.build(parent=course)  # has 2 child sequences
        xml.ChapterFactory.build(parent=course)  # has 0 child sequences
        chapter_3 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence
        chapter_4 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence, with hide_after_due
        chapter_5 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence, with a time limit

        xml.SequenceFactory.build(parent=chapter_1)
        xml.SequenceFactory.build(parent=chapter_1)
        sequence_3_1 = xml.SequenceFactory.build(parent=chapter_3)  # has 3 verticals
        xml.SequenceFactory.build(  # sequence_4_1
            parent=chapter_4,
            hide_after_due=str(True),
            due=str(DUE_DATE),
        )

        for _ in range(3):
            xml.VerticalFactory.build(parent=sequence_3_1)

        sequence_5_1 = xml.SequenceFactory.build(
            parent=chapter_5,
            is_time_limited=str(True)
        )
        vertical_5_1 = xml.VerticalFactory.build(parent=sequence_5_1)
        xml.ProblemFactory.build(parent=vertical_5_1)

        return course

    def _set_up_block(self, parent, index_in_parent):
        """
        Sets up the stub sequence module for testing.
        """
        block = parent.get_children()[index_in_parent]

        self._set_up_module_system(block)

        block.xmodule_runtime._services['bookmarks'] = Mock()  # pylint: disable=protected-access
        block.xmodule_runtime._services['completion'] = Mock(  # pylint: disable=protected-access
            return_value=Mock(vertical_is_complete=Mock(return_value=True))
        )
        block.xmodule_runtime._services['user'] = StubUserService()  # pylint: disable=protected-access
        block.parent = parent.location
        return block

    def _set_up_module_system(self, block):
        """
        Sets up the test module system for the given block.
        """
        module_system = get_test_system()
        module_system.descriptor_runtime = block._runtime  # pylint: disable=protected-access
        block.xmodule_runtime = module_system

    def _get_rendered_view(self,
                           sequence,
                           requested_child=None,
                           extra_context=None,
                           self_paced=False,
                           view=STUDENT_VIEW):
        """
        Returns the rendered student view for the given sequence and the
        requested_child parameter.
        """
        context = {'requested_child': requested_child}
        if extra_context:
            context.update(extra_context)

        # The render operation will ask modulestore for the current course to get some data. As these tests were
        # originally not written to be compatible with a real modulestore, we've mocked out the relevant return values.
        with patch.object(SequenceBlock, '_get_course') as mock_course:
            self.course.self_paced = self_paced
            mock_course.return_value = self.course
            return sequence.xmodule_runtime.render(sequence, view, context).content

    def _assert_view_at_position(self, rendered_html, expected_position):
        """
        Verifies that the rendered view contains the expected position.
        """
        assert f"'position': {expected_position}" in rendered_html

    def test_student_view_init(self):
        module_system = get_test_system()
        module_system.position = 2
        seq_module = SequenceBlock(runtime=module_system, scope_ids=Mock())
        seq_module.bind_for_student(module_system, 34)
        assert seq_module.position == 2
        # matches position set in the runtime

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_render_student_view(self, view):
        html = self._get_rendered_view(
            self.sequence_3_1,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
            view=view
        )
        self._assert_view_at_position(html, expected_position=1)
        assert str(self.sequence_3_1.location) in html
        assert "'gated': False" in html
        assert "'next_url': 'NextSequential'" in html
        assert "'prev_url': 'PrevSequential'" in html
        assert 'fa fa-check-circle check-circle is-hidden' not in html

    # pylint: disable=line-too-long
    @patch('xmodule.seq_module.SequenceBlock.gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems')
    def test_timed_exam_gating_waffle_flag(self, mocked_function):  # pylint: disable=unused-argument
        """
        Verify the code inside the waffle flag is not executed with the flag off
        Verify the code inside the waffle flag is executed with the flag on
        """
        # the order of the overrides is important since the `assert_not_called` does
        # not appear to be limited to just the override_waffle_flag = False scope
        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=False):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_function.assert_not_called()

        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_function.assert_called_once()

    @override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True)
    def test_that_timed_sequence_gating_respects_access_configurations(self):
        """
        Verify that if a time limited sequence contains content type gated problems, we gate the sequence
        """
        # the one problem in this sequence needs to have graded set to true in order to test content type gating
        self.sequence_5_1.get_children()[0].get_children()[0].graded = True
        gated_fragment = Fragment('i_am_gated')

        # When a time limited sequence contains content type gated problems, the sequence itself is gated
        self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock(  # pylint: disable=protected-access
            check_children_for_content_type_gating_paywall=Mock(return_value=gated_fragment.content),
        ))
        view = self._get_rendered_view(
            self.sequence_5_1,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
            view=STUDENT_VIEW
        )
        assert 'i_am_gated' in view
        # check a few elements to ensure the correct page was loaded
        assert 'seq_module.html' in view
        assert 'NextSequential' in view
        assert 'PrevSequential' in view

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_student_view_first_child(self, view):
        html = self._get_rendered_view(
            self.sequence_3_1, requested_child='first', view=view
        )
        self._assert_view_at_position(html, expected_position=1)

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_student_view_last_child(self, view):
        html = self._get_rendered_view(self.sequence_3_1, requested_child='last', view=view)
        self._assert_view_at_position(html, expected_position=3)

    def test_tooltip(self):
        html = self._get_rendered_view(self.sequence_3_1, requested_child=None)
        for child in self.sequence_3_1.children:
            assert f"'page_title': '{child.block_id}'" in html

    def test_hidden_content_before_due(self):
        html = self._get_rendered_view(self.sequence_4_1)
        assert 'seq_module.html' in html
        assert "'banner_text': None" in html

    def test_hidden_content_past_due(self):
        with freeze_time(COURSE_END_DATE):
            progress_url = 'http://test_progress_link'
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(progress_url=progress_url),
            )
            assert 'hidden_content.html' in html
            assert progress_url in html

    def test_masquerade_hidden_content_past_due(self):
        with freeze_time(COURSE_END_DATE):
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(specific_masquerade=True),
            )
            assert 'seq_module.html' in html
            html = self.get_context_dict_from_string(html)
            assert 'Because the due date has passed, this assignment is hidden from the learner.' == html['banner_text']

    def test_hidden_content_self_paced_past_due_before_end(self):
        with freeze_time(PAST_DUE_BEFORE_END_DATE):
            html = self._get_rendered_view(self.sequence_4_1, self_paced=True)
            assert 'seq_module.html' in html
            assert "'banner_text': None" in html

    def test_hidden_content_self_paced_past_end(self):
        with freeze_time(COURSE_END_DATE + timedelta(days=7)):
            progress_url = 'http://test_progress_link'
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(progress_url=progress_url),
                self_paced=True,
            )
            assert 'hidden_content.html' in html
            assert progress_url in html

    def _assert_gated(self, html, sequence):
        """
        Assert sequence content is gated
        """
        assert 'seq_module.html' in html
        html = self.get_context_dict_from_string(html)
        assert html['banner_text'] is None
        assert [] == html['items']
        assert html['gated_content']['gated']
        assert 'PrereqUrl' == html['gated_content']['prereq_url']
        assert 'PrereqSectionName' == html['gated_content']['prereq_section_name']
        assert str(sequence.display_name) in html['gated_content']['gated_section_name']
        assert 'NextSequential' == html['next_url']
        assert 'PrevSequential' == html['prev_url']

    def _assert_prereq(self, html, sequence):
        """
        Assert sequence is a prerequisite with unfulfilled gates
        """
        assert 'seq_module.html' in html
        html = self.get_context_dict_from_string(html)
        assert 'This section is a prerequisite. You must complete this section in order to unlock additional content.' == html['banner_text']
        assert not html['gated_content']['gated']
        assert str(sequence.location) == html['item_id']
        assert html['gated_content']['prereq_url'] is None
        assert html['gated_content']['prereq_section_name'] is None
        assert 'NextSequential' == html['next_url']
        assert 'PrevSequential' == html['prev_url']

    def _assert_ungated(self, html, sequence):
        """
        Assert sequence is not gated
        """
        assert 'seq_module.html' in html
        assert "'banner_text': None" in html
        assert "'gated': False" in html
        assert str(sequence.location) in html
        assert "'prereq_url': None" in html
        assert "'prereq_section_name': None" in html
        assert "'next_url': 'NextSequential'" in html
        assert "'prev_url': 'PrevSequential'" in html

    def test_gated_content(self):
        """
        Test when sequence is both a prerequisite for a sequence
        and gated on another prerequisite sequence
        """
        # setup seq_1_2 as a gate and gated
        gating_mock_1_2 = Mock()
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = False
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [
            False,
            {'url': 'PrereqUrl', 'display_name': 'PrereqSectionName', 'id': 'mockId'}
        ]
        self.sequence_1_2.xmodule_runtime._services['gating'] = gating_mock_1_2  # pylint: disable=protected-access
        self.sequence_1_2.display_name = 'sequence_1_2'

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )

        # expect content to be gated, with no banner
        self._assert_gated(html, self.sequence_1_2)

        # change seq_1_2 to be ungated, but still a gate (prequiste)
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = False
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}]

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )
        # assert that content and preq banner is shown
        self._assert_prereq(html, self.sequence_1_2)

        # change seq_1_2 to have no unfulfilled gates
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = True
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}]

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )

        # assert content shown as normal
        self._assert_ungated(html, self.sequence_1_2)

    def test_xblock_handler_get_completion_success(self):
        """Test that the completion data is returned successfully on targeted vertical through ajax call"""
        for child in self.sequence_3_1.get_children():
            usage_key = str(child.location)
            request = RequestFactory().post(
                '/',
                data=json.dumps({'usage_key': usage_key}),
                content_type='application/json',
            )
            completion_return = self.sequence_3_1.handle('get_completion', request)
            assert completion_return.json == {'complete': True}

    def test_xblock_handler_get_completion_bad_key(self):
        """Test that the completion data is returned as False when usage key is None through ajax call"""
        request = RequestFactory().post(
            '/',
            data=json.dumps({'usage_key': None}),
            content_type='application/json',
        )
        completion_return = self.sequence_3_1.handle('get_completion', request)
        assert completion_return.json == {'complete': False}

    def test_handle_ajax_get_completion_success(self):
        """Test that the old-style ajax handler for completion still works"""
        for child in self.sequence_3_1.get_children():
            usage_key = str(child.location)
            completion_return = self.sequence_3_1.handle_ajax('get_completion', {'usage_key': usage_key})
            assert json.loads(completion_return) == {'complete': True}

    def test_xblock_handler_goto_position_success(self):
        """Test that we can set position through ajax call"""
        assert self.sequence_3_1.position != 5
        request = RequestFactory().post(
            '/',
            data=json.dumps({'position': 5}),
            content_type='application/json',
        )
        goto_return = self.sequence_3_1.handle('goto_position', request)
        assert goto_return.json == {'success': True}
        assert self.sequence_3_1.position == 5

    def test_xblock_handler_goto_position_bad_position(self):
        """Test that we gracefully handle bad positions as position 1"""
        assert self.sequence_3_1.position != 1
        request = RequestFactory().post(
            '/',
            data=json.dumps({'position': -10}),
            content_type='application/json',
        )
        goto_return = self.sequence_3_1.handle('goto_position', request)
        assert goto_return.json == {'success': True}
        assert self.sequence_3_1.position == 1

    def test_handle_ajax_goto_position_success(self):
        """Test that the old-style ajax handler for setting position still works"""
        goto_return = self.sequence_3_1.handle_ajax('goto_position', {'position': 5})
        assert json.loads(goto_return) == {'success': True}
        assert self.sequence_3_1.position == 5

    def test_get_metadata(self):
        """Test that the sequence metadata is returned correctly"""
        # rather than dealing with json serialization of the Mock object,
        # let's just disable the bookmarks service
        self.sequence_3_1.xmodule_runtime._services['bookmarks'] = None  # lint-amnesty, pylint: disable=protected-access
        metadata = self.sequence_3_1.get_metadata()
        assert len(metadata['items']) == 3
        assert metadata['tag'] == 'sequential'
        assert metadata['display_name'] == self.sequence_3_1.display_name_with_default

    @override_settings(FIELD_OVERRIDE_PROVIDERS=(
        'openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride',
    ))
    def test_get_metadata_content_type_gated_content(self):
        """The contains_content_type_gated_content field tells whether the item contains content type gated content"""
        self.sequence_5_1.xmodule_runtime._services['bookmarks'] = None  # pylint: disable=protected-access
        ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
        metadata = self.sequence_5_1.get_metadata()
        assert metadata['items'][0]['contains_content_type_gated_content'] is False

        # When a block contains content type gated problems, set the contains_content_type_gated_content field
        self.sequence_5_1.get_children()[0].get_children()[0].graded = True
        self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock(  # pylint: disable=protected-access
            enabled_for_enrollment=Mock(return_value=True),
            content_type_gate_for_block=Mock(return_value=Fragment('i_am_gated'))
        ))
        metadata = self.sequence_5_1.get_metadata()
        assert metadata['items'][0]['contains_content_type_gated_content'] is True

    def get_context_dict_from_string(self, data):
        """
        Retrieve dictionary from string.
        """
        # Replace tuple and un-necessary info from inside string and get the dictionary.
        cleaned_data = data.replace("(('seq_module.html',\n", '').replace("),\n {})", '').strip()
        return ast.literal_eval(cleaned_data)

"""
Tests for sequence module.
"""
# pylint: disable=no-member


import ast
import json
from datetime import datetime, timedelta
from unittest.mock import Mock, patch

import ddt
from django.test import RequestFactory
from django.test.utils import override_settings
from django.utils.timezone import now
from freezegun import freeze_time
from web_fragments.fragment import Fragment

from edx_toggles.toggles.testutils import override_waffle_flag
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from xmodule.seq_module import TIMED_EXAM_GATING_WAFFLE_FLAG, SequenceBlock
from xmodule.tests import get_test_system
from xmodule.tests.helpers import StubUserService
from xmodule.tests.xml import XModuleXmlImportTest
from xmodule.tests.xml import factories as xml
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW

TODAY = now()
DUE_DATE = TODAY + timedelta(days=7)
PAST_DUE_BEFORE_END_DATE = TODAY + timedelta(days=14)
COURSE_END_DATE = TODAY + timedelta(days=21)


@ddt.ddt
class SequenceBlockTestCase(XModuleXmlImportTest):
    """
    Base class for tests of Sequence Module.
    """

    def setUp(self):
        super().setUp()

        course_xml = self._set_up_course_xml()
        self.course = self.process_xml(course_xml)
        self._set_up_module_system(self.course)

        for chapter_index in range(len(self.course.get_children())):
            chapter = self._set_up_block(self.course, chapter_index)
            setattr(self, f'chapter_{chapter_index + 1}', chapter)

            for sequence_index in range(len(chapter.get_children())):
                sequence = self._set_up_block(chapter, sequence_index)
                setattr(self, f'sequence_{chapter_index + 1}_{sequence_index + 1}', sequence)

    @staticmethod
    def _set_up_course_xml():
        """
        Sets up and returns XML course structure.
        """
        course = xml.CourseFactory.build(end=str(COURSE_END_DATE))

        chapter_1 = xml.ChapterFactory.build(parent=course)  # has 2 child sequences
        xml.ChapterFactory.build(parent=course)  # has 0 child sequences
        chapter_3 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence
        chapter_4 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence, with hide_after_due
        chapter_5 = xml.ChapterFactory.build(parent=course)  # has 1 child sequence, with a time limit

        xml.SequenceFactory.build(parent=chapter_1)
        xml.SequenceFactory.build(parent=chapter_1)
        sequence_3_1 = xml.SequenceFactory.build(parent=chapter_3)  # has 3 verticals
        xml.SequenceFactory.build(  # sequence_4_1
            parent=chapter_4,
            hide_after_due=str(True),
            due=str(DUE_DATE),
        )

        for _ in range(3):
            xml.VerticalFactory.build(parent=sequence_3_1)

        sequence_5_1 = xml.SequenceFactory.build(
            parent=chapter_5,
            is_time_limited=str(True)
        )
        vertical_5_1 = xml.VerticalFactory.build(parent=sequence_5_1)
        xml.ProblemFactory.build(parent=vertical_5_1)

        return course

    def _set_up_block(self, parent, index_in_parent):
        """
        Sets up the stub sequence module for testing.
        """
        block = parent.get_children()[index_in_parent]

        self._set_up_module_system(block)

        block.xmodule_runtime._services['bookmarks'] = Mock()  # pylint: disable=protected-access
        block.xmodule_runtime._services['completion'] = Mock(  # pylint: disable=protected-access
            return_value=Mock(vertical_is_complete=Mock(return_value=True))
        )
        block.xmodule_runtime._services['user'] = StubUserService()  # pylint: disable=protected-access
        block.parent = parent.location
        return block

    def _set_up_module_system(self, block):
        """
        Sets up the test module system for the given block.
        """
        module_system = get_test_system()
        module_system.descriptor_runtime = block._runtime  # pylint: disable=protected-access
        block.xmodule_runtime = module_system

    def _get_rendered_view(self,
                           sequence,
                           requested_child=None,
                           extra_context=None,
                           self_paced=False,
                           view=STUDENT_VIEW):
        """
        Returns the rendered student view for the given sequence and the
        requested_child parameter.
        """
        context = {'requested_child': requested_child}
        if extra_context:
            context.update(extra_context)

        # The render operation will ask modulestore for the current course to get some data. As these tests were
        # originally not written to be compatible with a real modulestore, we've mocked out the relevant return values.
        with patch.object(SequenceBlock, '_get_course') as mock_course:
            self.course.self_paced = self_paced
            mock_course.return_value = self.course
            return sequence.xmodule_runtime.render(sequence, view, context).content

    def _assert_view_at_position(self, rendered_html, expected_position):
        """
        Verifies that the rendered view contains the expected position.
        """
        assert f"'position': {expected_position}" in rendered_html

    def test_student_view_init(self):
        module_system = get_test_system()
        module_system.position = 2
        seq_module = SequenceBlock(runtime=module_system, scope_ids=Mock())
        seq_module.bind_for_student(module_system, 34)
        assert seq_module.position == 2
        # matches position set in the runtime

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_render_student_view(self, view):
        html = self._get_rendered_view(
            self.sequence_3_1,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
            view=view
        )
        self._assert_view_at_position(html, expected_position=1)
        assert str(self.sequence_3_1.location) in html
        assert "'gated': False" in html
        assert "'next_url': 'NextSequential'" in html
        assert "'prev_url': 'PrevSequential'" in html
        assert 'fa fa-check-circle check-circle is-hidden' not in html

    # pylint: disable=line-too-long
    @patch('xmodule.seq_module.SequenceBlock.gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems')
    def test_timed_exam_gating_waffle_flag(self, mocked_function):  # pylint: disable=unused-argument
        """
        Verify the code inside the waffle flag is not executed with the flag off
        Verify the code inside the waffle flag is executed with the flag on
        """
        # the order of the overrides is important since the `assert_not_called` does
        # not appear to be limited to just the override_waffle_flag = False scope
        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=False):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_function.assert_not_called()

        with override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True):
            self._get_rendered_view(
                self.sequence_5_1,
                extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
                view=STUDENT_VIEW
            )
            mocked_function.assert_called_once()

    @override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True)
    def test_that_timed_sequence_gating_respects_access_configurations(self):
        """
        Verify that if a time limited sequence contains content type gated problems, we gate the sequence
        """
        # the one problem in this sequence needs to have graded set to true in order to test content type gating
        self.sequence_5_1.get_children()[0].get_children()[0].graded = True
        gated_fragment = Fragment('i_am_gated')

        # When a time limited sequence contains content type gated problems, the sequence itself is gated
        self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock(  # pylint: disable=protected-access
            check_children_for_content_type_gating_paywall=Mock(return_value=gated_fragment.content),
        ))
        view = self._get_rendered_view(
            self.sequence_5_1,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
            view=STUDENT_VIEW
        )
        assert 'i_am_gated' in view
        # check a few elements to ensure the correct page was loaded
        assert 'seq_module.html' in view
        assert 'NextSequential' in view
        assert 'PrevSequential' in view

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_student_view_first_child(self, view):
        html = self._get_rendered_view(
            self.sequence_3_1, requested_child='first', view=view
        )
        self._assert_view_at_position(html, expected_position=1)

    @ddt.unpack
    @ddt.data(
        {'view': STUDENT_VIEW},
        {'view': PUBLIC_VIEW},
    )
    def test_student_view_last_child(self, view):
        html = self._get_rendered_view(self.sequence_3_1, requested_child='last', view=view)
        self._assert_view_at_position(html, expected_position=3)

    def test_tooltip(self):
        html = self._get_rendered_view(self.sequence_3_1, requested_child=None)
        for child in self.sequence_3_1.children:
            assert f"'page_title': '{child.block_id}'" in html

    def test_hidden_content_before_due(self):
        html = self._get_rendered_view(self.sequence_4_1)
        assert 'seq_module.html' in html
        assert "'banner_text': None" in html

    def test_hidden_content_past_due(self):
        with freeze_time(COURSE_END_DATE):
            progress_url = 'http://test_progress_link'
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(progress_url=progress_url),
            )
            assert 'hidden_content.html' in html
            assert progress_url in html

    def test_masquerade_hidden_content_past_due(self):
        with freeze_time(COURSE_END_DATE):
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(specific_masquerade=True),
            )
            assert 'seq_module.html' in html
            html = self.get_context_dict_from_string(html)
            assert 'Because the due date has passed, this assignment is hidden from the learner.' == html['banner_text']

    def test_hidden_content_self_paced_past_due_before_end(self):
        with freeze_time(PAST_DUE_BEFORE_END_DATE):
            html = self._get_rendered_view(self.sequence_4_1, self_paced=True)
            assert 'seq_module.html' in html
            assert "'banner_text': None" in html

    def test_hidden_content_self_paced_past_end(self):
        with freeze_time(COURSE_END_DATE + timedelta(days=7)):
            progress_url = 'http://test_progress_link'
            html = self._get_rendered_view(
                self.sequence_4_1,
                extra_context=dict(progress_url=progress_url),
                self_paced=True,
            )
            assert 'hidden_content.html' in html
            assert progress_url in html

    def _assert_gated(self, html, sequence):
        """
        Assert sequence content is gated
        """
        assert 'seq_module.html' in html
        html = self.get_context_dict_from_string(html)
        assert html['banner_text'] is None
        assert [] == html['items']
        assert html['gated_content']['gated']
        assert 'PrereqUrl' == html['gated_content']['prereq_url']
        assert 'PrereqSectionName' == html['gated_content']['prereq_section_name']
        assert str(sequence.display_name) in html['gated_content']['gated_section_name']
        assert 'NextSequential' == html['next_url']
        assert 'PrevSequential' == html['prev_url']

    def _assert_prereq(self, html, sequence):
        """
        Assert sequence is a prerequisite with unfulfilled gates
        """
        assert 'seq_module.html' in html
        html = self.get_context_dict_from_string(html)
        assert 'This section is a prerequisite. You must complete this section in order to unlock additional content.' == html['banner_text']
        assert not html['gated_content']['gated']
        assert str(sequence.location) == html['item_id']
        assert html['gated_content']['prereq_url'] is None
        assert html['gated_content']['prereq_section_name'] is None
        assert 'NextSequential' == html['next_url']
        assert 'PrevSequential' == html['prev_url']

    def _assert_ungated(self, html, sequence):
        """
        Assert sequence is not gated
        """
        assert 'seq_module.html' in html
        assert "'banner_text': None" in html
        assert "'gated': False" in html
        assert str(sequence.location) in html
        assert "'prereq_url': None" in html
        assert "'prereq_section_name': None" in html
        assert "'next_url': 'NextSequential'" in html
        assert "'prev_url': 'PrevSequential'" in html

    def test_gated_content(self):
        """
        Test when sequence is both a prerequisite for a sequence
        and gated on another prerequisite sequence
        """
        # setup seq_1_2 as a gate and gated
        gating_mock_1_2 = Mock()
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = False
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [
            False,
            {'url': 'PrereqUrl', 'display_name': 'PrereqSectionName', 'id': 'mockId'}
        ]
        self.sequence_1_2.xmodule_runtime._services['gating'] = gating_mock_1_2  # pylint: disable=protected-access
        self.sequence_1_2.display_name = 'sequence_1_2'

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )

        # expect content to be gated, with no banner
        self._assert_gated(html, self.sequence_1_2)

        # change seq_1_2 to be ungated, but still a gate (prequiste)
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = False
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}]

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )
        # assert that content and preq banner is shown
        self._assert_prereq(html, self.sequence_1_2)

        # change seq_1_2 to have no unfulfilled gates
        gating_mock_1_2.return_value.is_gate_fulfilled.return_value = True
        gating_mock_1_2.return_value.required_prereq.return_value = True
        gating_mock_1_2.return_value.compute_is_prereq_met.return_value = [True, {}]

        html = self._get_rendered_view(
            self.sequence_1_2,
            extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
        )

        # assert content shown as normal
        self._assert_ungated(html, self.sequence_1_2)

    def test_xblock_handler_get_completion_success(self):
        """Test that the completion data is returned successfully on targeted vertical through ajax call"""
        for child in self.sequence_3_1.get_children():
            usage_key = str(child.location)
            request = RequestFactory().post(
                '/',
                data=json.dumps({'usage_key': usage_key}),
                content_type='application/json',
            )
            completion_return = self.sequence_3_1.handle('get_completion', request)
            assert completion_return.json == {'complete': True}

    def test_xblock_handler_get_completion_bad_key(self):
        """Test that the completion data is returned as False when usage key is None through ajax call"""
        request = RequestFactory().post(
            '/',
            data=json.dumps({'usage_key': None}),
            content_type='application/json',
        )
        completion_return = self.sequence_3_1.handle('get_completion', request)
        assert completion_return.json == {'complete': False}

    def test_handle_ajax_get_completion_success(self):
        """Test that the old-style ajax handler for completion still works"""
        for child in self.sequence_3_1.get_children():
            usage_key = str(child.location)
            completion_return = self.sequence_3_1.handle_ajax('get_completion', {'usage_key': usage_key})
            assert json.loads(completion_return) == {'complete': True}

    def test_xblock_handler_goto_position_success(self):
        """Test that we can set position through ajax call"""
        assert self.sequence_3_1.position != 5
        request = RequestFactory().post(
            '/',
            data=json.dumps({'position': 5}),
            content_type='application/json',
        )
        goto_return = self.sequence_3_1.handle('goto_position', request)
        assert goto_return.json == {'success': True}
        assert self.sequence_3_1.position == 5

    def test_xblock_handler_goto_position_bad_position(self):
        """Test that we gracefully handle bad positions as position 1"""
        assert self.sequence_3_1.position != 1
        request = RequestFactory().post(
            '/',
            data=json.dumps({'position': -10}),
            content_type='application/json',
        )
        goto_return = self.sequence_3_1.handle('goto_position', request)
        assert goto_return.json == {'success': True}
        assert self.sequence_3_1.position == 1

    def test_handle_ajax_goto_position_success(self):
        """Test that the old-style ajax handler for setting position still works"""
        goto_return = self.sequence_3_1.handle_ajax('goto_position', {'position': 5})
        assert json.loads(goto_return) == {'success': True}
        assert self.sequence_3_1.position == 5

    def test_get_metadata(self):
        """Test that the sequence metadata is returned correctly"""
        # rather than dealing with json serialization of the Mock object,
        # let's just disable the bookmarks service
        self.sequence_3_1.xmodule_runtime._services['bookmarks'] = None  # lint-amnesty, pylint: disable=protected-access
        metadata = self.sequence_3_1.get_metadata()
        assert len(metadata['items']) == 3
        assert metadata['tag'] == 'sequential'
        assert metadata['display_name'] == self.sequence_3_1.display_name_with_default

    @override_settings(FIELD_OVERRIDE_PROVIDERS=(
        'openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride',
    ))
    def test_get_metadata_content_type_gated_content(self):
        """The contains_content_type_gated_content field tells whether the item contains content type gated content"""
        self.sequence_5_1.xmodule_runtime._services['bookmarks'] = None  # pylint: disable=protected-access
        ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1))
        metadata = self.sequence_5_1.get_metadata()
        assert metadata['items'][0]['contains_content_type_gated_content'] is False

        # When a block contains content type gated problems, set the contains_content_type_gated_content field
        self.sequence_5_1.get_children()[0].get_children()[0].graded = True
        self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock(  # pylint: disable=protected-access
            enabled_for_enrollment=Mock(return_value=True),
            content_type_gate_for_block=Mock(return_value=Fragment('i_am_gated'))
        ))
        metadata = self.sequence_5_1.get_metadata()
        assert metadata['items'][0]['contains_content_type_gated_content'] is True

    def get_context_dict_from_string(self, data):
        """
        Retrieve dictionary from string.
        """
        # Replace tuple and un-necessary info from inside string and get the dictionary.
        cleaned_data = data.replace("(('seq_module.html',\n", '').replace("),\n {})", '').strip()
        return ast.literal_eval(cleaned_data)

Tests for sequence module. # pylint: disable=no-member Base class for tests of Sequence Module. Sets up and returns XML course structure. # has 2 child sequences # has 0 child sequences # has 1 child sequence # has 1 child sequence, with hide_after_due # has 1 child sequence, with a time limit # has 3 verticals # sequence_4_1 Sets up the stub sequence module for testing. # pylint: disable=protected-access # pylint: disable=protected-access # pylint: disable=protected-access Sets up the test module system for the given block. # pylint: disable=protected-access Returns the rendered student view for the given sequence and the         requested_child parameter. # The render operation will ask modulestore for the current course to get some data. As these tests were # originally not written to be compatible with a real modulestore, we've mocked out the relevant return values. Verifies that the rendered view contains the expected position. # matches position set in the runtime # pylint: disable=line-too-long # pylint: disable=unused-argument Verify the code inside the waffle flag is not executed with the flag off         Verify the code inside the waffle flag is executed with the flag on # the order of the overrides is important since the `assert_not_called` does # not appear to be limited to just the override_waffle_flag = False scope Verify that if a time limited sequence contains content type gated problems, we gate the sequence # the one problem in this sequence needs to have graded set to true in order to test content type gating # When a time limited sequence contains content type gated problems, the sequence itself is gated # pylint: disable=protected-access # check a few elements to ensure the correct page was loaded Assert sequence content is gated Assert sequence is a prerequisite with unfulfilled gates Assert sequence is not gated Test when sequence is both a prerequisite for a sequence         and gated on another prerequisite sequence # setup seq_1_2 as a gate and gated # pylint: disable=protected-access # expect content to be gated, with no banner # change seq_1_2 to be ungated, but still a gate (prequiste) # assert that content and preq banner is shown # change seq_1_2 to have no unfulfilled gates # assert content shown as normal Test that the completion data is returned successfully on targeted vertical through ajax call Test that the completion data is returned as False when usage key is None through ajax call Test that the old-style ajax handler for completion still works Test that we can set position through ajax call Test that we gracefully handle bad positions as position 1 Test that the old-style ajax handler for setting position still works Test that the sequence metadata is returned correctly # rather than dealing with json serialization of the Mock object, # let's just disable the bookmarks service # lint-amnesty, pylint: disable=protected-access The contains_content_type_gated_content field tells whether the item contains content type gated content # pylint: disable=protected-access # When a block contains content type gated problems, set the contains_content_type_gated_content field # pylint: disable=protected-access Retrieve dictionary from string. # Replace tuple and un-necessary info from inside string and get the dictionary.