import shutil import tempfile from django.conf import settings from django.test.utils import override_settings from django.utils.encoding import force_bytes from django_downloadview.middlewares import is_download_response from django_downloadview.response import encode_basename_ascii, encode_basename_utf8 def setup_view(view, request, *args, **kwargs): """Mimic ``as_view()``, but returns view instance. Use this function to get view instances on which you can run unit tests, by testing specific methods. This is an early implementation of https://code.djangoproject.com/ticket/20456 ``view`` A view instance, such as ``TemplateView(template_name='dummy.html')``. Initialization arguments are the same you would pass to ``as_view()``. ``request`` A request object, typically built with :class:`~django.test.client.RequestFactory`. ``args`` and ``kwargs`` "URLconf" positional and keyword arguments, the same you would pass to :func:`~django.core.urlresolvers.reverse`. """ view.request = request view.args = args view.kwargs = kwargs return view class temporary_media_root(override_settings): """Temporarily override settings.MEDIA_ROOT with a temporary directory. The temporary directory is automatically created and destroyed. Use this function as a context manager: >>> from django_downloadview.test import temporary_media_root >>> from django.conf import settings # NoQA >>> global_media_root = settings.MEDIA_ROOT >>> with temporary_media_root(): ... global_media_root == settings.MEDIA_ROOT False >>> global_media_root == settings.MEDIA_ROOT True Or as a decorator: >>> @temporary_media_root() ... def use_temporary_media_root(): ... return settings.MEDIA_ROOT >>> tmp_media_root = use_temporary_media_root() >>> global_media_root == tmp_media_root False >>> global_media_root == settings.MEDIA_ROOT True """ def enable(self): """Create a temporary directory and use it to override settings.MEDIA_ROOT.""" tmp_dir = tempfile.mkdtemp() self.options["MEDIA_ROOT"] = tmp_dir super().enable() def disable(self): """Remove directory settings.MEDIA_ROOT then restore original setting.""" shutil.rmtree(settings.MEDIA_ROOT) super().disable() class DownloadResponseValidator(object): """Utility class to validate DownloadResponse instances.""" def __call__(self, test_case, response, **assertions): """Assert that ``response`` is a valid DownloadResponse instance. Optional ``assertions`` dictionary can be used to check additional items: * ``basename``: the basename of the file in the response. * ``content_type``: the value of "Content-Type" header. * ``mime_type``: the MIME type part of "Content-Type" header (without charset). * ``content``: the contents of the file. * ``attachment``: whether the file is returned as attachment or not. """ self.assert_download_response(test_case, response) for key, value in assertions.items(): assert_func = getattr(self, "assert_%s" % key) assert_func(test_case, response, value) def assert_download_response(self, test_case, response): test_case.assertTrue(is_download_response(response)) def assert_basename(self, test_case, response, value): """Implies ``attachement is True``.""" ascii_name = encode_basename_ascii(value) utf8_name = encode_basename_utf8(value) check_utf8 = False check_ascii = False if ascii_name == utf8_name: # Only ASCII characters. check_ascii = True if "filename*=" in response["Content-Disposition"]: check_utf8 = True else: check_utf8 = True if "filename=" in response["Content-Disposition"]: check_ascii = True if check_ascii: test_case.assertIn( f'filename="{ascii_name}"', response["Content-Disposition"], ) if check_utf8: test_case.assertIn( f"filename*=UTF-8''{utf8_name}", response["Content-Disposition"], ) def assert_content_type(self, test_case, response, value): test_case.assertEqual(response["Content-Type"], value) def assert_mime_type(self, test_case, response, value): test_case.assertTrue(response["Content-Type"].startswith(value)) def assert_content(self, test_case, response, value): """Assert value equals response's content (byte comparison).""" parts = [force_bytes(s) for s in response.streaming_content] test_case.assertEqual(b"".join(parts), force_bytes(value)) def assert_attachment(self, test_case, response, value): if value: test_case.assertTrue("attachment;" in response["Content-Disposition"]) else: test_case.assertTrue( "Content-Disposition" not in response or "attachment;" not in response["Content-Disposition"] ) def assert_download_response(test_case, response, **assertions): """Make ``test_case`` assert that ``response`` meets ``assertions``. Optional ``assertions`` dictionary can be used to check additional items: * ``basename``: the basename of the file in the response. * ``content_type``: the value of "Content-Type" header. * ``mime_type``: the MIME type part of "Content-Type" header (without charset). * ``content``: the contents of the file. * ``attachment``: whether the file is returned as attachment or not. """ validator = DownloadResponseValidator() return validator(test_case, response, **assertions)