class ApiGetUploadTestCase(TestCase):
    def setUp(self):
        # Set up an initial file upload
        self.upload_id = _get_file_id()
        self.file_id = _get_file_id()
        self.file_content = ('This is some test file data for an '
                             'uploaded file.')
        self.fn = 'my_test_file.txt'
        self.test_target_filename = '/test_storage/testfile.txt'
        self.su = StoredUpload(upload_id=self.upload_id,
                               file=self.test_target_filename[1:],
                               uploaded=timezone.now())
        self.su.save()
        # Create file
        self.file_store_path = getattr(local_settings, 'FILE_STORE_PATH', None)
        file_full_path = os.path.join(self.file_store_path,
                                      self.test_target_filename[1:])
        if not os.path.exists(os.path.dirname(file_full_path)):
            os.mkdir(os.path.dirname(file_full_path))
        with open(file_full_path, 'w') as f:
            f.write(self.file_content)

    def test_store_upload_unset_file_store_path(self):
        (filename, bytes_io) = get_stored_upload_file_data(self.su)
        file_data = bytes_io.read().decode()
        self.assertEqual(file_data, self.file_content,
                         'Returned file content not correct.')
        self.assertEqual(filename, os.path.basename(self.test_target_filename),
                         'Returned file name is not correct.')

    def test_get_local_stored_upload_no_filestore(self):
        fsp = local_settings.FILE_STORE_PATH
        local_settings.FILE_STORE_PATH = None
        with self.assertRaisesMessage(
                ConfigurationError,
                'The file upload settings are not configured correctly.'):
            get_stored_upload_file_data(self.su)
        local_settings.FILE_STORE_PATH = fsp

    def test_get_remote_stored_upload_data(self):
        # Set up the mock_storage_backend.open to return the file content
        mock_storage_backend = self._setup_mock_storage_backend()
        mock_storage_backend.open.return_value = BytesIO(
            self.file_content.encode())
        mock_storage_backend.exists.return_value = True
        (filename, bytes_io) = get_stored_upload_file_data(self.su)
        file_data = bytes_io.read().decode()
        local_settings.STORAGES_BACKEND = None
        django_drf_filepond.api.storage_backend = None
        self.assertEqual(file_data, self.file_content,
                         'Returned file content not correct.')
        self.assertEqual(filename, os.path.basename(self.test_target_filename),
                         'Returned file name is not correct.')

    def test_storage_backend_initialised_when_required(self):
        mock_storage_backend = self._setup_mock_storage_backend()
        mock_storage_backend.open.return_value = BytesIO(
            self.file_content.encode())
        mock_storage_backend.exists.return_value = True
        with patch('django_drf_filepond.api._init_storage_backend') as m:
            django_drf_filepond.api.storage_backend_initialised = False
            get_stored_upload_file_data(self.su)
            local_settings.STORAGES_BACKEND = None
            django_drf_filepond.api.storage_backend = None
            try:
                m.assert_called_once()
            # No assert_called_once on Python 3.5
            except AttributeError:
                self.assertEqual(
                    m.call_count, 1,
                    ('Expected _init_storage_backend to be called once but '
                     'it has been called %s times.' % m.call_count))

    def test_get_remote_upload_not_on_remote_store(self):
        # File store path for remote testing should be ''
        file_store_path = ''
        mock_storage_backend = self._setup_mock_storage_backend()
        mock_storage_backend.exists.return_value = False
        file_path = os.path.join(file_store_path, self.su.file.name)
        with self.assertRaisesMessage(
                FileNotFoundError,
            ('File [%s] for upload_id [%s] not found on remote '
             'file store.' % (file_path, self.su.upload_id))):
            get_stored_upload_file_data(self.su)
            local_settings.STORAGES_BACKEND = None
            django_drf_filepond.api.storage_backend = None

    def _setup_mock_storage_backend(self):
        # Set storage backend to sftp storage
        local_settings.STORAGES_BACKEND = \
            'storages.backends.sftpstorage.SFTPStorage'

        # Set up mocked objects for storage backend testing
        # Setting up a mock for the storage backend based on examples at
        # https://docs.python.org/3.7/library/unittest.mock-examples.html# \
        # applying-the-same-patch-to-every-test-method
        patcher = patch('storages.backends.sftpstorage.SFTPStorage')
        patcher.start()
        self.addCleanup(patcher.stop)

        # Mock the temporary storage object so that we're not unnecessarily
        # saving files to the local file system.
        patcher2 = patch('django.core.files.storage.FileSystemStorage')
        patcher2.start()
        self.addCleanup(patcher2.stop)
        # Set the backend initialisation flag to false to force re-init
        django_drf_filepond.api.storage_backend_initialised = False
        django_drf_filepond.api._init_storage_backend()
        mock_storage_backend = django_drf_filepond.api.storage_backend
        return mock_storage_backend

    def tearDown(self):
        # Delete stored upload
        self.su.delete()

        # Delete files
        filestore_base = getattr(local_settings, 'FILE_STORE_PATH', None)
        test_stored_file = os.path.join(filestore_base,
                                        self.test_target_filename[1:])
        test_target_dir = os.path.dirname(test_stored_file)

        # Remove test_stored_file
        if (os.path.exists(test_stored_file)
                and os.path.isfile(test_stored_file)):
            LOG.debug('Removing test stored file: <%s>' % test_stored_file)
            os.remove(test_stored_file)
        # Remove directory
        if (os.path.exists(test_target_dir)
                and os.path.isdir(test_target_dir)):
            LOG.debug('Removing test target dir: <%s>' % test_target_dir)
            os.rmdir(test_target_dir)
