net.netheos.pcsapi.providers.BasicTest.java Source code

Java tutorial

Introduction

Here is the source code for net.netheos.pcsapi.providers.BasicTest.java

Source

/**
 * Copyright (c) 2014 Netheos (http://www.netheos.net)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.netheos.pcsapi.providers;

import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;

import net.netheos.pcsapi.bytesio.ByteSource;
import net.netheos.pcsapi.bytesio.FileByteSink;
import net.netheos.pcsapi.bytesio.MemoryByteSink;
import net.netheos.pcsapi.bytesio.MemoryByteSource;
import net.netheos.pcsapi.bytesio.ProgressListener;
import net.netheos.pcsapi.bytesio.StdoutProgressListener;
import net.netheos.pcsapi.exceptions.CFileNotFoundException;
import net.netheos.pcsapi.exceptions.CInvalidFileTypeException;
import net.netheos.pcsapi.exceptions.CRetriableException;
import net.netheos.pcsapi.models.CBlob;
import net.netheos.pcsapi.models.CDownloadRequest;
import net.netheos.pcsapi.models.CFile;
import net.netheos.pcsapi.models.CFolder;
import net.netheos.pcsapi.models.CFolderContent;
import net.netheos.pcsapi.models.CMetadata;
import net.netheos.pcsapi.models.CPath;
import net.netheos.pcsapi.models.CQuota;
import net.netheos.pcsapi.models.CUploadRequest;
import net.netheos.pcsapi.oauth.SessionManager;
import net.netheos.pcsapi.oauth.SessionManagerUtil;
import net.netheos.pcsapi.providers.cloudme.CloudMe;
import net.netheos.pcsapi.providers.dropbox.Dropbox;
import net.netheos.pcsapi.providers.googledrive.GoogleDrive;
import net.netheos.pcsapi.providers.hubic.Hubic;
import net.netheos.pcsapi.storage.IStorageProvider;
import net.netheos.pcsapi.storage.StorageFacade;
import net.netheos.pcsapi.storage.StorageProvider;
import net.netheos.pcsapi.utils.PcsUtils;

import org.apache.commons.io.FileUtils;
import org.hamcrest.CoreMatchers;

import static org.hamcrest.CoreMatchers.*;

import org.junit.After;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.Assert.*;

import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

/**
 *
 */
