Beispiel #1
0
    def test_get_base_url(self):
        request = RequestFactory().get('/')
        root_url = utils.get_base_url(request)
        eq_(root_url, 'http://testserver')

        request = RequestFactory().get('/', SERVER_PORT=443)
        request.is_secure = lambda: True
        assert request.is_secure()
        root_url = utils.get_base_url(request)
        eq_(root_url, 'https://testserver')
Beispiel #2
0
def _make_resp(mw_cls, secure=False):
    mw = mw_cls()
    req = RequestFactory().get('/')
    if secure:
        req.is_secure = lambda: True
    resp = mw.process_response(req, HttpResponse())
    return resp
    def test_abs_static_with_STATIC_URL_with_https(self):
        context = {}
        context['request'] = RequestFactory().get('/')
        context['request'].is_secure = lambda: True
        assert context['request'].is_secure()

        with self.settings(STATIC_URL='//my.cdn.com/static/'):
            result = abs_static(context, 'foo.png')
            eq_(result, 'https://my.cdn.com/static/foo.png')
Beispiel #4
0
def get_request_for_user(user):
    """Create a request object for user."""

    request = RequestFactory()
    request.user = user
    request.META = {}
    request.is_secure = lambda: True
    request.get_host = lambda: "edx.org"
    return request
Beispiel #5
0
    def test_abs_static_with_STATIC_URL_with_https(self):
        context = {}
        context['request'] = RequestFactory().get('/')
        context['request'].is_secure = lambda: True
        assert context['request'].is_secure()

        absolute_relative_path = self._create_static_file('foo.png', 'data')

        with self.settings(STATIC_URL='//my.cdn.com/static/'):
            result = abs_static(context, 'foo.png')
            eq_(result, 'https://my.cdn.com%s' % absolute_relative_path)
Beispiel #6
0
def get_request_for_user(user):
    """Create a request object for user."""

    request = RequestFactory()
    request.user = user
    request.COOKIES = {}
    request.META = {}
    request.is_secure = lambda: True
    request.get_host = lambda: "edx.org"
    request.method = 'GET'
    return request
Beispiel #7
0
    def test_abs_static_with_STATIC_URL_with_https(self):
        context = {}
        context['request'] = RequestFactory().get('/')
        context['request'].is_secure = lambda: True
        assert context['request'].is_secure()

        absolute_relative_path = self._create_static_file('foo.png', 'data')

        with self.settings(STATIC_URL='//my.cdn.com/static/'):
            result = abs_static(context, 'foo.png')
            eq_(result, 'https://my.cdn.com%s' % absolute_relative_path)
Beispiel #8
0
    def test_make_absolute(self):
        context = {}
        context['request'] = RequestFactory().get('/')

        result = make_absolute(context, reverse('main:home'))
        eq_(result, 'http://testserver/')

        result = make_absolute(context, result)
        eq_(result, 'http://testserver/')

        result = make_absolute(context, '//some.cdn.com/foo.js')
        eq_(result, 'http://some.cdn.com/foo.js')

        context['request'].is_secure = lambda: True
        result = make_absolute(context, '//some.cdn.com/foo.js')
        eq_(result, 'https://some.cdn.com/foo.js')
Beispiel #9
0
    def test_make_absolute(self):
        context = {}
        context['request'] = RequestFactory().get('/')

        result = make_absolute(context, reverse('main:home'))
        eq_(result, 'http://testserver/')

        result = make_absolute(context, result)
        eq_(result, 'http://testserver/')

        result = make_absolute(context, '//some.cdn.com/foo.js')
        eq_(result, 'http://some.cdn.com/foo.js')

        context['request'].is_secure = lambda: True
        result = make_absolute(context, '//some.cdn.com/foo.js')
        eq_(result, 'https://some.cdn.com/foo.js')
