Repository: zestedesavoir/zds-site

Issue:
Corriger et refactoriser remove_url_scheme et ses tests
On a une fonction utilitaire qui s'appelle [remove_url_scheme](https://github.com/zestedesavoir/zds-site/blob/03c8f316c46e51d42afb8b8d2d9553cdd8fb0f08/zds/utils/templatetags/remove_url_scheme.py#L9), qui permet d'enlever le schéma des urls (http ou https) et le nom du domaine, afin de toujours servir les ressources locales au site avec le bon protocole (http ou https).

**Description du problème**

Le problème actuellement, c'est qu'elle gère mal le nom du domaine spécifié dans l'environnement de dev, à savoir `ZDS_APP['site']['dns'] = 127.0.0.1:8000`, ce qui a pour effet de faire rater un des tests (zds.utils.tests.test_misc.Misc.test_remove_url_scheme), mais en **local seulement**, pas sur Travis. Il s'agit donc d'un faux positif sur l'environnement de dev, ce qui est pénible.

**Comportement attendu**

On devrait avoir le bon fonctionnement sur l'environnement de dev en gérant correctement le numéro de port à la fin de l'url. Au passage, on devrait aussi :

* utiliser `urllib.parse` au lieu du module de `six` ;
* réunir tous les tests de `remove_url_scheme` dans le bon fichier (actuellement c'est réparti dans `test_misc` et un fichier `test_remove_url_scheme`).



Discussion:

Resolving patch:
diff --git a/zds/utils/templatetags/remove_url_scheme.py b/zds/utils/templatetags/remove_url_scheme.py
--- a/zds/utils/templatetags/remove_url_scheme.py
+++ b/zds/utils/templatetags/remove_url_scheme.py
@@ -1,27 +1,37 @@
+import urllib.parse
+
 from django import template
 from django.conf import settings
-from six.moves import urllib_parse as urlparse
 
 register = template.Library()
 
 
 @register.filter('remove_url_scheme')
-def remove_url_scheme(input_url):
+def remove_url_scheme(url):
     """
-    make every image url pointing to this website protocol independant so that if we use https, we are sure
-    that all our media are served with this protocol.
-
-    .. notice::
+    Remove the scheme and hostname from a URL if it is internal, but leave it unchanged otherwise.
 
-        this also removes the ``settings.ZDS_APP['site']['dns']`` from the url.
+    The internal hostname is determined using the value of ``ZDS_APP['site']['dns']``.
+    URLs with no scheme are accepted. URLs with no hostname are treated as internal.
 
-    :return: the url without its scheme, e.g. ``http://zestedesavoir.com/media/gallery/1/1.png`` becomes
-    ``/media/gallery/1/1.png``
+    For example, ``http://zestedesavoir.com/media/gallery/1/1.png`` becomes ``/media/gallery/1/1.png``,
+    whereas ``/media/gallery/1/1.png`` and ``example.com/media/gallery/1/1.png`` stay the same.
 
+    :return: the url without its scheme and hostname.
     """
 
-    schemeless_url = input_url[len(urlparse.urlparse(input_url).scheme):]
-    schemeless_url = schemeless_url[len('://'):] if schemeless_url.startswith('://') else schemeless_url
-    if schemeless_url.startswith(settings.ZDS_APP['site']['dns']):
-        return schemeless_url[len(settings.ZDS_APP['site']['dns']):]
-    return input_url
+    # Parse URLs after adding a prefix if necessary (e.g 'zestedesavoir.com' becomes '//zestedesavoir.com')
+    url_normalized = url
+    if '//' not in url:
+        url_normalized = '//' + url
+    url_parsed = urllib.parse.urlsplit(url_normalized)
+
+    # Return external URLs unchanged
+    if url_parsed.netloc != settings.ZDS_APP['site']['dns']:
+        return url
+
+    # Clean internal URLs
+    url_noscheme = urllib.parse.urlunsplit(['', '', url_parsed.path, url_parsed.query, url_parsed.fragment])
+    url_cleaned = url_noscheme[0:]  # remove first "/"
+
+    return url_cleaned


Test patch:
diff --git a/zds/utils/tests/test_misc.py b/zds/utils/tests/test_misc.py
--- a/zds/utils/tests/test_misc.py
+++ b/zds/utils/tests/test_misc.py
@@ -1,13 +1,10 @@
-import collections
 import datetime
 from django.test import TestCase
-from django.conf import settings
 from zds.member.factories import ProfileFactory, StaffProfileFactory
 from zds.tutorialv2.factories import PublishedContentFactory
 from zds.utils.misc import contains_utf8mb4
 from zds.utils.models import Alert
 from zds.utils.context_processor import get_header_notifications
-from zds.utils.templatetags.remove_url_scheme import remove_url_scheme
 
 
 class Misc(TestCase):
@@ -34,24 +31,3 @@ def test_intervention_filter_for_tribunes(self):
         filter_result = get_header_notifications(staff.user)['alerts']
         self.assertEqual(1, filter_result['total'])
         self.assertEqual(alert.text, filter_result['list'][0]['text'])
-
-    def test_remove_url_scheme(self):
-        Element = collections.namedtuple('element', ['name', 'given', 'expected'])
-        oracle = {
-            Element('cannonical http', 'http://{}/media/gallery/1/1.png'.format(settings.ZDS_APP['site']['dns']),
-                    '/media/gallery/1/1.png'),
-            Element('cannonical no scheme internal', '{}/media/gallery/1/1.png'.format(settings.ZDS_APP['site']['dns']),
-                    '/media/gallery/1/1.png'),
-            Element('cannonical no scheme external', 'example.com/media/gallery/1/1.png',
-                    'example.com/media/gallery/1/1.png'),
-            Element('cannonical https', 'https://{}/media/gallery/1/1.png'.format(settings.ZDS_APP['site']['dns']),
-                    '/media/gallery/1/1.png'),
-            Element('limit: empty url', '',
-                    ''),
-            Element('old bug: url in qstring', 'http://example.com?q=http://{}'.format(settings.ZDS_APP['site']['dns']),
-                    'http://example.com?q=http://{}'.format(settings.ZDS_APP['site']['dns'])),
-        }
-
-        for element in oracle:
-            # as we are not in py3 we do not have subTest method. so we use a bare for loop.
-            self.assertEqual(remove_url_scheme(element.given), element.expected)
diff --git a/zds/utils/tests/tests_remove_url_scheme.py b/zds/utils/tests/tests_remove_url_scheme.py
--- a/zds/utils/tests/tests_remove_url_scheme.py
+++ b/zds/utils/tests/tests_remove_url_scheme.py
@@ -1,17 +1,54 @@
+import copy
+
 from django.conf import settings
-from django.test import TestCase
+from django.test import TestCase, override_settings
 
 from zds.utils.templatetags.remove_url_scheme import remove_url_scheme
 
 
-class RemoveUrlProtocolTest(TestCase):
+class RemoveUrlSchemeTests(TestCase):
 
-    def test_remove_protocole_when_local_url(self):
-        self.assertEqual('/bla.html', remove_url_scheme('http://' + settings.ZDS_APP['site']['dns'] + '/bla.html'))
-        self.assertEqual('/bla.html', remove_url_scheme('https://' + settings.ZDS_APP['site']['dns'] + '/bla.html'))
+    @staticmethod
+    def get_cases(internal_hostname):
+        """Return test cases corresponding to different URLs types."""
+        return {
+            'no scheme, no hostname':
+                {'input': '/bla.html',
+                 'expected_output': '/bla.html'},
+            'http scheme, internal hostname':
+                {'input': 'http://{}/media/gallery/1/1.png'.format(internal_hostname),
+                 'expected_output': '/media/gallery/1/1.png'},
+            'https scheme, internal hostname':
+                {'input': 'https://{}/media/gallery/1/1.png'.format(internal_hostname),
+                 'expected_output': '/media/gallery/1/1.png'},
+            'no scheme, internal hostname':
+                {'input': '{}/media/gallery/1/1.png'.format(internal_hostname),
+                 'expected_output': '/media/gallery/1/1.png'},
+            'no scheme, external hostname':
+                {'input': 'example.com/media/gallery/1/1.png',
+                 'expected_output': 'example.com/media/gallery/1/1.png'},
+            'http scheme, external hostname, internal hostname in query':
+                {'input': 'http://example.com/?q=http://{}'.format(internal_hostname),
+                 'expected_output': 'http://example.com/?q=http://{}'.format(internal_hostname)}
+        }
 
-    def test_no_change_when_no_protocole(self):
-        self.assertEqual('/bla.html', remove_url_scheme('/bla.html'))
+    def run_cases(self, hostname):
+        """Test url_remove_scheme on all URL cases for a given hostname."""
+        overridden_zds_app = copy.deepcopy(settings.ZDS_APP)
+        overridden_zds_app['site']['dns'] = hostname
+        with override_settings(ZDS_APP=overridden_zds_app):
+            internal_hostname = settings.ZDS_APP['site']['dns']
+            test_cases = RemoveUrlSchemeTests.get_cases(internal_hostname)
+            for case_name, case in test_cases.items():
+                with self.subTest(msg=case_name):
+                    self.assertEqual(case['expected_output'], remove_url_scheme(case['input']))
 
-    def test_no_change_when_extern_address(self):
-        self.assertEqual('http://www.google.com/bla.html', remove_url_scheme('http://www.google.com/bla.html'))
+    def test_url_remove_scheme(self):
+        """Test url_remove_scheme for different hostnames."""
+        hostnames = ['127.0.0.1:8000',  # Raw IP with port
+                     'localhost:8000',  # Name with port
+                     'beta.zestedesavoir.com',  # Name without port, but with subdomain
+                     'zestedesavoir.com']  # Name without port again
+        for hostname in hostnames:
+            with self.subTest(msg=hostname):
+                self.run_cases(hostname)