@RunWith(Parameterized.class)
public class BasicTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(BasicTest.class);

    @Parameterized.Parameters(name = "{0}")
    public static Collection<Object[]> getTestsParameters() throws Exception {
        return StorageProviderFactory.storageProviderFactory();
    }

    private File tmpDir;
    private final IStorageProvider storage;

    public BasicTest(IStorageProvider storage) {
        this.storage = storage;
        LOGGER.info("Will run tests with provider : {}", storage);
    }

    @Before
    public void setUp() throws Exception {
        tmpDir = File.createTempFile("pcs_api", ".dir");
        FileUtils.deleteQuietly(tmpDir);
        tmpDir.mkdirs();
    }

    @After
    public void tearDown() {
        FileUtils.deleteQuietly(tmpDir);
    }

    @Test
    public void testRegisteredProviders() {
        Set<String> providerNames = StorageFacade.getRegisteredProviders();
        LOGGER.info("Registered providers in pcs_api: {}", providerNames);
        assertTrue(providerNames.contains(Dropbox.PROVIDER_NAME));
        assertTrue(providerNames.contains(Hubic.PROVIDER_NAME));
        assertTrue(providerNames.contains(GoogleDrive.PROVIDER_NAME));
        assertTrue(providerNames.contains(CloudMe.PROVIDER_NAME));
    }

    @Ignore
    @Test
    public void testCleanupStorageBeforeTests() throws Exception {
        // Not a real test : only some cleanup before next tests
        MiscUtils.cleanupTestFolders(storage);
    }

    @Test
    public void testGetUserId() throws Exception {
        String userId = storage.getUserId();
        LOGGER.info("Retrieved from provider {} : user_id = {}", storage.getProviderName(), userId);
        SessionManager sm = ((StorageProvider) storage).getSessionManager();
        String expectedUserId = SessionManagerUtil.getUserCredentials(sm).getUserId();
        assertEquals(expectedUserId, userId);
    }

    @Test
    public void testDisplayQuota() throws Exception {
        CQuota quota = storage.getQuota();
        LOGGER.info("Retrieved quota for provider {}: {} ({}% used)", storage.getProviderName(), quota,
                quota.getPercentUsed());
    }

    @Test
    public void testQuotaChangedAfterUpload() throws Exception {
        // Quota is not updated in real time for some providers
        Assume.assumeThat("quota not updated in real time for provider " + storage.getProviderName(),
                storage.getProviderName(), not(equalTo(Hubic.PROVIDER_NAME)));
        Assume.assumeThat("quota not updated in real time for provider " + storage.getProviderName(),
                storage.getProviderName(), not(equalTo(GoogleDrive.PROVIDER_NAME)));

        CQuota quotaBefore = storage.getQuota();
        LOGGER.info("Quota BEFORE upload ({} provider) : used={} / total={}", storage.getProviderName(),
                quotaBefore.getBytesUsed(), quotaBefore.getBytesAllowed());

        // Upload a quite big blob :
        int fileSize = 500000;
        CPath path = MiscUtils.generateTestPath(null);
        LOGGER.info("Uploading blob with size {} bytes to {}", fileSize, path);
        byte[] content = MiscUtils.generateRandomByteArray(fileSize, null);
        CUploadRequest uploadRequest = new CUploadRequest(path, new MemoryByteSource(content));
        storage.upload(uploadRequest);

        // Check uploaded blob has correct length :
        CFile cblob = storage.getFile(path);
        assertThat("uploaded file exists", cblob, CoreMatchers.instanceOf(CBlob.class));
        assertEquals(fileSize, ((CBlob) cblob).length());

        LOGGER.info("Checking quota has changed");
        CQuota quotaAfter = storage.getQuota();
        storage.delete(path);
        LOGGER.info("Quota AFTER upload ({} provider) : used={} / total={}", storage.getProviderName(),
                quotaAfter.getBytesUsed(), quotaAfter.getBytesAllowed());
        long usedDifference = quotaAfter.getBytesUsed() - quotaBefore.getBytesUsed();
        LOGGER.info("used bytes difference = {} (upload file size was {})", usedDifference, fileSize);
        assertEquals(fileSize, usedDifference);
    }

    @Test
    public void testFileOperations() throws Exception {
        // We'll use a temp folder for tests
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        // Create a sub-folder :
        CPath subPath = tempRootPath.add("sub_folder");
        LOGGER.info("Creating sub_folder : {}", subPath);
        assertTrue(storage.createFolder(subPath)); // True because actually created
        assertFalse(storage.createFolder(subPath)); // False because not created

        // Check back :
        CFile subFolder = storage.getFile(subPath);
        assertEquals(subPath, subFolder.getPath());
        assertTrue(subFolder.isFolder());
        assertFalse(subFolder.isBlob());
        if (subFolder.getModificationDate() != null) { // Not all providers have a modif time on folders
            MiscUtils.assertDatetimeIsAlmostNow(subFolder.getModificationDate());
        }

        // Upload 2 files into this sub-folder :
        CPath fpath1 = subPath.add("a_test_file1");
        byte[] contentFile1 = "This is binary contnt of test file 1...".getBytes(PcsUtils.UTF8);
        LOGGER.info("Uploading blob to : {}", fpath1);
        CUploadRequest uploadRequest = new CUploadRequest(fpath1, new MemoryByteSource(contentFile1));
        storage.upload(uploadRequest);

        CPath fpath2 = subPath.add("a_test_file2");
        // Generate a quite big random data :
        byte[] contentFile2 = MiscUtils.generateRandomByteArray(500000, null);
        LOGGER.info("Uploading blob to : {}", fpath2);
        uploadRequest = new CUploadRequest(fpath2, new MemoryByteSource(contentFile2));
        storage.upload(uploadRequest);

        // Check uploaded blobs informations :
        // we check file2 first because has just been uploaded / for modif time check
        CFile cblob = storage.getFile(fpath2);
        assertTrue(cblob.isBlob());
        assertFalse(cblob.isFolder());
        assertEquals(contentFile2.length, ((CBlob) cblob).length());
        MiscUtils.assertDatetimeIsAlmostNow(cblob.getModificationDate());

        cblob = storage.getFile(fpath1);
        assertTrue(cblob.isBlob());
        assertFalse(cblob.isFolder());
        assertEquals(contentFile1.length, ((CBlob) cblob).length());

        // Download data, and check :
        LOGGER.info("Downloading back and checking file : {}", fpath1);
        MemoryByteSink mbs = new MemoryByteSink();
        CDownloadRequest downloadRequest = new CDownloadRequest(fpath1, mbs);
        storage.download(downloadRequest);
        assertArrayEquals(contentFile1, mbs.getData());

        // Check also with different Ranges:
        LOGGER.info("Downloading back and checking file ranges: {}", fpath1);
        downloadRequest.setRange(5, -1); // starting at offset 5
        storage.download(downloadRequest);
        assertArrayEquals(Arrays.copyOfRange(contentFile1, 5, contentFile1.length), mbs.getData());
        downloadRequest.setRange(-1, 5); // last 5 bytes
        storage.download(downloadRequest);
        assertArrayEquals(Arrays.copyOfRange(contentFile1, contentFile1.length - 5, contentFile1.length),
                mbs.getData());
        downloadRequest.setRange(2, 5); // 5 bytes at offset 2
        storage.download(downloadRequest);
        assertArrayEquals(Arrays.copyOfRange(contentFile1, 2, 7), mbs.getData());

        LOGGER.info("Downloading back and checking file : {}", fpath2);
        downloadRequest = new CDownloadRequest(fpath2, mbs);
        storage.download(downloadRequest);
        assertArrayEquals(contentFile2, mbs.getData());

        // Check that if we upload again, blob is replaced :
        LOGGER.info("Checking file overwrite : {}", fpath2);
        contentFile2 = MiscUtils.generateRandomByteArray(300000, null);
        uploadRequest = new CUploadRequest(fpath2, new MemoryByteSource(contentFile2));
        storage.upload(uploadRequest);
        storage.download(downloadRequest);
        assertArrayEquals(contentFile2, mbs.getData());

        // Check that we can replace replace existing blob with empty content:
        LOGGER.info("Checking file overwrite with empty file: {}", fpath2);
        contentFile2 = new byte[0];
        uploadRequest = new CUploadRequest(fpath2, new MemoryByteSource(contentFile2));
        storage.upload(uploadRequest);
        storage.download(downloadRequest);
        assertArrayEquals(contentFile2, mbs.getData());

        // Create a sub_sub_folder :
        CPath subSubPath = subPath.add("a_sub_sub_folder");
        LOGGER.info("Creating sub_sub folder : {}", subSubPath);
        storage.createFolder(subSubPath);

        LOGGER.info("Check uploaded blobs and sub_sub_folder all appear in folder list");
        CFolderContent folderContent = storage.listFolder((CFolder) subFolder);
        LOGGER.info("sub_folder contains files : {}", folderContent);
        // It happened once here that hubic did not list fpath1 'a_test_file1' in folder_content :
        // only 2 files were present ?!
        assertEquals(3, folderContent.size());
        assertTrue(folderContent.containsPath(fpath1));
        assertTrue(folderContent.getFile(fpath1).isBlob());
        assertFalse(folderContent.getFile(fpath1).isFolder());
        assertTrue(folderContent.containsPath(fpath2));
        assertTrue(folderContent.getFile(fpath2).isBlob());
        assertFalse(folderContent.getFile(fpath2).isFolder());
        assertTrue(folderContent.containsPath(subSubPath));
        assertFalse(folderContent.getFile(subSubPath).isBlob());
        assertTrue(folderContent.getFile(subSubPath).isFolder());

        LOGGER.info("Check that list of sub_sub folder is empty : {}", subSubPath);
        assertTrue(storage.listFolder(subSubPath).isEmpty());

        LOGGER.info("Check that listing content of a blob raises : {}", fpath1);
        try {
            storage.listFolder(fpath1);
            fail("Listing a blob should raise");
        } catch (CInvalidFileTypeException ex) {
            assertEquals(fpath1, ex.getPath());
            assertFalse(ex.isBlobExpected());
        }

        LOGGER.info("Delete file1 : {}", fpath1);
        assertTrue(storage.delete(fpath1)); // We have deleted the file
        assertFalse(storage.delete(fpath1)); // We have not deleted anything

        LOGGER.info("Check file1 does not appear anymore in folder : {}", subFolder);
        assertFalse(storage.listFolder((CFolder) subFolder).containsPath(fpath1));
        assertNull(storage.getFile(fpath1));

        LOGGER.info("Delete whole test folder : {}", tempRootPath);
        boolean ret = storage.delete(tempRootPath);
        assertTrue(ret); // We have deleted at least one file
        LOGGER.info("Deleting again returns False");
        ret = storage.delete(tempRootPath);
        assertFalse(ret); // We have not deleted anything

        LOGGER.info("Listing a deleted folder returns None : {}", tempRootPath);
        assertNull(storage.listFolder(tempRootPath));
        assertNull(storage.getFile(tempRootPath));
    }

    @Test
    public void testCreateIntermediateFolders() {
        // We'll use a temp folder for tests
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        // We create a deep sub-folder, and check each parent has been created:
        CPath path = tempRootPath.add("sub1/sub2/sub3/sub4/sub5_folder");
        storage.createFolder(path);
        while (!path.isRoot()) {
            CFile file = storage.getFile(path);
            assertNotNull(file);
            assertTrue(file.isFolder());
            path = path.getParent();
        }
    }

    @Test
    public void testBlobContentType() throws Exception {
        Assume.assumeThat("Dropbox ignores content type", storage.getProviderName(),
                not(equalTo(Dropbox.PROVIDER_NAME)));
        Assume.assumeThat("Google Drive ignores content type", storage.getProviderName(),
                not(equalTo(GoogleDrive.PROVIDER_NAME)));
        Assume.assumeThat("CloudMe ignores content type", storage.getProviderName(),
                not(equalTo(CloudMe.PROVIDER_NAME)));

        CPath tempRootPath = MiscUtils.generateTestPath(null);
        try {
            LOGGER.info("Will use test folder :{}", tempRootPath);

            // some providers are sensitive to filename suffix, so we do not specify any here :
            CPath path = tempRootPath.add("uploaded_blob");
            byte[] data = "some content...".getBytes(PcsUtils.UTF8);
            String contentType = "text/plain; charset=Latin-1";
            CUploadRequest uploadRequest = new CUploadRequest(path, new MemoryByteSource(data));
            uploadRequest.setContentType(contentType);
            storage.upload(uploadRequest);

            CFile file = storage.getFile(path);
            assertEquals(contentType, ((CBlob) file).getContentType());

            // Update file content, check content-type is updated also :
            data = "some binary content...".getBytes(PcsUtils.UTF8);
            data[4] = 0x05;
            data[11] = -1; // 0xff
            contentType = "application/octet-stream";
            uploadRequest = new CUploadRequest(path, new MemoryByteSource(data));
            uploadRequest.setContentType(contentType);
            storage.upload(uploadRequest);

            file = storage.getFile(path);
            assertEquals(contentType, ((CBlob) file).getContentType());
        } finally {
            deleteQuietly(tempRootPath, storage);
        }
    }

    @Test
    public void testBlobMetadata() throws Exception {
        Assume.assumeThat("Dropbox ignores metadata", storage.getProviderName(),
                not(equalTo(Dropbox.PROVIDER_NAME)));
        Assume.assumeThat("GoogleDrive ignores metadata", storage.getProviderName(),
                not(equalTo(GoogleDrive.PROVIDER_NAME)));
        Assume.assumeThat("CloudMe ignores metadata", storage.getProviderName(),
                not(equalTo(CloudMe.PROVIDER_NAME)));

        CPath tempRootPath = MiscUtils.generateTestPath(null);
        try {
            LOGGER.info("Will use test folder : {}", tempRootPath);
            CPath path = tempRootPath.add("uploaded_blob.txt");
            byte[] data = "some content...".getBytes(PcsUtils.UTF8);
            CUploadRequest uploadRequest = new CUploadRequest(path, new MemoryByteSource(data));

            CMetadata metadata = new CMetadata();
            metadata.put("foo", "this is foo...");

            try {
                metadata.put("bar", "1 la bire");
                fail("Illegal character setted in metadata value");
            } catch (IllegalArgumentException ex) {
                // Error expected
            }
            uploadRequest.setMetadata(metadata);
            storage.upload(uploadRequest);

            CFile file = storage.getFile(path);
            assertEquals(file.getMetadata(), metadata);

        } finally {
            deleteQuietly(tempRootPath, storage);
        }
    }

    @Test
    public void testDeleteSingleFolder() throws Exception {
        // We'll use a temp folder for tests :
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        // Create two sub folders : a/ and ab/ :
        CPath fpatha = tempRootPath.add("a");
        CPath fpathab = tempRootPath.add("ab");
        storage.createFolder(fpatha);
        storage.createFolder(fpathab);
        assertTrue(storage.getFile(fpatha).isFolder());
        assertTrue(storage.getFile(fpathab).isFolder());

        CPath path = fpatha.add("uploaded_blob.txt");
        byte[] data = "some content...".getBytes(PcsUtils.UTF8);
        CUploadRequest uploadRequest = new CUploadRequest(path, new MemoryByteSource(data));
        storage.upload(uploadRequest);

        // Now delete folder a : folder ab should not be deleted
        storage.delete(fpatha);
        assertNull(storage.getFile(fpatha));
        assertNull(storage.getFile(path));
        assertTrue(storage.getFile(fpathab).isFolder());

        storage.delete(tempRootPath);
    }

    @Test
    public void testInvalidFileOperation() throws Exception {
        // We'll use a temp folder for tests :
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        // Upload 1 file into this folder :
        CPath fpath1 = tempRootPath.add("a_test_file1");
        byte[] contentFile1 = "This is binary contnt of test file 1...".getBytes(PcsUtils.UTF8);
        CUploadRequest uploadRequest = new CUploadRequest(fpath1, new MemoryByteSource(contentFile1));
        storage.upload(uploadRequest);
        LOGGER.info("Created blob : {}", fpath1);

        // Create a sub_folder :
        CPath subFolder = tempRootPath.add("sub_folder");
        storage.createFolder(subFolder);

        LOGGER.info("Check that listing content of a blob raises : {}", fpath1);
        try {
            storage.listFolder(fpath1);
            fail("Listing a blob should raise");
        } catch (CInvalidFileTypeException ex) {
            assertEquals(fpath1, ex.getPath());
            assertFalse(ex.isBlobExpected());
        }

        LOGGER.info("Check that trying to download a folder raises : {}", subFolder);
        MemoryByteSink mbs = new MemoryByteSink();
        CDownloadRequest downloadRequest = new CDownloadRequest(subFolder, mbs);
        try {
            storage.download(downloadRequest);
            fail("Downloading a folder should raise");
        } catch (CInvalidFileTypeException ex) {
            assertEquals(subFolder, ex.getPath());
            assertTrue(ex.isBlobExpected());
        }

        LOGGER.info("Check that we cannot create a folder over a blob : {}", fpath1);
        try {
            storage.createFolder(fpath1);
            fail("Creating a folder over a blob should raise");
        } catch (CInvalidFileTypeException ex) {
            assertEquals(fpath1, ex.getPath());
            assertFalse(ex.isBlobExpected());
        }

        LOGGER.info("Check we cannot upload over an existing folder : {}", subFolder);
        try {
            byte[] content = "some data...".getBytes(PcsUtils.UTF8);
            uploadRequest = new CUploadRequest(subFolder, new MemoryByteSource(content));
            storage.upload(uploadRequest);
            fail("Uploading over a folder should raise");

        } catch (CInvalidFileTypeException ex) {
            assertEquals(subFolder, ex.getPath());
            assertTrue(ex.isBlobExpected());
        }

        LOGGER.info("Check that content of a never existed folder is None");
        CPath path = new CPath("/hope i did never exist (even for tests) !");
        assertNull(storage.listFolder(path));
        LOGGER.info("Check that get_file() returns None is file does not exist");
        assertNull(storage.getFile(path));

        LOGGER.info("Check that downloading a non-existing file raises");
        downloadRequest = new CDownloadRequest(path, new MemoryByteSink());
        try {
            storage.download(downloadRequest);
            fail("Downlad a non-existing blob should raise");
        } catch (CFileNotFoundException ex) {
            LOGGER.debug("Expected exception : {}", ex.toString());
            assertEquals(path, ex.getPath());
        }

        storage.delete(tempRootPath);
    }

    @Test
    public void testCreateFolderOverBlob() throws Exception {
        // We'll use a temp folder for tests :
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        try {
            // Upload 1 file into this folder :
            CPath fpath1 = tempRootPath.add("a_test_file1");
            byte[] contentFile1 = "This is binary contnt of test file 1...".getBytes(PcsUtils.UTF8);
            CUploadRequest uploadRequest = new CUploadRequest(fpath1, new MemoryByteSource(contentFile1));
            storage.upload(uploadRequest);
            LOGGER.info("Created blob : {}", fpath1);

            try {
                CPath path = fpath1.add("sub_folder1");
                LOGGER.info("Check we cannot create a folder when remote path traverses a blob : {}", path);
                storage.createFolder(path);

                // This is known to fail on Dropbox:
                Assume.assumeThat(
                        "Creating folder when path contains a blob should raise: not supported by Dropbox",
                        storage.getProviderName(), not(equalTo(Dropbox.PROVIDER_NAME)));
                fail("Creating folder when path contains a blob should raise");

            } catch (CInvalidFileTypeException ex) {
                assertEquals(fpath1, ex.getPath());
                assertFalse(ex.isBlobExpected());
            }

        } finally {
            deleteQuietly(tempRootPath, storage);
        }
    }

    @Test
    public void testImplicitCreateFolderOverBlob() throws Exception {
        // We'll use a temp folder for tests :
        CPath tempRootPath = MiscUtils.generateTestPath(null);
        LOGGER.info("Will use test folder : {}", tempRootPath);

        try {
            // Upload 1 file into this folder :
            CPath fpath1 = tempRootPath.add("a_test_file1");
            byte[] contentFile1 = "This is binary contnt of test file 1...".getBytes(PcsUtils.UTF8);
            CUploadRequest uploadRequest = new CUploadRequest(fpath1, new MemoryByteSource(contentFile1));
            storage.upload(uploadRequest);
            LOGGER.info("Created blob : {}", fpath1);

            // Uploading blob will implicitely create intermediate folders,
            // so will try to erase fpath1
            try {
                CPath path = fpath1.add("sub_file1");
                LOGGER.info("Check we cannot upload a blob when remote path traverses a blob : {}", path);
                byte[] content = "some data...".getBytes(PcsUtils.UTF8);
                uploadRequest = new CUploadRequest(path, new MemoryByteSource(content));
                storage.upload(uploadRequest);

                // This is known to fail on Dropbox :
                Assume.assumeThat("Uploading when path contains a blob should raise : not supported by Dropbox",
                        storage.getProviderName(), not(equalTo(Dropbox.PROVIDER_NAME)));
                fail("Uploading when path contains a blob should raise");

            } catch (CInvalidFileTypeException ex) {
                assertEquals(fpath1, ex.getPath()); // this is the problematic path
                assertFalse(ex.isBlobExpected());
            }

        } finally {
            deleteQuietly(tempRootPath, storage);
        }
    }

    @Test
    public void testFileWithSpecialChars() throws Exception {
        CPath tempRootPath = MiscUtils.generateTestPath(null);

        try {
            CPath folderPath = tempRootPath.add("hum...\u00a0',;.:\u00a0!*%&~#{[|`_^@ ");
            assertTrue(storage.createFolder(folderPath));
            CFile fback = storage.getFile(folderPath);
            assertEquals(folderPath, fback.getPath());
            assertTrue(fback.isFolder());
            assertFalse(fback.isBlob());

            // Folder must appear in test root folder list :
            CFolderContent rootTestContent = storage.listFolder(tempRootPath);
            assertTrue(rootTestContent.containsPath(folderPath));
            assertEquals(folderPath, rootTestContent.getFile(folderPath).getPath());
            assertTrue(rootTestContent.getFile(folderPath).isFolder());
            assertFalse(rootTestContent.getFile(folderPath).isBlob());

            // Generate a random blob name (ensure it does not start nor end with a space)
            StringBuilder nameb = new StringBuilder("b");
            for (int i = 0; i < 30; i++) {
                nameb.append(generateRandomBlobNameChar());
            }
            nameb.append("e");
            for (int nbBlobs = 0; nbBlobs < 10; nbBlobs++) {
                // slightly change blob name, so that we get similar but different names :
                int index = (int) (Math.random() * (nameb.length() - 2)) + 1;
                nameb.setCharAt(index, generateRandomBlobNameChar());
                String blobName = nameb.toString();

                CPath blobPath = folderPath.add(blobName);
                LOGGER.info("Will upload file to path: {}", blobPath);

                String contentFileStr = "This is contnt of test file : '" + blobName + "'";
                byte[] contentFile = contentFileStr.getBytes(PcsUtils.UTF8);
                CUploadRequest uploadRequest = new CUploadRequest(blobPath, new MemoryByteSource(contentFile));
                uploadRequest.setContentType("text/plain ; charset=UTF-8");
                storage.upload(uploadRequest);
                CFile bback = storage.getFile(blobPath);
                assertEquals(blobPath, bback.getPath());
                assertTrue(bback.isBlob());
                assertFalse(bback.isFolder());

                // Download and check content :
                MemoryByteSink mbs = new MemoryByteSink();
                CDownloadRequest downloadRequest = new CDownloadRequest(blobPath, mbs);
                storage.download(downloadRequest);
                assertArrayEquals(contentFile, mbs.getData());

                // new blob must appear in folder list :
                CFolderContent foderContent = storage.listFolder(folderPath);
                assertTrue(foderContent.containsPath(blobPath));
                assertEquals(blobPath, foderContent.getFile(blobPath).getPath());
                assertTrue(foderContent.getFile(blobPath).isBlob());
                assertFalse(foderContent.getFile(blobPath).isFolder());
            }
        } finally {
            deleteQuietly(tempRootPath, storage);
        }
    }