Beispiel #10
0
class TestUserPreferenceMiddleware(CacheIsolationTestCase):
    """
    Tests to make sure user preferences are getting properly set in the middleware.
    """
    def setUp(self):
        super(TestUserPreferenceMiddleware, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
        self.middleware = LanguagePreferenceMiddleware()
        self.session_middleware = SessionMiddleware()
        self.user = UserFactory.create()
        self.anonymous_user = AnonymousUserFactory()
        self.request = RequestFactory().get('/somewhere')
        self.request.user = self.user
        self.request.META['HTTP_ACCEPT_LANGUAGE'] = 'ar;q=1.0'
        self.session_middleware.process_request(self.request)
        self.client = Client()

    def test_logout_shouldnt_remove_cookie(self):

        self.middleware.process_request(self.request)

        self.request.user = self.anonymous_user

        response = mock.Mock(spec=HttpResponse)
        self.middleware.process_response(self.request, response)

        response.delete_cookie.assert_not_called()

    @ddt.data(None, 'es', 'en')
    def test_preference_setting_changes_cookie(self, lang_pref_out):
        """
        Test that the LANGUAGE_COOKIE is always set to the user's current language preferences
        at the end of the request, with an expiry that's the same as the users current session cookie.
        """
        if lang_pref_out:
            set_user_preference(self.user, LANGUAGE_KEY, lang_pref_out)
        else:
            delete_user_preference(self.user, LANGUAGE_KEY)

        response = mock.Mock(spec=HttpResponse)
        self.middleware.process_response(self.request, response)

        if lang_pref_out:
            response.set_cookie.assert_called_with(
                settings.LANGUAGE_COOKIE,
                value=lang_pref_out,
                domain=settings.SESSION_COOKIE_DOMAIN,
                max_age=COOKIE_DURATION,
                secure=self.request.is_secure(),
            )
        else:
            response.delete_cookie.assert_called_with(
                settings.LANGUAGE_COOKIE,
                domain=settings.SESSION_COOKIE_DOMAIN,
            )

        assert LANGUAGE_SESSION_KEY not in self.request.session

    @ddt.data(*itertools.product(
        (None, 'eo', 'es'),  # LANGUAGE_COOKIE
        (None, 'es', 'en'),  # Language Preference In
    ))
    @ddt.unpack
    @mock.patch(
        'openedx.core.djangoapps.lang_pref.middleware.set_user_preference')
    def test_preference_cookie_changes_setting(self, lang_cookie, lang_pref_in,
                                               mock_set_user_preference):
        self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie

        if lang_pref_in:
            set_user_preference(self.user, LANGUAGE_KEY, lang_pref_in)
        else:
            delete_user_preference(self.user, LANGUAGE_KEY)

        self.middleware.process_request(self.request)

        if lang_cookie is None:
            assert mock_set_user_preference.mock_calls == []
        else:
            mock_set_user_preference.assert_called_with(
                self.user, LANGUAGE_KEY, lang_cookie)

    @ddt.data(*(
        (logged_in, ) + test_def for logged_in in (True, False)
        for test_def in [
            # (LANGUAGE_COOKIE, LANGUAGE_SESSION_KEY, Accept-Language In,
            #  Accept-Language Out, Session Lang Out)
            (None, None, None, None, None),
            (None, 'eo', None, None, 'eo'),
            (None, 'en', None, None, 'en'),
            (None, 'eo', 'en', 'en', 'eo'),
            (None, None, 'en', 'en', None),
            ('en', None, None, 'en', None),
            ('en', 'en', None, 'en', 'en'),
            ('en', None, 'eo', 'en;q=1.0,eo', None),
            ('en', None, 'en', 'en', None),
            ('en', 'eo', 'en', 'en', None),
            ('en', 'eo', 'eo', 'en;q=1.0,eo', None)
        ]))
    @ddt.unpack
    def test_preference_cookie_overrides_browser(
        self,
        logged_in,
        lang_cookie,
        lang_session_in,
        accept_lang_in,
        accept_lang_out,
        lang_session_out,
    ):
        if not logged_in:
            self.request.user = self.anonymous_user
        if lang_cookie:
            self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie
        if lang_session_in:
            self.request.session[LANGUAGE_SESSION_KEY] = lang_session_in
        if accept_lang_in:
            self.request.META['HTTP_ACCEPT_LANGUAGE'] = accept_lang_in
        else:
            del self.request.META['HTTP_ACCEPT_LANGUAGE']

        self.middleware.process_request(self.request)

        accept_lang_result = self.request.META.get('HTTP_ACCEPT_LANGUAGE')
        if accept_lang_result:
            accept_lang_result = parse_accept_lang_header(accept_lang_result)

        if accept_lang_out:
            accept_lang_out = parse_accept_lang_header(accept_lang_out)

        if accept_lang_out and accept_lang_result:
            six.assertCountEqual(self, accept_lang_result, accept_lang_out)
        else:
            assert accept_lang_result == accept_lang_out

        assert self.request.session.get(
            LANGUAGE_SESSION_KEY) == lang_session_out

    @ddt.data(None, 'es', 'en')
    def test_logout_preserves_cookie(self, lang_cookie):
        if lang_cookie:
            self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie
        elif settings.LANGUAGE_COOKIE in self.client.cookies:
            del self.client.cookies[settings.LANGUAGE_COOKIE]
        # Use an actual call to the logout endpoint, because the logout function
        # explicitly clears all cookies
        self.client.get(reverse('logout'))
        if lang_cookie:
            assert self.client.cookies[
                settings.LANGUAGE_COOKIE].value == lang_cookie
        else:
            assert settings.LANGUAGE_COOKIE not in self.client.cookies

    @ddt.data((None, None), ('es', 'es-419'), ('en', 'en'),
              ('es-419', 'es-419'))
    @ddt.unpack
    def test_login_captures_lang_pref(self, lang_cookie, expected_lang):
        if lang_cookie:
            self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie
        elif settings.LANGUAGE_COOKIE in self.client.cookies:
            del self.client.cookies[settings.LANGUAGE_COOKIE]

        # Use an actual call to the login endpoint, to validate that the middleware
        # stack does the right thing
        response = self.client.post(
            reverse('user_api_login_session'),
            data={
                'email': self.user.email,
                'password': UserFactory._DEFAULT_PASSWORD,  # pylint: disable=protected-access
                'remember': True,
            })

        assert response.status_code == 200

        if lang_cookie:
            assert response['Content-Language'] == expected_lang
            assert get_user_preference(self.user, LANGUAGE_KEY) == lang_cookie
            assert self.client.cookies[
                settings.LANGUAGE_COOKIE].value == lang_cookie
        else:
            assert response['Content-Language'] == 'en'
            assert get_user_preference(self.user, LANGUAGE_KEY) is None
            assert self.client.cookies[settings.LANGUAGE_COOKIE].value == ''

    def test_process_response_no_user_noop(self):
        del self.request.user
        response = mock.Mock(spec=HttpResponse)

        result = self.middleware.process_response(self.request, response)

        assert result is response
        assert response.mock_calls == []

    def test_preference_update_noop(self):
        self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'es'

        # No preference yet, should write to the database

        assert get_user_preference(self.user, LANGUAGE_KEY) is None
        self.middleware.process_request(self.request)
        assert get_user_preference(self.user, LANGUAGE_KEY) == 'es'

        response = mock.Mock(spec=HttpResponse)

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)

        # Preference is the same as the cookie, shouldn't write to the database

        with self.assertNumQueries(3):
            self.middleware.process_request(self.request)

        assert get_user_preference(self.user, LANGUAGE_KEY) == 'es'

        response = mock.Mock(spec=HttpResponse)

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)

        # Cookie changed, should write to the database again

        self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'en'
        self.middleware.process_request(self.request)
        assert get_user_preference(self.user, LANGUAGE_KEY) == 'en'

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)

    @mock.patch(
        'openedx.core.djangoapps.lang_pref.middleware.is_request_from_mobile_app'
    )
    @mock.patch(
        'openedx.core.djangoapps.lang_pref.middleware.get_user_preference')
    def test_remove_lang_cookie_for_mobile_app(self, mock_get_user_preference,
                                               mock_is_mobile_request):
        """
        Test to verify language preference cookie removed for mobile app requests.
        """
        mock_get_user_preference.return_value = 'en'
        mock_is_mobile_request.return_value = False
        response = mock.Mock(spec=HttpResponse)

        response = self.middleware.process_response(self.request, response)
        response.delete_cookie.assert_not_called()
        response.set_cookie.assert_called()

        mock_is_mobile_request.return_value = True
        response = self.middleware.process_response(self.request, response)
        response.delete_cookie.assert_called()