예제 #2
0
class ApiDeleteTestCase(TestCase):
    def setUp(self):
        self.storage_backend = local_settings.STORAGES_BACKEND
        # Set storage backend to sftp storage
        local_settings.STORAGES_BACKEND = \
            'storages.backends.sftpstorage.SFTPStorage'

        # Setting up a mock for the storage backend based on examples at
        # https://docs.python.org/3.7/library/unittest.mock-examples.html# \
        # applying-the-same-patch-to-every-test-method
        patcher = patch('storages.backends.sftpstorage.SFTPStorage')
        patcher.start()
        self.addCleanup(patcher.stop)

        # Mock the temporary storage object so that we're not unnecessarily
        # saving files to the local file system.
        patcher2 = patch('django.core.files.storage.FileSystemStorage')
        patcher2.start()
        self.addCleanup(patcher2.stop)
        # Reinitialse stored upload after we changed the STORAGES_BACKEND
        # class name above. This ensures that we're looking at the mocked
        # backend class.
        import django_drf_filepond.api
        self.api = django_drf_filepond.api
        django_drf_filepond.api.storage_backend_initialised = False
        django_drf_filepond.api._init_storage_backend()
        self.mock_storage_backend = django_drf_filepond.api.storage_backend
        self.delete_upload = django_drf_filepond.api.delete_stored_upload

        # Check that we're using a mocked storage backend
        self.assertTrue(
            isinstance(django_drf_filepond.api.storage_backend, MagicMock),
            ('The created storage backend should be mocked but it is not of '
             'type unittest.mock.MagicMock...'))

        # Set up a "StoredUpload"
        self.upload_id = _get_file_id()
        self.file_id = _get_file_id()
        # self.file_content = ('This is some test file data for an '
        #                      'uploaded file.')
        self.fn = 'my_test_file.txt'
        self.test_target_filepath = os.path.join('test_storage', self.fn)
        self.su = StoredUpload(upload_id=self.upload_id,
                               file=self.test_target_filepath,
                               uploaded=timezone.now(),
                               stored=timezone.now())
        self.su.save()

    def test_delete_stored_upload_local(self):
        # Make an API call to delete the stored upload and check that delete
        # is called on the FileSystemStorage backend.
        # Set the storage backend to None to force use of local storage.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False

        # Get the target file path that we want to check that delete has been
        # called with. This is the file path from the DB plus the base file
        # system storage location.
        base_store_location = local_settings.FILE_STORE_PATH
        file_location = os.path.join(base_store_location,
                                     self.test_target_filepath)

        with patch('os.remove') as os_patcher:
            with patch('os.path.exists') as exists:
                with patch('os.path.isfile') as isfile:
                    exists.return_value = True
                    isfile.return_value = True
                    self.delete_upload(self.upload_id, delete_file=True)
                    with self.assertRaises(StoredUpload.DoesNotExist):
                        StoredUpload.objects.get(upload_id=self.upload_id)
                    os_patcher.assert_called_once_with(file_location)

    def test_delete_stored_upload_remote(self):
        self.mock_storage_backend.exists.return_value = True
        self.delete_upload(self.upload_id, delete_file=True)
        self.mock_storage_backend.delete.assert_called_once_with(
            self.test_target_filepath)

    def test_delete_stored_upload_local_keepfile(self):
        # Make an API call to delete the stored upload and check that delete
        # is called on the FileSystemStorage backend.
        # Set the storage backend to None to force use of local storage.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False

        with patch('os.remove') as os_patcher:
            with patch('os.path.exists') as exists:
                with patch('os.path.isdir') as isdir:
                    exists.return_value = True
                    isdir.return_value = True
                    self.delete_upload(self.upload_id, delete_file=False)
                    with self.assertRaises(StoredUpload.DoesNotExist):
                        StoredUpload.objects.get(upload_id=self.upload_id)
                    os_patcher.assert_not_called()
                    exists.assert_not_called()
                    isdir.assert_not_called()

    def test_delete_stored_upload_remote_keepfile(self):
        self.delete_upload(self.upload_id, delete_file=False)
        self.mock_storage_backend.exists.assert_not_called()
        self.mock_storage_backend.delete.assert_not_called()

    def test_delete_stored_upload_invalid_id(self):
        test_id = 'abcdefghijklmnopqrstuv'
        ('No stored upload found with the specified ID [%s].' % (test_id))
        with self.assertRaisesMessage(
                StoredUpload.DoesNotExist,
                'StoredUpload matching query does not exist.'):
            self.delete_upload(test_id)

    def test_delete_stored_upload_local_no_filestore_path(self):
        fsp = local_settings.FILE_STORE_PATH
        local_settings.FILE_STORE_PATH = None
        # Need to set storage backend to None and make it reinitialise to
        # ensure that we're not using a remote backend for this test.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False
        with self.assertRaisesMessage(
                ConfigurationError,
                'The file upload settings are not configured correctly.'):
            self.delete_upload(self.upload_id, delete_file=True)
        local_settings.FILE_STORE_PATH = fsp

    def test_delete_stored_upload_local_not_exists(self):
        # Need to set storage backend to None and make it reinitialise to
        # ensure that we're not using a remote backend for this test.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False
        with patch('os.path.exists') as exists:
            with patch('os.path.isdir') as isdir:
                exists.return_value = False
                isdir.return_value = True
                with self.assertRaisesMessage(
                        ConfigurationError,
                    ('The file upload settings are not configured '
                     'correctly.')):
                    self.delete_upload(self.upload_id, delete_file=True)

    def test_delete_stored_upload_local_not_isdir(self):
        # Need to set storage backend to None and make it reinitialise to
        # ensure that we're not using a remote backend for this test.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False
        with patch('os.path.exists') as exists:
            with patch('os.path.isdir') as isdir:
                exists.return_value = True
                isdir.return_value = False
                with self.assertRaisesMessage(
                        ConfigurationError,
                    ('The file upload settings are not configured '
                     'correctly.')):
                    self.delete_upload(self.upload_id, delete_file=True)

    def test_delete_stored_upload_remote_file_missing(self):
        self.mock_storage_backend.exists.return_value = False
        with self.assertRaisesMessage(
                FileNotFoundError,
            ('File [%s] for stored upload with id [%s] not found on '
             'remote file store.' %
             (self.test_target_filepath, self.upload_id))):
            self.delete_upload(self.upload_id, delete_file=True)

    def test_delete_stored_upload_local_file_missing(self):
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False
        target_filepath = os.path.join(local_settings.FILE_STORE_PATH,
                                       self.test_target_filepath)
        with patch('os.path.exists') as exists:
            exists.side_effect = [True, False]
            with self.assertRaisesMessage(
                    FileNotFoundError,
                ('File [%s] to delete was not found on the local disk' %
                 (target_filepath))):
                self.delete_upload(self.upload_id, delete_file=True)

    def test_delete_stored_upload_local_remove_fails(self):
        # Make an API call to delete the stored upload and check that delete
        # is called on the FileSystemStorage backend.
        # Set the storage backend to None to force use of local storage.
        local_settings.STORAGES_BACKEND = None
        self.api.storage_backend_initialised = False

        # Get the target file path that we want to check that delete has been
        # called with. This is the file path from the DB plus the base file
        # system storage location.
        base_store_location = local_settings.FILE_STORE_PATH
        file_location = os.path.join(base_store_location,
                                     self.test_target_filepath)

        with patch('os.remove') as os_patcher:
            with patch('os.path.exists') as exists:
                with patch('os.path.isfile') as isfile:
                    exists.return_value = True
                    isfile.return_value = True
                    os_patcher.side_effect = OSError('Error deleting file')
                    with self.assertRaisesMessage(OSError,
                                                  'Error deleting file'):
                        self.delete_upload(self.upload_id, delete_file=True)
                    os_patcher.assert_called_once_with(file_location)

    def tearDown(self):
        local_settings.STORAGES_BACKEND = self.storage_backend

        self.su.delete()

        self.api.storage_backend_initialised = False