private char generateRandomBlobNameChar()
{
    while ( true ) {
        char c = ( char ) ( ( Math.random() * Math.random() * 200 ) + 32 );
        if ( Math.random() < 0.01 ) {
            c = ''; // longer once UTF-8 encoded
        }

        if ( c >= 127 && c < 160 ) {
            continue;
        }
        if ( c == '/' || c == '\\' ) {
            continue;
        }
        // CloudMe provider does not support quotes so we do not use them :
        if ( c == '"' && CloudMe.PROVIDER_NAME.equals( storage.getProviderName() ) ) {
            continue;
        }
        return c;
    }
}

    @Test
    public void testAbortDuringDownload() throws Exception {
        // If download fails, check that abort is called on progress listener
        // Upload a quite big file (for download) :
        int fileSize = 500000;
        CPath path = MiscUtils.generateTestPath(null);
        LOGGER.info("Uploading blob with size {} bytes to {}", fileSize, path);

        try {
            LOGGER.info("Will upload a blob for download test ({} bytes) to {}", fileSize, path);
            byte[] content = MiscUtils.generateRandomByteArray(fileSize, null);
            CUploadRequest uploadRequest = new CUploadRequest(path, new MemoryByteSource(content));
            storage.upload(uploadRequest);

            // Now we download, asking for progress :
            LOGGER.info("Will download this blob but fail during download...");
            File tempFile = new File(tmpDir, "back_from_provider");
            FileByteSink fbs = new FileByteSink(tempFile, false, true, false);
            TestAbortProgressListener pl = new TestAbortProgressListener(1, // fail once
                    fileSize / 2, // abort at half download
                    false); // not retriable
            CDownloadRequest dr = new CDownloadRequest(path, fbs).setProgressListener(pl);

            try {
                storage.download(dr);
                fail("Download should have failed !");
            } catch (Exception ex) {
                LOGGER.info("Download has failed (as expected)");
            }

            // Check listener saw the abort :
            assertTrue(pl.isAborted());

            // check file does not exist (has been removed) :
            LOGGER.info("Check destination file does not exist : {}", tempFile.getPath());
            assertFalse(tempFile.exists());

        } finally {
            deleteQuietly(path, storage);
        }
    }

    @Test
    public void testAbortTwiceDuringUpload() throws Exception {
        int fileSize = 500000;
        CPath path = MiscUtils.generateTestPath(null);
        LOGGER.info("Uploading blob with size {} bytes to {}", fileSize, path);

        try {
            LOGGER.info(
                    "Will upload a blob for test ({} bytes) to {}, but fail temporarily during first two uploads",
                    fileSize, path);
            byte[] content = MiscUtils.generateRandomByteArray(fileSize, null);
            ByteSource bs = new MemoryByteSource(content);
            ProgressListener pl = new TestAbortProgressListener(2, // fail twice
                    fileSize / 2, // fail at half upload
                    true); // and retry
            CUploadRequest uploadRequest = new CUploadRequest(path, bs).setProgressListener(pl);
            storage.upload(uploadRequest);

            // check it has worked :
            CFile cfile = storage.getFile(path);
            assertThat(cfile, CoreMatchers.instanceOf(CBlob.class));
            assertEquals(fileSize, ((CBlob) cfile).length());

        } finally {
            deleteQuietly(path, storage);
        }
    }

    private void deleteQuietly(CPath path, IStorageProvider storage) {
        if (path != null) {
            try {
                storage.delete(path);
            } catch (Throwable th) {
                LOGGER.warn("Error deleting file " + path, th);
            }
        }
    }

    private static class TestAbortProgressListener extends StdoutProgressListener {

        private final int nbFailsTotal;
        private final int limit;
        private final boolean retriableException;
        private int nbFailsCurrent;

        /**
         * @param nbFails number of times an exception will be raised
         * @param offsetLimit at which offset in byte source the exception will be raised
         * @param retriableException if true, a CRetriableException is thrown so request will be retried
         */
        private TestAbortProgressListener(int nbFails, int offsetLimit, boolean retriableException) {
            this.nbFailsTotal = nbFails;
            this.limit = offsetLimit;
            this.retriableException = retriableException;
        }

        @Override
        public void progress(long current) {
            super.progress(current);
            if (current >= limit) {
                if (nbFailsCurrent < nbFailsTotal) {
                    nbFailsCurrent++;
                    RuntimeException ex = new RuntimeException(
                            "Test error to make up/download fail : " + nbFailsCurrent + "/" + nbFailsTotal);
                    if (retriableException) {
                        ex = new CRetriableException(ex);
                    }
                    throw ex;
                }
            }
        }

    }

}