Beispiel #11
0
class EdxNotesHelpersTest(ModuleStoreTestCase):
    """
    Tests for EdxNotes helpers.
    """
    def setUp(self):
        """
        Setup a dummy course content.
        """
        super(EdxNotesHelpersTest, self).setUp()

        # There are many tests that are comparing locators as returned from helper methods. When using
        # the split modulestore, some of those locators have version and branch information, but the
        # comparison values do not. This needs further investigation in order to enable these tests
        # with the split modulestore.
        with self.store.default_store(ModuleStoreEnum.Type.mongo):
            ClientFactory(name="edx-notes")
            self.course = CourseFactory.create()
            self.chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
            self.chapter_2 = ItemFactory.create(category="chapter", parent_location=self.course.location)
            self.sequential = ItemFactory.create(category="sequential", parent_location=self.chapter.location)
            self.vertical = ItemFactory.create(category="vertical", parent_location=self.sequential.location)
            self.html_module_1 = ItemFactory.create(category="html", parent_location=self.vertical.location)
            self.html_module_2 = ItemFactory.create(category="html", parent_location=self.vertical.location)
            self.vertical_with_container = ItemFactory.create(
                category='vertical', parent_location=self.sequential.location
            )
            self.child_container = ItemFactory.create(
                category='split_test', parent_location=self.vertical_with_container.location)
            self.child_vertical = ItemFactory.create(category='vertical', parent_location=self.child_container.location)
            self.child_html_module = ItemFactory.create(category="html", parent_location=self.child_vertical.location)

            # Read again so that children lists are accurate
            self.course = self.store.get_item(self.course.location)
            self.chapter = self.store.get_item(self.chapter.location)
            self.chapter_2 = self.store.get_item(self.chapter_2.location)
            self.sequential = self.store.get_item(self.sequential.location)
            self.vertical = self.store.get_item(self.vertical.location)

            self.vertical_with_container = self.store.get_item(self.vertical_with_container.location)
            self.child_container = self.store.get_item(self.child_container.location)
            self.child_vertical = self.store.get_item(self.child_vertical.location)
            self.child_html_module = self.store.get_item(self.child_html_module.location)

            self.user = UserFactory.create(username="******", email="*****@*****.**", password="******")
            self.client.login(username=self.user.username, password="******")

        self.request = RequestFactory().request()
        self.request.user = self.user

    def _get_unit_url(self, course, chapter, section, position=1):
        """
        Returns `jump_to_id` url for the `vertical`.
        """
        return reverse('courseware_position', kwargs={
            'course_id': course.id,
            'chapter': chapter.url_name,
            'section': section.url_name,
            'position': position,
        })

    def test_edxnotes_harvard_notes_enabled(self):
        """
        Tests that edxnotes are disabled when Harvard Annotation Tool is enabled.
        """
        self.course.advanced_modules = ["foo", "imageannotation", "boo"]
        self.assertFalse(helpers.is_feature_enabled(self.course))

        self.course.advanced_modules = ["foo", "boo", "videoannotation"]
        self.assertFalse(helpers.is_feature_enabled(self.course))

        self.course.advanced_modules = ["textannotation", "foo", "boo"]
        self.assertFalse(helpers.is_feature_enabled(self.course))

        self.course.advanced_modules = ["textannotation", "videoannotation", "imageannotation"]
        self.assertFalse(helpers.is_feature_enabled(self.course))

    @ddt.unpack
    @ddt.data(
        {'_edxnotes': True},
        {'_edxnotes': False}
    )
    def test_is_feature_enabled(self, _edxnotes):
        """
        Tests that is_feature_enabled shows correct behavior.
        """
        self.course.edxnotes = _edxnotes
        self.assertEqual(helpers.is_feature_enabled(self.course), _edxnotes)

    @ddt.data(
        helpers.get_public_endpoint,
        helpers.get_internal_endpoint,
    )
    def test_get_endpoints(self, get_endpoint_function):
        """
        Test that the get_public_endpoint and get_internal_endpoint functions
        return appropriate values.
        """
        @contextmanager
        def patch_edxnotes_api_settings(url):
            """
            Convenience function for patching both EDXNOTES_PUBLIC_API and
            EDXNOTES_INTERNAL_API.
            """
            with override_settings(EDXNOTES_PUBLIC_API=url):
                with override_settings(EDXNOTES_INTERNAL_API=url):
                    yield

        # url ends with "/"
        with patch_edxnotes_api_settings("http://example.com/"):
            self.assertEqual("http://example.com/", get_endpoint_function())

        # url doesn't have "/" at the end
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/", get_endpoint_function())

        # url with path that starts with "/"
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/some_path/", get_endpoint_function("/some_path"))

        # url with path without "/"
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/some_path/", get_endpoint_function("some_path/"))

        # url is not configured
        with patch_edxnotes_api_settings(None):
            self.assertRaises(ImproperlyConfigured, get_endpoint_function)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_correct_data(self, mock_get):
        """
        Tests the result if correct data is received.
        """
        mock_get.return_value.content = json.dumps(
            {
                "total": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "rows": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
                    }
                ]
            }
        )

        self.assertItemsEqual(
            {
                "count": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "results": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location), unicode(self.vertical_with_container.location)
                            ]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": "Nov 19, 2014 at 08:06 UTC",
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": "Nov 19, 2014 at 08:05 UTC",
                    },
                ]
            },
            helpers.get_notes(self.request, self.course)
        )

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_json_error(self, mock_get):
        """
        Tests the result if incorrect json is received.
        """
        mock_get.return_value.content = "Error"
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_empty_collection(self, mock_get):
        """
        Tests the result if an empty response is received.
        """
        mock_get.return_value.content = json.dumps({})
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_correct_data(self, mock_get):
        """
        Tests the result if correct data is received.
        """
        mock_get.return_value.content = json.dumps({
            "total": 2,
            "current_page": 1,
            "start": 0,
            "next": None,
            "previous": None,
            "num_pages": 1,
            "rows": [
                {
                    u"quote": u"quote text",
                    u"text": u"text",
                    u"usage_id": unicode(self.html_module_1.location),
                    u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
                },
                {
                    u"quote": u"quote text",
                    u"text": u"text",
                    u"usage_id": unicode(self.html_module_2.location),
                    u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
                }
            ]
        })

        self.assertItemsEqual(
            {
                "count": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "results": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": "Nov 19, 2014 at 08:06 UTC",
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": "Nov 19, 2014 at 08:05 UTC",
                    },
                ]
            },
            helpers.get_notes(self.request, self.course)
        )

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_json_error(self, mock_get):
        """
        Tests the result if incorrect json is received.
        """
        mock_get.return_value.content = "Error"
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_wrong_data_format(self, mock_get):
        """
        Tests the result if incorrect data structure is received.
        """
        mock_get.return_value.content = json.dumps({"1": 2})
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_empty_collection(self, mock_get):
        """
        Tests no results.
        """
        mock_get.return_value.content = json.dumps(NOTES_API_EMPTY_RESPONSE)
        self.assertItemsEqual(
            NOTES_VIEW_EMPTY_RESPONSE,
            helpers.get_notes(self.request, self.course)
        )

    def test_preprocess_collection_no_item(self):
        """
        Tests the result if appropriate module is not found.
        """
        initial_collection = [
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat()
            },
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.course.id.make_usage_key("html", "test_item")),
                u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat()
            },
        ]

        self.assertItemsEqual(
            [{
                u"quote": u"quote text",
                u"text": u"text",
                u"chapter": {
                    u"display_name": self.chapter.display_name_with_default_escaped,
                    u"index": 0,
                    u"location": unicode(self.chapter.location),
                    u"children": [unicode(self.sequential.location)]
                },
                u"section": {
                    u"display_name": self.sequential.display_name_with_default_escaped,
                    u"location": unicode(self.sequential.location),
                    u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)]
                },
                u"unit": {
                    u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                    u"display_name": self.vertical.display_name_with_default_escaped,
                    u"location": unicode(self.vertical.location),
                },
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
            }],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    def test_preprocess_collection_has_access(self):
        """
        Tests the result if the user does not have access to some of the modules.
        """
        initial_collection = [
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
            },
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_2.location),
                u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
            },
        ]
        self.html_module_2.visible_to_staff_only = True
        self.store.update_item(self.html_module_2, self.user.id)
        self.assertItemsEqual(
            [{
                u"quote": u"quote text",
                u"text": u"text",
                u"chapter": {
                    u"display_name": self.chapter.display_name_with_default_escaped,
                    u"index": 0,
                    u"location": unicode(self.chapter.location),
                    u"children": [unicode(self.sequential.location)]
                },
                u"section": {
                    u"display_name": self.sequential.display_name_with_default_escaped,
                    u"location": unicode(self.sequential.location),
                    u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)]
                },
                u"unit": {
                    u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                    u"display_name": self.vertical.display_name_with_default_escaped,
                    u"location": unicode(self.vertical.location),
                },
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
            }],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    @patch("edxnotes.helpers.has_access", autospec=True)
    @patch("edxnotes.helpers.modulestore", autospec=True)
    def test_preprocess_collection_no_unit(self, mock_modulestore, mock_has_access):
        """
        Tests the result if the unit does not exist.
        """
        store = MagicMock()
        store.get_item().get_parent.return_value = None
        mock_modulestore.return_value = store
        mock_has_access.return_value = True
        initial_collection = [{
            u"quote": u"quote text",
            u"text": u"text",
            u"usage_id": unicode(self.html_module_1.location),
            u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
        }]

        self.assertItemsEqual(
            [], helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    @override_settings(NOTES_DISABLED_TABS=['course_structure', 'tags'])
    def test_preprocess_collection_with_disabled_tabs(self, ):
        """
        Tests that preprocess collection returns correct data if `course_structure` and `tags` are disabled.
        """
        initial_collection = [
            {
                u"quote": u"quote text1",
                u"text": u"text1",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2016, 01, 26, 8, 5, 16, 00000).isoformat(),
            },
            {
                u"quote": u"quote text2",
                u"text": u"text2",
                u"usage_id": unicode(self.html_module_2.location),
                u"updated": datetime(2016, 01, 26, 9, 6, 17, 00000).isoformat(),
            },
        ]

        self.assertItemsEqual(
            [
                {

                    'section': {},
                    'chapter': {},
                    "unit": {
                        u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                        u"display_name": self.vertical.display_name_with_default_escaped,
                        u"location": unicode(self.vertical.location),
                    },
                    u'text': u'text1',
                    u'quote': u'quote text1',
                    u'usage_id': unicode(self.html_module_1.location),
                    u'updated': datetime(2016, 01, 26, 8, 5, 16)
                },
                {
                    'section': {},
                    'chapter': {},
                    "unit": {
                        u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                        u"display_name": self.vertical.display_name_with_default_escaped,
                        u"location": unicode(self.vertical.location),
                    },
                    u'text': u'text2',
                    u'quote': u'quote text2',
                    u'usage_id': unicode(self.html_module_2.location),
                    u'updated': datetime(2016, 01, 26, 9, 6, 17)
                }
            ],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    def test_get_parent_unit(self):
        """
        Tests `get_parent_unit` method for the successful result.
        """
        parent = helpers.get_parent_unit(self.html_module_1)
        self.assertEqual(parent.location, self.vertical.location)

        parent = helpers.get_parent_unit(self.child_html_module)
        self.assertEqual(parent.location, self.vertical_with_container.location)

        self.assertIsNone(helpers.get_parent_unit(None))
        self.assertIsNone(helpers.get_parent_unit(self.course))
        self.assertIsNone(helpers.get_parent_unit(self.chapter))
        self.assertIsNone(helpers.get_parent_unit(self.sequential))

    def test_get_module_context_sequential(self):
        """
        Tests `get_module_context` method for the sequential.
        """
        self.assertDictEqual(
            {
                u"display_name": self.sequential.display_name_with_default_escaped,
                u"location": unicode(self.sequential.location),
                u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)],
            },
            helpers.get_module_context(self.course, self.sequential)
        )

    def test_get_module_context_html_component(self):
        """
        Tests `get_module_context` method for the components.
        """
        self.assertDictEqual(
            {
                u"display_name": self.html_module_1.display_name_with_default_escaped,
                u"location": unicode(self.html_module_1.location),
            },
            helpers.get_module_context(self.course, self.html_module_1)
        )

    def test_get_module_context_chapter(self):
        """
        Tests `get_module_context` method for the chapters.
        """
        self.assertDictEqual(
            {
                u"display_name": self.chapter.display_name_with_default_escaped,
                u"index": 0,
                u"location": unicode(self.chapter.location),
                u"children": [unicode(self.sequential.location)],
            },
            helpers.get_module_context(self.course, self.chapter)
        )
        self.assertDictEqual(
            {
                u"display_name": self.chapter_2.display_name_with_default_escaped,
                u"index": 1,
                u"location": unicode(self.chapter_2.location),
                u"children": [],
            },
            helpers.get_module_context(self.course, self.chapter_2)
        )

    @override_settings(EDXNOTES_PUBLIC_API="http://example.com")
    @override_settings(EDXNOTES_INTERNAL_API="http://example.com")
    @patch("edxnotes.helpers.anonymous_id_for_user", autospec=True)
    @patch("edxnotes.helpers.get_edxnotes_id_token", autospec=True)
    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_send_request_with_text_param(self, mock_get, mock_get_id_token, mock_anonymous_id_for_user):
        """
        Tests that requests are send with correct information.
        """
        mock_get_id_token.return_value = "test_token"
        mock_anonymous_id_for_user.return_value = "anonymous_id"
        helpers.send_request(
            self.user,
            self.course.id,
            path="test",
            text="text",
            page=helpers.DEFAULT_PAGE,
            page_size=helpers.DEFAULT_PAGE_SIZE
        )
        mock_get.assert_called_with(
            "http://example.com/test/",
            headers={
                "x-annotator-auth-token": "test_token"
            },
            params={
                "user": "******",
                "course_id": unicode(self.course.id),
                "text": "text",
                "highlight": True,
                'page': 1,
                'page_size': 25,
            },
            timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
        )

    @override_settings(EDXNOTES_PUBLIC_API="http://example.com")
    @override_settings(EDXNOTES_INTERNAL_API="http://example.com")
    @patch("edxnotes.helpers.anonymous_id_for_user", autospec=True)
    @patch("edxnotes.helpers.get_edxnotes_id_token", autospec=True)
    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_send_request_without_text_param(self, mock_get, mock_get_id_token, mock_anonymous_id_for_user):
        """
        Tests that requests are send with correct information.
        """
        mock_get_id_token.return_value = "test_token"
        mock_anonymous_id_for_user.return_value = "anonymous_id"
        helpers.send_request(
            self.user, self.course.id, path="test", page=1, page_size=25
        )
        mock_get.assert_called_with(
            "http://example.com/test/",
            headers={
                "x-annotator-auth-token": "test_token"
            },
            params={
                "user": "******",
                "course_id": unicode(self.course.id),
                'page': helpers.DEFAULT_PAGE,
                'page_size': helpers.DEFAULT_PAGE_SIZE,
            },
            timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
        )

    def test_get_course_position_no_chapter(self):
        """
        Returns `None` if no chapter found.
        """
        mock_course_module = MagicMock()
        mock_course_module.position = 3
        mock_course_module.get_display_items.return_value = []
        self.assertIsNone(helpers.get_course_position(mock_course_module))

    def test_get_course_position_to_chapter(self):
        """
        Returns a position that leads to COURSE/CHAPTER if this isn't the users's
        first time.
        """
        mock_course_module = MagicMock(id=self.course.id, position=3)

        mock_chapter = MagicMock()
        mock_chapter.url_name = 'chapter_url_name'
        mock_chapter.display_name_with_default_escaped = 'Test Chapter Display Name'

        mock_course_module.get_display_items.return_value = [mock_chapter]

        self.assertEqual(helpers.get_course_position(mock_course_module), {
            'display_name': 'Test Chapter Display Name',
            'url': '/courses/{}/courseware/chapter_url_name/'.format(self.course.id),
        })

    def test_get_course_position_no_section(self):
        """
        Returns `None` if no section found.
        """
        mock_course_module = MagicMock(id=self.course.id, position=None)
        mock_course_module.get_display_items.return_value = [MagicMock()]
        self.assertIsNone(helpers.get_course_position(mock_course_module))

    def test_get_course_position_to_section(self):
        """
        Returns a position that leads to COURSE/CHAPTER/SECTION if this is the
        user's first time.
        """
        mock_course_module = MagicMock(id=self.course.id, position=None)

        mock_chapter = MagicMock()
        mock_chapter.url_name = 'chapter_url_name'
        mock_course_module.get_display_items.return_value = [mock_chapter]

        mock_section = MagicMock()
        mock_section.url_name = 'section_url_name'
        mock_section.display_name_with_default_escaped = 'Test Section Display Name'

        mock_chapter.get_display_items.return_value = [mock_section]
        mock_section.get_display_items.return_value = [MagicMock()]

        self.assertEqual(helpers.get_course_position(mock_course_module), {
            'display_name': 'Test Section Display Name',
            'url': '/courses/{}/courseware/chapter_url_name/section_url_name/'.format(self.course.id),
        })

    def test_get_index(self):
        """
        Tests `get_index` method returns unit url.
        """
        children = self.sequential.children
        self.assertEqual(0, helpers.get_index(unicode(self.vertical.location), children))
        self.assertEqual(1, helpers.get_index(unicode(self.vertical_with_container.location), children))

    @ddt.unpack
    @ddt.data(
        {'previous_api_url': None, 'next_api_url': None},
        {'previous_api_url': None, 'next_api_url': 'edxnotes/?course_id=abc&page=2&page_size=10&user=123'},
        {'previous_api_url': 'edxnotes.org/?course_id=abc&page=2&page_size=10&user=123', 'next_api_url': None},
        {
            'previous_api_url': 'edxnotes.org/?course_id=abc&page_size=10&user=123',
            'next_api_url': 'edxnotes.org/?course_id=abc&page=3&page_size=10&user=123'
        },
        {
            'previous_api_url': 'edxnotes.org/?course_id=abc&page=2&page_size=10&text=wow&user=123',
            'next_api_url': 'edxnotes.org/?course_id=abc&page=4&page_size=10&text=wow&user=123'
        },
    )
    def test_construct_url(self, previous_api_url, next_api_url):
        """
        Verify that `construct_url` works correctly.
        """
        # make absolute url
        # pylint: disable=no-member
        if self.request.is_secure():
            host = 'https://' + self.request.get_host()
        else:
            host = 'http://' + self.request.get_host()
        notes_url = host + reverse("notes", args=[unicode(self.course.id)])

        def verify_url(constructed, expected):
            """
            Verify that constructed url is correct.
            """
            # if api url is None then constructed url should also be None
            if expected is None:
                self.assertEqual(expected, constructed)
            else:
                # constructed url should startswith notes view url instead of api view url
                self.assertTrue(constructed.startswith(notes_url))

                # constructed url should not contain extra params
                self.assertNotIn('user', constructed)

                # constructed url should only has these params if present in api url
                allowed_params = ('page', 'page_size', 'text')

                # extract query params from constructed url
                parsed = urlparse.urlparse(constructed)
                params = urlparse.parse_qs(parsed.query)

                # verify that constructed url has only correct params and params have correct values
                for param, value in params.items():
                    self.assertIn(param, allowed_params)
                    self.assertIn('{}={}'.format(param, value[0]), expected)

        next_url, previous_url = helpers.construct_pagination_urls(
            self.request,
            self.course.id,
            next_api_url, previous_api_url
        )
        verify_url(next_url, next_api_url)
        verify_url(previous_url, previous_api_url)
class EdxNotesHelpersTest(ModuleStoreTestCase):
    """
    Tests for EdxNotes helpers.
    """
    def setUp(self):
        """
        Setup a dummy course content.
        """
        super(EdxNotesHelpersTest, self).setUp()

        # There are many tests that are comparing locators as returned from helper methods. When using
        # the split modulestore, some of those locators have version and branch information, but the
        # comparison values do not. This needs further investigation in order to enable these tests
        # with the split modulestore.
        with self.store.default_store(ModuleStoreEnum.Type.mongo):
            ClientFactory(name="edx-notes")
            self.course = CourseFactory.create()
            self.chapter = ItemFactory.create(category="chapter", parent_location=self.course.location)
            self.chapter_2 = ItemFactory.create(category="chapter", parent_location=self.course.location)
            self.sequential = ItemFactory.create(category="sequential", parent_location=self.chapter.location)
            self.vertical = ItemFactory.create(category="vertical", parent_location=self.sequential.location)
            self.html_module_1 = ItemFactory.create(category="html", parent_location=self.vertical.location)
            self.html_module_2 = ItemFactory.create(category="html", parent_location=self.vertical.location)
            self.vertical_with_container = ItemFactory.create(
                category='vertical', parent_location=self.sequential.location
            )
            self.child_container = ItemFactory.create(
                category='split_test', parent_location=self.vertical_with_container.location)
            self.child_vertical = ItemFactory.create(category='vertical', parent_location=self.child_container.location)
            self.child_html_module = ItemFactory.create(category="html", parent_location=self.child_vertical.location)

            # Read again so that children lists are accurate
            self.course = self.store.get_item(self.course.location)
            self.chapter = self.store.get_item(self.chapter.location)
            self.chapter_2 = self.store.get_item(self.chapter_2.location)
            self.sequential = self.store.get_item(self.sequential.location)
            self.vertical = self.store.get_item(self.vertical.location)

            self.vertical_with_container = self.store.get_item(self.vertical_with_container.location)
            self.child_container = self.store.get_item(self.child_container.location)
            self.child_vertical = self.store.get_item(self.child_vertical.location)
            self.child_html_module = self.store.get_item(self.child_html_module.location)

            self.user = UserFactory()
            self.client.login(username=self.user.username, password=UserFactory._DEFAULT_PASSWORD)

        self.request = RequestFactory().request()
        self.request.user = self.user

    def _get_unit_url(self, course, chapter, section, position=1):
        """
        Returns `jump_to_id` url for the `vertical`.
        """
        return reverse('courseware_position', kwargs={
            'course_id': course.id,
            'chapter': chapter.url_name,
            'section': section.url_name,
            'position': position,
        })

    def test_edxnotes_harvard_notes_enabled(self):
        """
        Tests that edxnotes are disabled when Harvard Annotation Tool is enabled.
        """
        self.course.advanced_modules = ['imageannotation', 'textannotation', 'videoannotation']
        assert not helpers.is_feature_enabled(self.course, self.user)

    @ddt.data(True, False)
    def test_is_feature_enabled(self, enabled):
        """
        Tests that is_feature_enabled shows correct behavior.
        """
        course = CourseFactory(edxnotes=enabled)
        enrollment = CourseEnrollmentFactory(course_id=course.id)
        assert helpers.is_feature_enabled(course, enrollment.user) == enabled

    @ddt.data(
        helpers.get_public_endpoint,
        helpers.get_internal_endpoint,
    )
    def test_get_endpoints(self, get_endpoint_function):
        """
        Test that the get_public_endpoint and get_internal_endpoint functions
        return appropriate values.
        """
        @contextmanager
        def patch_edxnotes_api_settings(url):
            """
            Convenience function for patching both EDXNOTES_PUBLIC_API and
            EDXNOTES_INTERNAL_API.
            """
            with override_settings(EDXNOTES_PUBLIC_API=url):
                with override_settings(EDXNOTES_INTERNAL_API=url):
                    yield

        # url ends with "/"
        with patch_edxnotes_api_settings("http://example.com/"):
            self.assertEqual("http://example.com/", get_endpoint_function())

        # url doesn't have "/" at the end
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/", get_endpoint_function())

        # url with path that starts with "/"
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/some_path/", get_endpoint_function("/some_path"))

        # url with path without "/"
        with patch_edxnotes_api_settings("http://example.com"):
            self.assertEqual("http://example.com/some_path/", get_endpoint_function("some_path/"))

        # url is not configured
        with patch_edxnotes_api_settings(None):
            self.assertRaises(ImproperlyConfigured, get_endpoint_function)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_correct_data(self, mock_get):
        """
        Tests the result if correct data is received.
        """
        mock_get.return_value.content = json.dumps(
            {
                "total": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "rows": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
                    }
                ]
            }
        )

        self.assertItemsEqual(
            {
                "count": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "results": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location), unicode(self.vertical_with_container.location)
                            ]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": "Nov 19, 2014 at 08:06 UTC",
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": "Nov 19, 2014 at 08:05 UTC",
                    },
                ]
            },
            helpers.get_notes(self.request, self.course)
        )

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_json_error(self, mock_get):
        """
        Tests the result if incorrect json is received.
        """
        mock_get.return_value.content = "Error"
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_get_notes_empty_collection(self, mock_get):
        """
        Tests the result if an empty response is received.
        """
        mock_get.return_value.content = json.dumps({})
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_correct_data(self, mock_get):
        """
        Tests the result if correct data is received.
        """
        mock_get.return_value.content = json.dumps({
            "total": 2,
            "current_page": 1,
            "start": 0,
            "next": None,
            "previous": None,
            "num_pages": 1,
            "rows": [
                {
                    u"quote": u"quote text",
                    u"text": u"text",
                    u"usage_id": unicode(self.html_module_1.location),
                    u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
                },
                {
                    u"quote": u"quote text",
                    u"text": u"text",
                    u"usage_id": unicode(self.html_module_2.location),
                    u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
                }
            ]
        })

        self.assertItemsEqual(
            {
                "count": 2,
                "current_page": 1,
                "start": 0,
                "next": None,
                "previous": None,
                "num_pages": 1,
                "results": [
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_2.location),
                        u"updated": "Nov 19, 2014 at 08:06 UTC",
                    },
                    {
                        u"quote": u"quote text",
                        u"text": u"text",
                        u"chapter": {
                            u"display_name": self.chapter.display_name_with_default_escaped,
                            u"index": 0,
                            u"location": unicode(self.chapter.location),
                            u"children": [unicode(self.sequential.location)]
                        },
                        u"section": {
                            u"display_name": self.sequential.display_name_with_default_escaped,
                            u"location": unicode(self.sequential.location),
                            u"children": [
                                unicode(self.vertical.location),
                                unicode(self.vertical_with_container.location)]
                        },
                        u"unit": {
                            u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                            u"display_name": self.vertical.display_name_with_default_escaped,
                            u"location": unicode(self.vertical.location),
                        },
                        u"usage_id": unicode(self.html_module_1.location),
                        u"updated": "Nov 19, 2014 at 08:05 UTC",
                    },
                ]
            },
            helpers.get_notes(self.request, self.course)
        )

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_json_error(self, mock_get):
        """
        Tests the result if incorrect json is received.
        """
        mock_get.return_value.content = "Error"
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_wrong_data_format(self, mock_get):
        """
        Tests the result if incorrect data structure is received.
        """
        mock_get.return_value.content = json.dumps({"1": 2})
        self.assertRaises(EdxNotesParseError, helpers.get_notes, self.request, self.course)

    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_search_empty_collection(self, mock_get):
        """
        Tests no results.
        """
        mock_get.return_value.content = json.dumps(NOTES_API_EMPTY_RESPONSE)
        self.assertItemsEqual(
            NOTES_VIEW_EMPTY_RESPONSE,
            helpers.get_notes(self.request, self.course)
        )

    @override_settings(EDXNOTES_PUBLIC_API="http://example.com")
    @override_settings(EDXNOTES_INTERNAL_API="http://example.com")
    @patch("edxnotes.helpers.requests.delete")
    def test_delete_all_notes_for_user(self, mock_delete):
        """
        Test GDPR data deletion for Notes user_id
        """
        with mock.patch('edxnotes.helpers.get_edxnotes_id_token', return_value="test_token"):
            helpers.delete_all_notes_for_user(user=self.user, user_id="anonymous_id")
            mock_delete.assert_called_with(
                url='http://example.com/',
                headers={
                    'x-annotator-auth-token': 'test_token'
                },
                data={
                    'user_id': 'anonymous_id'
                },
                timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
            )

    def test_preprocess_collection_no_item(self):
        """
        Tests the result if appropriate module is not found.
        """
        initial_collection = [
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat()
            },
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.course.id.make_usage_key("html", "test_item")),
                u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat()
            },
        ]

        self.assertItemsEqual(
            [{
                u"quote": u"quote text",
                u"text": u"text",
                u"chapter": {
                    u"display_name": self.chapter.display_name_with_default_escaped,
                    u"index": 0,
                    u"location": unicode(self.chapter.location),
                    u"children": [unicode(self.sequential.location)]
                },
                u"section": {
                    u"display_name": self.sequential.display_name_with_default_escaped,
                    u"location": unicode(self.sequential.location),
                    u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)]
                },
                u"unit": {
                    u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                    u"display_name": self.vertical.display_name_with_default_escaped,
                    u"location": unicode(self.vertical.location),
                },
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
            }],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    def test_preprocess_collection_has_access(self):
        """
        Tests the result if the user does not have access to some of the modules.
        """
        initial_collection = [
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
            },
            {
                u"quote": u"quote text",
                u"text": u"text",
                u"usage_id": unicode(self.html_module_2.location),
                u"updated": datetime(2014, 11, 19, 8, 6, 16, 00000).isoformat(),
            },
        ]
        self.html_module_2.visible_to_staff_only = True
        self.store.update_item(self.html_module_2, self.user.id)
        self.assertItemsEqual(
            [{
                u"quote": u"quote text",
                u"text": u"text",
                u"chapter": {
                    u"display_name": self.chapter.display_name_with_default_escaped,
                    u"index": 0,
                    u"location": unicode(self.chapter.location),
                    u"children": [unicode(self.sequential.location)]
                },
                u"section": {
                    u"display_name": self.sequential.display_name_with_default_escaped,
                    u"location": unicode(self.sequential.location),
                    u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)]
                },
                u"unit": {
                    u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                    u"display_name": self.vertical.display_name_with_default_escaped,
                    u"location": unicode(self.vertical.location),
                },
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000),
            }],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    @patch("edxnotes.helpers.has_access", autospec=True)
    @patch("edxnotes.helpers.modulestore", autospec=True)
    def test_preprocess_collection_no_unit(self, mock_modulestore, mock_has_access):
        """
        Tests the result if the unit does not exist.
        """
        store = MagicMock()
        store.get_item().get_parent.return_value = None
        mock_modulestore.return_value = store
        mock_has_access.return_value = True
        initial_collection = [{
            u"quote": u"quote text",
            u"text": u"text",
            u"usage_id": unicode(self.html_module_1.location),
            u"updated": datetime(2014, 11, 19, 8, 5, 16, 00000).isoformat(),
        }]

        self.assertItemsEqual(
            [], helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    @override_settings(NOTES_DISABLED_TABS=['course_structure', 'tags'])
    def test_preprocess_collection_with_disabled_tabs(self, ):
        """
        Tests that preprocess collection returns correct data if `course_structure` and `tags` are disabled.
        """
        initial_collection = [
            {
                u"quote": u"quote text1",
                u"text": u"text1",
                u"usage_id": unicode(self.html_module_1.location),
                u"updated": datetime(2016, 01, 26, 8, 5, 16, 00000).isoformat(),
            },
            {
                u"quote": u"quote text2",
                u"text": u"text2",
                u"usage_id": unicode(self.html_module_2.location),
                u"updated": datetime(2016, 01, 26, 9, 6, 17, 00000).isoformat(),
            },
        ]

        self.assertItemsEqual(
            [
                {

                    'section': {},
                    'chapter': {},
                    "unit": {
                        u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                        u"display_name": self.vertical.display_name_with_default_escaped,
                        u"location": unicode(self.vertical.location),
                    },
                    u'text': u'text1',
                    u'quote': u'quote text1',
                    u'usage_id': unicode(self.html_module_1.location),
                    u'updated': datetime(2016, 01, 26, 8, 5, 16)
                },
                {
                    'section': {},
                    'chapter': {},
                    "unit": {
                        u"url": self._get_unit_url(self.course, self.chapter, self.sequential),
                        u"display_name": self.vertical.display_name_with_default_escaped,
                        u"location": unicode(self.vertical.location),
                    },
                    u'text': u'text2',
                    u'quote': u'quote text2',
                    u'usage_id': unicode(self.html_module_2.location),
                    u'updated': datetime(2016, 01, 26, 9, 6, 17)
                }
            ],
            helpers.preprocess_collection(self.user, self.course, initial_collection)
        )

    def test_get_module_context_sequential(self):
        """
        Tests `get_module_context` method for the sequential.
        """
        self.assertDictEqual(
            {
                u"display_name": self.sequential.display_name_with_default_escaped,
                u"location": unicode(self.sequential.location),
                u"children": [unicode(self.vertical.location), unicode(self.vertical_with_container.location)],
            },
            helpers.get_module_context(self.course, self.sequential)
        )

    def test_get_module_context_html_component(self):
        """
        Tests `get_module_context` method for the components.
        """
        self.assertDictEqual(
            {
                u"display_name": self.html_module_1.display_name_with_default_escaped,
                u"location": unicode(self.html_module_1.location),
            },
            helpers.get_module_context(self.course, self.html_module_1)
        )

    def test_get_module_context_chapter(self):
        """
        Tests `get_module_context` method for the chapters.
        """
        self.assertDictEqual(
            {
                u"display_name": self.chapter.display_name_with_default_escaped,
                u"index": 0,
                u"location": unicode(self.chapter.location),
                u"children": [unicode(self.sequential.location)],
            },
            helpers.get_module_context(self.course, self.chapter)
        )
        self.assertDictEqual(
            {
                u"display_name": self.chapter_2.display_name_with_default_escaped,
                u"index": 1,
                u"location": unicode(self.chapter_2.location),
                u"children": [],
            },
            helpers.get_module_context(self.course, self.chapter_2)
        )

    @override_settings(EDXNOTES_PUBLIC_API="http://example.com")
    @override_settings(EDXNOTES_INTERNAL_API="http://example.com")
    @patch("edxnotes.helpers.anonymous_id_for_user", autospec=True)
    @patch("edxnotes.helpers.get_edxnotes_id_token", autospec=True)
    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_send_request_with_text_param(self, mock_get, mock_get_id_token, mock_anonymous_id_for_user):
        """
        Tests that requests are send with correct information.
        """
        mock_get_id_token.return_value = "test_token"
        mock_anonymous_id_for_user.return_value = "anonymous_id"
        helpers.send_request(
            self.user,
            self.course.id,
            path="test",
            text="text",
            page=helpers.DEFAULT_PAGE,
            page_size=helpers.DEFAULT_PAGE_SIZE
        )
        mock_get.assert_called_with(
            "http://example.com/test/",
            headers={
                "x-annotator-auth-token": "test_token"
            },
            params={
                "user": "******",
                "course_id": unicode(self.course.id),
                "text": "text",
                "highlight": True,
                'page': 1,
                'page_size': 25,
            },
            timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
        )

    @override_settings(EDXNOTES_PUBLIC_API="http://example.com")
    @override_settings(EDXNOTES_INTERNAL_API="http://example.com")
    @patch("edxnotes.helpers.anonymous_id_for_user", autospec=True)
    @patch("edxnotes.helpers.get_edxnotes_id_token", autospec=True)
    @patch("edxnotes.helpers.requests.get", autospec=True)
    def test_send_request_without_text_param(self, mock_get, mock_get_id_token, mock_anonymous_id_for_user):
        """
        Tests that requests are send with correct information.
        """
        mock_get_id_token.return_value = "test_token"
        mock_anonymous_id_for_user.return_value = "anonymous_id"
        helpers.send_request(
            self.user, self.course.id, path="test", page=1, page_size=25
        )
        mock_get.assert_called_with(
            "http://example.com/test/",
            headers={
                "x-annotator-auth-token": "test_token"
            },
            params={
                "user": "******",
                "course_id": unicode(self.course.id),
                'page': helpers.DEFAULT_PAGE,
                'page_size': helpers.DEFAULT_PAGE_SIZE,
            },
            timeout=(settings.EDXNOTES_CONNECT_TIMEOUT, settings.EDXNOTES_READ_TIMEOUT)
        )

    def test_get_course_position_no_chapter(self):
        """
        Returns `None` if no chapter found.
        """
        mock_course_module = MagicMock()
        mock_course_module.position = 3
        mock_course_module.get_display_items.return_value = []
        self.assertIsNone(helpers.get_course_position(mock_course_module))

    def test_get_course_position_to_chapter(self):
        """
        Returns a position that leads to COURSE/CHAPTER if this isn't the users's
        first time.
        """
        mock_course_module = MagicMock(id=self.course.id, position=3)

        mock_chapter = MagicMock()
        mock_chapter.url_name = 'chapter_url_name'
        mock_chapter.display_name_with_default_escaped = 'Test Chapter Display Name'

        mock_course_module.get_display_items.return_value = [mock_chapter]

        self.assertEqual(helpers.get_course_position(mock_course_module), {
            'display_name': 'Test Chapter Display Name',
            'url': '/courses/{}/courseware/chapter_url_name/'.format(self.course.id),
        })

    def test_get_course_position_no_section(self):
        """
        Returns `None` if no section found.
        """
        mock_course_module = MagicMock(id=self.course.id, position=None)
        mock_course_module.get_display_items.return_value = [MagicMock()]
        self.assertIsNone(helpers.get_course_position(mock_course_module))

    def test_get_course_position_to_section(self):
        """
        Returns a position that leads to COURSE/CHAPTER/SECTION if this is the
        user's first time.
        """
        mock_course_module = MagicMock(id=self.course.id, position=None)

        mock_chapter = MagicMock()
        mock_chapter.url_name = 'chapter_url_name'
        mock_course_module.get_display_items.return_value = [mock_chapter]

        mock_section = MagicMock()
        mock_section.url_name = 'section_url_name'
        mock_section.display_name_with_default_escaped = 'Test Section Display Name'

        mock_chapter.get_display_items.return_value = [mock_section]
        mock_section.get_display_items.return_value = [MagicMock()]

        self.assertEqual(helpers.get_course_position(mock_course_module), {
            'display_name': 'Test Section Display Name',
            'url': '/courses/{}/courseware/chapter_url_name/section_url_name/'.format(self.course.id),
        })

    def test_get_index(self):
        """
        Tests `get_index` method returns unit url.
        """
        children = self.sequential.children
        self.assertEqual(0, helpers.get_index(unicode(self.vertical.location), children))
        self.assertEqual(1, helpers.get_index(unicode(self.vertical_with_container.location), children))

    @ddt.unpack
    @ddt.data(
        {'previous_api_url': None, 'next_api_url': None},
        {'previous_api_url': None, 'next_api_url': 'edxnotes/?course_id=abc&page=2&page_size=10&user=123'},
        {'previous_api_url': 'edxnotes.org/?course_id=abc&page=2&page_size=10&user=123', 'next_api_url': None},
        {
            'previous_api_url': 'edxnotes.org/?course_id=abc&page_size=10&user=123',
            'next_api_url': 'edxnotes.org/?course_id=abc&page=3&page_size=10&user=123'
        },
        {
            'previous_api_url': 'edxnotes.org/?course_id=abc&page=2&page_size=10&text=wow&user=123',
            'next_api_url': 'edxnotes.org/?course_id=abc&page=4&page_size=10&text=wow&user=123'
        },
    )
    def test_construct_url(self, previous_api_url, next_api_url):
        """
        Verify that `construct_url` works correctly.
        """
        # make absolute url
        # pylint: disable=no-member
        if self.request.is_secure():
            host = 'https://' + self.request.get_host()
        else:
            host = 'http://' + self.request.get_host()
        notes_url = host + reverse("notes", args=[unicode(self.course.id)])

        def verify_url(constructed, expected):
            """
            Verify that constructed url is correct.
            """
            # if api url is None then constructed url should also be None
            if expected is None:
                self.assertEqual(expected, constructed)
            else:
                # constructed url should startswith notes view url instead of api view url
                self.assertTrue(constructed.startswith(notes_url))

                # constructed url should not contain extra params
                self.assertNotIn('user', constructed)

                # constructed url should only has these params if present in api url
                allowed_params = ('page', 'page_size', 'text')

                # extract query params from constructed url
                parsed = urlparse.urlparse(constructed)
                params = urlparse.parse_qs(parsed.query)

                # verify that constructed url has only correct params and params have correct values
                for param, value in params.items():
                    self.assertIn(param, allowed_params)
                    self.assertIn('{}={}'.format(param, value[0]), expected)

        next_url, previous_url = helpers.construct_pagination_urls(
            self.request,
            self.course.id,
            next_api_url, previous_api_url
        )
        verify_url(next_url, next_api_url)
        verify_url(previous_url, previous_api_url)
Beispiel #13
0
class TestUserPreferenceMiddleware(CacheIsolationTestCase):
    """
    Tests to make sure user preferences are getting properly set in the middleware.
    """
    def setUp(self):
        super(TestUserPreferenceMiddleware, self).setUp()
        self.middleware = LanguagePreferenceMiddleware()
        self.session_middleware = SessionMiddleware()
        self.user = UserFactory.create()
        self.anonymous_user = AnonymousUserFactory()
        self.request = RequestFactory().get('/somewhere')
        self.request.user = self.user
        self.request.META['HTTP_ACCEPT_LANGUAGE'] = 'ar;q=1.0'
        self.session_middleware.process_request(self.request)

    def test_logout_shouldnt_remove_cookie(self):

        self.middleware.process_request(self.request)

        self.request.user = self.anonymous_user

        response = mock.Mock(spec=HttpResponse)
        self.middleware.process_response(self.request, response)

        response.delete_cookie.assert_not_called()

    @ddt.data(None, 'es', 'en')
    def test_preference_setting_changes_cookie(self, lang_pref_out):
        """
        Test that the LANGUAGE_COOKIE is always set to the user's current language preferences
        at the end of the request, with an expiry that's the same as the users current session cookie.
        """
        if lang_pref_out:
            set_user_preference(self.user, LANGUAGE_KEY, lang_pref_out)
        else:
            delete_user_preference(self.user, LANGUAGE_KEY)

        response = mock.Mock(spec=HttpResponse)
        self.middleware.process_response(self.request, response)

        if lang_pref_out:
            response.set_cookie.assert_called_with(
                settings.LANGUAGE_COOKIE,
                value=lang_pref_out,
                domain=settings.SESSION_COOKIE_DOMAIN,
                max_age=COOKIE_DURATION,
                secure=self.request.is_secure(),
            )
        else:
            response.delete_cookie.assert_called_with(
                settings.LANGUAGE_COOKIE,
                domain=settings.SESSION_COOKIE_DOMAIN,
            )

        self.assertNotIn(LANGUAGE_SESSION_KEY, self.request.session)

    @ddt.data(*itertools.product(
        (None, 'eo', 'es'),  # LANGUAGE_COOKIE
        (None, 'es', 'en'),  # Language Preference In
    ))
    @ddt.unpack
    @mock.patch(
        'openedx.core.djangoapps.lang_pref.middleware.set_user_preference')
    def test_preference_cookie_changes_setting(self, lang_cookie, lang_pref_in,
                                               mock_set_user_preference):
        self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie

        if lang_pref_in:
            set_user_preference(self.user, LANGUAGE_KEY, lang_pref_in)
        else:
            delete_user_preference(self.user, LANGUAGE_KEY)

        self.middleware.process_request(self.request)

        if lang_cookie is None:
            self.assertEqual(mock_set_user_preference.mock_calls, [])
        else:
            mock_set_user_preference.assert_called_with(
                self.user, LANGUAGE_KEY, lang_cookie)

    @ddt.data(*(
        (logged_in, ) + test_def for logged_in in (True, False)
        for test_def in [
            # (LANGUAGE_COOKIE, LANGUAGE_SESSION_KEY, Accept-Language In,
            #  Accept-Language Out, Session Lang Out)
            (None, None, None, None, None),
            (None, 'eo', None, None, 'eo'),
            (None, 'en', None, None, 'en'),
            (None, 'eo', 'en', 'en', 'eo'),
            (None, None, 'en', 'en', None),
            ('en', None, None, 'en', None),
            ('en', 'en', None, 'en', 'en'),
            ('en', None, 'eo', 'en;q=1.0,eo', None),
            ('en', None, 'en', 'en', None),
            ('en', 'eo', 'en', 'en', None),
            ('en', 'eo', 'eo', 'en;q=1.0,eo', None)
        ]))
    @ddt.unpack
    def test_preference_cookie_overrides_browser(
        self,
        logged_in,
        lang_cookie,
        lang_session_in,
        accept_lang_in,
        accept_lang_out,
        lang_session_out,
    ):
        if not logged_in:
            self.request.user = self.anonymous_user
        if lang_cookie:
            self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie
        if lang_session_in:
            self.request.session[LANGUAGE_SESSION_KEY] = lang_session_in
        if accept_lang_in:
            self.request.META['HTTP_ACCEPT_LANGUAGE'] = accept_lang_in
        else:
            del self.request.META['HTTP_ACCEPT_LANGUAGE']

        self.middleware.process_request(self.request)

        accept_lang_result = self.request.META.get('HTTP_ACCEPT_LANGUAGE')
        if accept_lang_result:
            accept_lang_result = parse_accept_lang_header(accept_lang_result)

        if accept_lang_out:
            accept_lang_out = parse_accept_lang_header(accept_lang_out)

        if accept_lang_out and accept_lang_result:
            self.assertItemsEqual(accept_lang_result, accept_lang_out)
        else:
            self.assertEqual(accept_lang_result, accept_lang_out)

        self.assertEquals(self.request.session.get(LANGUAGE_SESSION_KEY),
                          lang_session_out)

    @ddt.data(None, 'es', 'en')
    def test_logout_preserves_cookie(self, lang_cookie):
        if lang_cookie:
            self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie
        elif settings.LANGUAGE_COOKIE in self.client.cookies:
            del self.client.cookies[settings.LANGUAGE_COOKIE]
        # Use an actual call to the logout endpoint, because the logout function
        # explicitly clears all cookies
        self.client.get(reverse('logout'))
        if lang_cookie:
            self.assertEqual(
                self.client.cookies[settings.LANGUAGE_COOKIE].value,
                lang_cookie)
        else:
            self.assertNotIn(settings.LANGUAGE_COOKIE, self.client.cookies)

    @ddt.data((None, None), ('es', 'es-419'), ('en', 'en'),
              ('es-419', 'es-419'))
    @ddt.unpack
    def test_login_captures_lang_pref(self, lang_cookie, expected_lang):
        if lang_cookie:
            self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie
        elif settings.LANGUAGE_COOKIE in self.client.cookies:
            del self.client.cookies[settings.LANGUAGE_COOKIE]

        # Use an actual call to the login endpoint, to validate that the middleware
        # stack does the right thing
        if settings.FEATURES.get('ENABLE_COMBINED_LOGIN_REGISTRATION'):
            response = self.client.post(reverse('user_api_login_session'),
                                        data={
                                            'email': self.user.email,
                                            'password':
                                            UserFactory._DEFAULT_PASSWORD,
                                            'remember': True,
                                        })
        else:
            response = self.client.post(reverse('login_post'),
                                        data={
                                            'email': self.user.email,
                                            'password':
                                            UserFactory._DEFAULT_PASSWORD,
                                            'honor_code': True,
                                        })

        self.assertEqual(response.status_code, 200)

        if lang_cookie:
            self.assertEqual(response['Content-Language'], expected_lang)
            self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY),
                             lang_cookie)
            self.assertEqual(
                self.client.cookies[settings.LANGUAGE_COOKIE].value,
                lang_cookie)
        else:
            self.assertEqual(response['Content-Language'], 'en')
            self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY),
                             None)
            self.assertEqual(
                self.client.cookies[settings.LANGUAGE_COOKIE].value, '')

    def test_process_response_no_user_noop(self):
        del self.request.user
        response = mock.Mock(spec=HttpResponse)

        result = self.middleware.process_response(self.request, response)

        self.assertIs(result, response)
        self.assertEqual(response.mock_calls, [])

    def test_preference_update_noop(self):
        self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'es'

        # No preference yet, should write to the database

        self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY), None)
        self.middleware.process_request(self.request)
        self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY), 'es')

        response = mock.Mock(spec=HttpResponse)

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)

        # Preference is the same as the cookie, shouldn't write to the database

        with self.assertNumQueries(3):
            self.middleware.process_request(self.request)

        self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY), 'es')

        response = mock.Mock(spec=HttpResponse)

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)

        # Cookie changed, should write to the database again

        self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'en'
        self.middleware.process_request(self.request)
        self.assertEqual(get_user_preference(self.user, LANGUAGE_KEY), 'en')

        with self.assertNumQueries(1):
            self.middleware.process_response(self.request, response)