/**
 * Copyright Microsoft Corporation
 *
 * 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 com.microsoft.azure.storage.blob;

import static org.junit.Assert.*;

import android.annotation.SuppressLint;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;
import java.util.UUID;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.UriQueryBuilder;
import com.microsoft.azure.storage.NameValidator;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.ResultContinuation;
import com.microsoft.azure.storage.ResultSegment;
import com.microsoft.azure.storage.SendingRequestEvent;
import com.microsoft.azure.storage.SharedAccessAccountPermissions;
import com.microsoft.azure.storage.SharedAccessAccountPolicy;
import com.microsoft.azure.storage.SharedAccessAccountResourceType;
import com.microsoft.azure.storage.SharedAccessAccountService;
import com.microsoft.azure.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageEvent;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.TestHelper;
import com.microsoft.azure.storage.TestRunners.CloudTests;
import com.microsoft.azure.storage.TestRunners.DevFabricTests;
import com.microsoft.azure.storage.TestRunners.DevStoreTests;
import com.microsoft.azure.storage.TestRunners.SlowTests;

/**
 * Blob Container Tests
 */
@Category({ CloudTests.class })
public class CloudBlobContainerTests {
    protected static CloudBlobClient client;
    protected CloudBlobContainer container;

    @Before
    public void blobContainerTestMethodSetUp() throws Exception {
        this.container = BlobTestHelper.getRandomContainerReference();
    }

    @After
    public void blobContainerTestMethodTearDown() throws Exception {
        this.container.deleteIfExists();
    }

    /**
     * Test container name validation.
     */
    @Test
    public void testCloudBlobContainerNameValidation()
    {
        NameValidator.validateContainerName("alpha");
        NameValidator.validateContainerName("4lphanum3r1c");
        NameValidator.validateContainerName("middle-dash");
        NameValidator.validateContainerName("$root");
        NameValidator.validateContainerName("$logs");

        invalidContainertTestHelper(null, "Null containers invalid.", "Invalid container name. The name may not be null, empty, or whitespace only.");
        invalidContainertTestHelper("$ROOT", "Root container case sensitive.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("double--dash", "Double dashes not allowed.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("-start-dash", "Start dashes not allowed.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("CapsLock", "Lowercase only.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("illegal$char", "Only alphanumeric and hyphen characters.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("illegal!char", "Only alphanumeric and hyphen characters.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("white space", "Only alphanumeric and hyphen characters.", "Invalid container name. Check MSDN for more information about valid naming.");
        invalidContainertTestHelper("2c", "Root container case sensitive.", "Invalid container name length. The name must be between 3 and 63 characters long.");
        invalidContainertTestHelper(new String(new char[64]).replace("\0", "n"), "Between 3 and 64 characters.", "Invalid container name length. The name must be between 3 and 63 characters long.");
    }

    private void invalidContainertTestHelper(String containerName, String failMessage, String exceptionMessage)
    {
        try
        {
            NameValidator.validateContainerName(containerName);
            fail(failMessage);
        }
        catch (IllegalArgumentException e)
        {
            assertEquals(exceptionMessage, e.getMessage());
        }
    }

    /**
     * Validate container references
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerReference() throws StorageException, URISyntaxException{
        CloudBlobClient client = BlobTestHelper.createCloudBlobClient();
        CloudBlobContainer container = client.getContainerReference("container");
        CloudBlockBlob blockBlob = container.getBlockBlobReference("directory1/blob1");
        CloudPageBlob pageBlob = container.getPageBlobReference("directory2/blob2");
        CloudBlobDirectory directory = container.getDirectoryReference("directory3");
        CloudBlobDirectory directory2 = directory.getDirectoryReference("directory4");

        assertEquals(container.getStorageUri().toString(), blockBlob.getContainer().getStorageUri().toString());
        assertEquals(container.getStorageUri().toString(), pageBlob.getContainer().getStorageUri().toString());
        assertEquals(container.getStorageUri().toString(), directory.getContainer().getStorageUri().toString());
        assertEquals(container.getStorageUri().toString(), directory2.getContainer().getStorageUri().toString());
        assertEquals(container.getStorageUri().toString(), directory2.getParent().getContainer().getStorageUri()
                .toString());
        assertEquals(container.getStorageUri().toString(), blockBlob.getParent().getContainer().getStorageUri()
                .toString());
        assertEquals(container.getStorageUri().toString(), blockBlob.getParent().getContainer().getStorageUri()
                .toString());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerReferenceFromServer() throws StorageException, URISyntaxException, IOException {
        this.container.create();

        CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
        blob.getProperties().setContentType("application/octet-stream");
        blob.getProperties().setLength(1024);

        CloudBlob blobRef = this.container.getBlobReferenceFromServer(blob.getName());
        BlobTestHelper.assertAreEqual(blob, blobRef);

        blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.PAGE_BLOB, null, 1024, null);
        blob.getProperties().setContentType("application/octet-stream");
        blob.getProperties().setLength(1024);

        blobRef = this.container.getBlobReferenceFromServer(blob.getName());
        BlobTestHelper.assertAreEqual(blob, blobRef);

        blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.APPEND_BLOB, null, 1024, null);
        blob.getProperties().setContentType("application/octet-stream");
        blob.getProperties().setLength(1024);

        blobRef = this.container.getBlobReferenceFromServer(blob.getName());
        BlobTestHelper.assertAreEqual(blob, blobRef);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerReferenceFromServerSnapshot() throws StorageException, URISyntaxException,
            IOException {
        this.container.create();

        CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);
        CloudBlob snapshot = blob.createSnapshot();
        snapshot.getProperties().setContentType("application/octet-stream");
        snapshot.getProperties().setLength(1024);

        CloudBlob blobRef = this.container.getBlobReferenceFromServer(snapshot.getName(), snapshot.getSnapshotID(),
                null, null, null);
        BlobTestHelper.assertAreEqual(snapshot, blobRef);
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerReferenceFromServerSAS() throws StorageException, URISyntaxException,
            IOException, InvalidKeyException {
        this.container.create();
        CloudBlob blob = BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, null, 1024, null);

        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        Calendar now = Calendar.getInstance();
        now.add(Calendar.MINUTE, 10);
        policy.setSharedAccessExpiryTime(now.getTime());
        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ));
        String token = this.container.generateSharedAccessSignature(policy, null);

        CloudBlobContainer containerSAS = new CloudBlobContainer(this.container.getStorageUri(),
                new StorageCredentialsSharedAccessSignature(token));
        CloudBlob blobRef = containerSAS.getBlobReferenceFromServer(blob.getName());
        assertEquals(blob.getClass(), blobRef.getClass());
        assertEquals(blob.getUri(), blobRef.getUri());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerReferenceFromServerMissingBlob() throws StorageException, URISyntaxException,
            IOException {
        this.container.create();

        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("missing");

        try {
            this.container.getBlobReferenceFromServer(blobName);
            fail("Get reference from server should fail.");
        } catch (StorageException ex) {
            assertEquals(404, ex.getHttpStatusCode());
        }
    }

    /**
     * Create a container
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerCreate() throws StorageException {
        this.container.create();
        try {
            this.container.create();
            fail("Should not be able to create twice.");
        }
        catch (StorageException e) {
            assertEquals(e.getErrorCode(), "ContainerAlreadyExists");
            assertEquals(e.getHttpStatusCode(), 409);
            assertEquals(e.getMessage(), "The specified container already exists.");
        }
    }

    /**
     * Try to create a container after it is created
     *
     * @throws StorageException
     * @throws URISyntaxException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerCreateIfNotExists() throws StorageException {
        assertTrue(this.container.createIfNotExists());
        assertTrue(this.container.exists());
        assertFalse(this.container.createIfNotExists());
    }

    /**
     * Try to delete a non-existing container
     *
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerDeleteIfExists() throws  StorageException {
        assertFalse(container.deleteIfExists());
        container.create();
        assertTrue(container.deleteIfExists());
        assertFalse(container.exists());
        assertFalse(container.deleteIfExists());
    }

    @Test
    public void testCloudBlobContainerDeleteIfExistsErrorCode() throws StorageException {
        try {
            container.delete();
            fail("Container should not already exist.");
        }
        catch (StorageException e) {
            assertEquals(StorageErrorCodeStrings.CONTAINER_NOT_FOUND, e.getErrorCode());
        }

        OperationContext ctx = new OperationContext();
        ctx.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {

            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                if (((HttpURLConnection) eventArg.getConnectionObject()).getRequestMethod().equals("DELETE")) {
                    try {
                        container.delete();
                        assertFalse(container.exists());
                    }
                    catch (StorageException e) {
                        fail("Delete should succeed.");
                    }
                }
            }
        });

        container.create();

        // Container deletes succeed before garbage collection occurs.
        assertTrue(container.deleteIfExists(null, null, ctx));
    }

    /**
     * Check a container's existence
     *
     * @throws StorageException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerExists() throws StorageException {
        assertFalse(this.container.exists());

        this.container.create();
        assertTrue(this.container.exists());
        assertNotNull(this.container.getProperties().getEtag());

        this.container.delete();
        assertFalse(this.container.exists());
    }

    /**
     * Set and delete container permissions
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws InterruptedException
     */
    @Test
    @Category({ SlowTests.class, DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerSetPermissions() throws  StorageException,
            InterruptedException, URISyntaxException {
        CloudBlobClient client = BlobTestHelper.createCloudBlobClient();
        this.container.create();

        BlobContainerPermissions permissions = this.container.downloadPermissions();
        assertTrue(BlobContainerPublicAccessType.OFF.equals(permissions.getPublicAccess()));
        assertEquals(0, permissions.getSharedAccessPolicies().size());

        final Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        final Date start = cal.getTime();
        cal.add(Calendar.MINUTE, 30);
        final Date expiry = cal.getTime();

        permissions.setPublicAccess(BlobContainerPublicAccessType.CONTAINER);
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.CREATE));
        policy.setSharedAccessStartTime(start);
        policy.setSharedAccessExpiryTime(expiry);
        permissions.getSharedAccessPolicies().put("key1", policy);

        this.container.uploadPermissions(permissions);
        Thread.sleep(30000);
        // Check if permissions were set
        CloudBlobContainer container2 = client.getContainerReference(this.container.getName());
        assertPermissionsEqual(permissions, container2.downloadPermissions());

        // Clear permissions
        permissions.getSharedAccessPolicies().clear();
        this.container.uploadPermissions(permissions);
        Thread.sleep(30000);

        // Check if permissions were cleared
        // Public access should still be the same
        permissions = container2.downloadPermissions();
        assertPermissionsEqual(permissions, container2.downloadPermissions());
    }

    /**
     * Get permissions from string
     *
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerPermissionsFromString() {
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();

        policy.setPermissionsFromString("racwdl");
        assertEquals(EnumSet.of(
                SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.ADD, SharedAccessBlobPermissions.CREATE,
                SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST),
                policy.getPermissions());

        policy.setPermissionsFromString("rawdl");
        assertEquals(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.ADD,
                SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST),
                policy.getPermissions());

        policy.setPermissionsFromString("rwdl");
        assertEquals(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST), policy.getPermissions());

        policy.setPermissionsFromString("rwl");
        assertEquals(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                SharedAccessBlobPermissions.LIST), policy.getPermissions());

        policy.setPermissionsFromString("wr");
        assertEquals(EnumSet.of(SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.READ),
                policy.getPermissions());

        policy.setPermissionsFromString("d");
        assertEquals(EnumSet.of(SharedAccessBlobPermissions.DELETE), policy.getPermissions());
    }

    /**
     * Write permission to string
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerPermissionsToString() {
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();

        policy.setPermissions(EnumSet.of(
                SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.ADD, SharedAccessBlobPermissions.CREATE,
                SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST));
        assertEquals("racwdl", policy.permissionsToString());

        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.ADD,
                SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST));
        assertEquals("rawdl", policy.permissionsToString());

        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                SharedAccessBlobPermissions.DELETE, SharedAccessBlobPermissions.LIST));
        assertEquals("rwdl", policy.permissionsToString());

        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE,
                SharedAccessBlobPermissions.LIST));
        assertEquals("rwl", policy.permissionsToString());

        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.WRITE, SharedAccessBlobPermissions.READ));
        assertEquals("rw", policy.permissionsToString());

        policy.setPermissions(EnumSet.of(SharedAccessBlobPermissions.DELETE));
        assertEquals("d", policy.permissionsToString());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerUploadMetadata() throws StorageException, URISyntaxException {
        this.container.create();

        CloudBlobContainer container2 = this.container.getServiceClient().getContainerReference(
                this.container.getName());
        container2.downloadAttributes();
        assertEquals(0, container2.getMetadata().size());

        this.container.getMetadata().put("key1", "value1");
        this.container.uploadMetadata();

        container2.downloadAttributes();
        assertEquals(1, container2.getMetadata().size());
        assertEquals("value1", container2.getMetadata().get("key1"));

        Iterable<CloudBlobContainer> containers = this.container.getServiceClient().listContainers(
                this.container.getName(), ContainerListingDetails.METADATA, null, null);

        for (CloudBlobContainer container3 : containers) {
            assertEquals(1, container3.getMetadata().size());
            assertEquals("value1", container3.getMetadata().get("key1"));
        }

        this.container.getMetadata().clear();
        this.container.uploadMetadata();

        container2.downloadAttributes();
        assertEquals(0, container2.getMetadata().size());
    }

    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerInvalidMetadata() throws StorageException{
        // test client-side fails correctly
        testMetadataFailures(this.container, null, "value1", true);
        testMetadataFailures(this.container, "", "value1", true);
        testMetadataFailures(this.container, " ", "value1", true);
        testMetadataFailures(this.container, "\n \t", "value1", true);

        testMetadataFailures(this.container, "key1", null, false);
        testMetadataFailures(this.container, "key1", "", false);
        testMetadataFailures(this.container, "key1", " ", false);
        testMetadataFailures(this.container, "key1", "\n \t", false);

        // test client can get empty metadata
        this.container.create();

        OperationContext opContext = new OperationContext();
        opContext.getSendingRequestEventHandler().addListener(new StorageEvent<SendingRequestEvent>() {
            // insert a metadata element with an empty value
            @Override
            public void eventOccurred(SendingRequestEvent eventArg) {
                HttpURLConnection request = (HttpURLConnection) eventArg.getConnectionObject();
                request.setRequestProperty(Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA + "key1", "");
            }
        });
        this.container.uploadMetadata(null, null, opContext);

        this.container.downloadAttributes();
        assertEquals(1, this.container.getMetadata().size());
        assertEquals("", this.container.getMetadata().get("key1"));
    }

    private static void testMetadataFailures(CloudBlobContainer container, String key, String value, boolean badKey) {
        container.getMetadata().put(key, value);
        try {
            container.uploadMetadata();
            fail(SR.METADATA_KEY_INVALID);
        }
        catch (StorageException e) {
            if (badKey) {
                assertEquals(SR.METADATA_KEY_INVALID, e.getMessage());
            }
            else {
                assertEquals(SR.METADATA_VALUE_INVALID, e.getMessage());
            }

        }
        container.getMetadata().remove(key);
    }

    /**
     * List the blobs in a container
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerListBlobs() throws StorageException, IOException, URISyntaxException {
        this.container.create();
        int numBlobs = 200;
        List<String> blobNames = BlobTestHelper
                .uploadNewBlobs(this.container, BlobType.BLOCK_BLOB, numBlobs, 128, null);

        assertEquals(numBlobs, blobNames.size());

        int count = 0;
        for (ListBlobItem blob : this.container.listBlobs()) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            count++;
        }
        assertEquals(200, count);

        ResultContinuation token = null;

        do {
            ResultSegment<ListBlobItem> result = this.container.listBlobsSegmented("bb", false,
                    EnumSet.noneOf(BlobListingDetails.class), 150, token, null, null);
            for (ListBlobItem blob : result.getResults()) {
                assertEquals(CloudBlockBlob.class, blob.getClass());
                assertTrue(blobNames.remove(((CloudBlockBlob) blob).getName()));
            }
            token = result.getContinuationToken();
        } while (token != null);

        assertTrue(blobNames.size() == 0);
    }

    /**
     * List the blobs in a container with a prefix
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerListBlobsPrefix() throws StorageException, IOException, URISyntaxException {
        this.container.create();
        int numBlobs = 2;
        List<String> blobNames = BlobTestHelper
                .uploadNewBlobs(this.container, BlobType.BLOCK_BLOB, numBlobs, 128, null);

        BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "pref/blob1", 128, null);
        blobNames.add("pref/blob1");

        BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB, "pref/blob2", 128, null);
        blobNames.add("pref/blob2");

        // Flat listing false
        int count = 0;
        for (ListBlobItem blob : this.container.listBlobs("pref")) {
            assertEquals(CloudBlobDirectory.class, blob.getClass());
            assertTrue(((CloudBlobDirectory)blob).getPrefix().startsWith("pref"));
            count++;
        }
        assertEquals(1, count);

        // Flat listing true
        count = 0;
        for (ListBlobItem blob : this.container.listBlobs("pref", true)) {
            assertEquals(CloudBlockBlob.class, blob.getClass());
            assertTrue(((CloudBlockBlob)blob).getName().startsWith("pref/blob"));
            count++;
        }
        assertEquals(2, count);
    }

    /**
     * List the blobs in a container with next(). This tests for the item in the changelog: "Fixed a bug for all
     * listing API's where next() would sometimes throw an exception if hasNext() had not been called even if
     * there were more elements to iterate on."
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerListBlobsNext() throws StorageException, IOException, URISyntaxException {
        this.container.create();

        int numBlobs = 10;
        List<String> blobNames = BlobTestHelper.uploadNewBlobs(this.container, BlobType.PAGE_BLOB, 10, 512, null);
        assertEquals(numBlobs, blobNames.size());

        // hasNext first
        Iterator<ListBlobItem> iter = this.container.listBlobs().iterator();
        iter.hasNext();
        iter.next();
        iter.next();

        // next without hasNext
        iter = this.container.listBlobs().iterator();
        iter.next();
        iter.next();
    }

    /**
     * Try to list the blobs in a container to ensure maxResults validation is working.
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerListBlobsMaxResultsValidation()
            throws StorageException, IOException, URISyntaxException {
        this.container.create();

        // Validation should cause each of these to fail.
        for (int i = 0; i >= -2; i--) {
            try {
                this.container.listBlobsSegmented(
                        "bb", false, EnumSet.noneOf(BlobListingDetails.class), i, null, null, null);
                fail();
            }
            catch (IllegalArgumentException e) {
                assertTrue(String.format(SR.PARAMETER_SHOULD_BE_GREATER_OR_EQUAL, "maxResults", 1)
                        .equals(e.getMessage()));
            }
        }
        assertNotNull(this.container.listBlobsSegmented("thereshouldntbeanyblobswiththisprefix"));
    }

    /**
     * List the blobs in a container
     *
     * @throws URISyntaxException
     * @throws StorageException
     * @throws IOException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerListBlobsOptions() throws StorageException, IOException, InterruptedException,
            URISyntaxException {
        this.container.create();
        final int length = 128;

        // regular blob
        CloudBlockBlob originalBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container,
                BlobType.BLOCK_BLOB, "originalBlob", length, null);

        // leased blob
        CloudBlockBlob leasedBlob = (CloudBlockBlob) BlobTestHelper.uploadNewBlob(this.container, BlobType.BLOCK_BLOB,
                "originalBlobLeased", length, null);
        leasedBlob.acquireLease();

        // copy of regular blob
        CloudBlockBlob copyBlob = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("originalBlobCopy"));
        copyBlob.startCopy(originalBlob);
        BlobTestHelper.waitForCopy(copyBlob);

        // snapshot of regular blob
        CloudBlockBlob blobSnapshot = (CloudBlockBlob) originalBlob.createSnapshot();

        // snapshot of the copy of the regular blob
        CloudBlockBlob copySnapshot = this.container.getBlockBlobReference(BlobTestHelper
                .generateRandomBlobNameWithPrefix("originalBlobSnapshotCopy"));
        copySnapshot.startCopy(copyBlob);
        BlobTestHelper.waitForCopy(copySnapshot);

        int count = 0;
        for (ListBlobItem item : this.container.listBlobs("originalBlob", true,
                EnumSet.allOf(BlobListingDetails.class), null, null)) {
            CloudBlockBlob blob = (CloudBlockBlob) item;
            if (blob.getName().equals(originalBlob.getName()) && !blob.isSnapshot()) {
                assertCreatedAndListedBlobsEquivalent(originalBlob, blob, length);
            }
            else if (blob.getName().equals(leasedBlob.getName())) {
                assertCreatedAndListedBlobsEquivalent(leasedBlob, blob, length);
            }
            else if (blob.getName().equals(copyBlob.getName())) {
                assertCreatedAndListedBlobsEquivalent(copyBlob, blob, length);
            }
            else if (blob.getName().equals(blobSnapshot.getName()) && blob.isSnapshot()) {
                assertCreatedAndListedBlobsEquivalent(blobSnapshot, blob, length);
            }
            else if (blob.getName().equals(copySnapshot.getName())) {
                assertCreatedAndListedBlobsEquivalent(copySnapshot, blob, length);
            }
            else {
                fail("An unexpected blob " + blob.getName() + " was listed.");
            }
            count++;
        }
        assertEquals(5, count);
    }

    /**
     * @throws StorageException
     * @throws URISyntaxException
     * @throws InterruptedException
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testCloudBlobContainerSharedKey() throws StorageException, InterruptedException {
        BlobContainerPermissions expectedPermissions;
        BlobContainerPermissions testPermissions;

        this.container.create();

        // Test new permissions.
        expectedPermissions = new BlobContainerPermissions();
        testPermissions = this.container.downloadPermissions();
        assertPermissionsEqual(expectedPermissions, testPermissions);

        // Test setting empty permissions.
        this.container.uploadPermissions(expectedPermissions);
        testPermissions = this.container.downloadPermissions();
        assertPermissionsEqual(expectedPermissions, testPermissions);

        // Add a policy, check setting and getting.
        SharedAccessBlobPolicy policy1 = new SharedAccessBlobPolicy();
        Calendar now = Calendar.getInstance();
        policy1.setSharedAccessStartTime(now.getTime());
        now.add(Calendar.MINUTE, 10);
        policy1.setSharedAccessExpiryTime(now.getTime());

        policy1.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.CREATE,
                SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE));
        expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1);

        this.container.uploadPermissions(expectedPermissions);
        Thread.sleep(30000);

        testPermissions = this.container.downloadPermissions();
        assertPermissionsEqual(expectedPermissions, testPermissions);
    }
    
    /**
     * @throws StorageException
     * @throws InterruptedException
     * @throws URISyntaxException 
     * @throws IOException 
     * @throws InvalidKeyException 
     */
    @Test
    @Category({ DevFabricTests.class, DevStoreTests.class })
    public void testListBlobsWithIncrementalCopiedBlob() throws StorageException, InterruptedException, URISyntaxException, IOException, InvalidKeyException {
        this.container.create();
        
        String blobName = BlobTestHelper.generateRandomBlobNameWithPrefix("testblob");
        CloudPageBlob source = this.container.getPageBlobReference(blobName);
        source.create(1024);
        
        final Random randGenerator = new Random();
        final byte[] buffer = new byte[1024];
        randGenerator.nextBytes(buffer);

        source.upload(new ByteArrayInputStream(buffer), buffer.length);
        CloudPageBlob snapshot = (CloudPageBlob) source.createSnapshot();
        
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy();
        policy.setPermissions( EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE));

        Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
        cal.setTime(new Date());
        cal.add(Calendar.SECOND, 5000);
        policy.setSharedAccessExpiryTime(cal.getTime());

        SharedAccessAccountPolicy accountPolicy = new SharedAccessAccountPolicy();
        accountPolicy.setPermissions(EnumSet.of(SharedAccessAccountPermissions.READ, SharedAccessAccountPermissions.WRITE));
        accountPolicy.setServices(EnumSet.of(SharedAccessAccountService.BLOB));
        accountPolicy.setResourceTypes(EnumSet.of(SharedAccessAccountResourceType.OBJECT, SharedAccessAccountResourceType.CONTAINER));
        accountPolicy.setSharedAccessExpiryTime(cal.getTime());
        final CloudBlobClient sasClient = TestHelper.createCloudBlobClient(accountPolicy, false);

        CloudPageBlob sasSnapshotBlob = (CloudPageBlob) sasClient.getContainerReference(container.getName())
                .getBlobReferenceFromServer(snapshot.getName(), snapshot.snapshotID, null, null, null);
        sasSnapshotBlob.exists();
        CloudPageBlob copy = this.container.getPageBlobReference("copy");

        final UriQueryBuilder builder = new UriQueryBuilder();
        builder.add(Constants.QueryConstants.SNAPSHOT, sasSnapshotBlob.snapshotID);

        copy.startIncrementalCopy(BlobTestHelper.defiddler(sasSnapshotBlob));
        
        BlobTestHelper.waitForCopy(copy);
        
        boolean incrementalCopyFound = false;
        for (ListBlobItem blobItem : this.container.listBlobs(null, true, EnumSet.allOf(BlobListingDetails.class), null, null)) {
            CloudPageBlob blob = (CloudPageBlob) blobItem;
            
            if (blob.getName().equals("copy") && blob.isSnapshot()) {
                // Check that the incremental copied blob is found exactly once
                assertFalse(incrementalCopyFound);
                assertTrue(blob.properties.isIncrementalCopy());
                incrementalCopyFound = true;
            }
            else if (blob.getName().equals("copy")) {
                assertNotNull(blob.getCopyState().getCopyDestinationSnapshotID());
            }
        }

        assertTrue(incrementalCopyFound);
    }

    // Helper Method
    private static void assertPermissionsEqual(BlobContainerPermissions expected, BlobContainerPermissions actual) {
        assertEquals(expected.getPublicAccess(), actual.getPublicAccess());
        HashMap<String, SharedAccessBlobPolicy> expectedPolicies = expected.getSharedAccessPolicies();
        HashMap<String, SharedAccessBlobPolicy> actualPolicies = actual.getSharedAccessPolicies();
        assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size());
        for (String name : expectedPolicies.keySet()) {
            assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name));
            SharedAccessBlobPolicy expectedPolicy = expectedPolicies.get(name);
            SharedAccessBlobPolicy actualPolicy = actualPolicies.get(name);
            assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(),
                    actualPolicy.getPermissions().toString());
            assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime().toString(),
                    actualPolicy.getSharedAccessStartTime().toString());
            assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime().toString(),
                    actualPolicy.getSharedAccessExpiryTime().toString());
        }
    }

    /**
     * Checks that a given created blob is listed correctly
     *
     * @param createdBlob
     * @param listedBlob
     * @param length
     * @throws StorageException
     * @throws URISyntaxException
     */
    @SuppressLint("UseValueOf")
	private static void assertCreatedAndListedBlobsEquivalent(CloudBlockBlob createdBlob, CloudBlockBlob listedBlob,
            int length) throws StorageException, URISyntaxException{
        assertEquals(createdBlob.getContainer().getName(), listedBlob.getContainer().getName());
        assertEquals(createdBlob.getMetadata(), listedBlob.getMetadata());
        assertEquals(createdBlob.getName(), listedBlob.getName());
        assertEquals(createdBlob.getSnapshotQualifiedUri(), listedBlob.getSnapshotQualifiedUri());
        assertEquals(createdBlob.getSnapshotID(), listedBlob.getSnapshotID());
        assertEquals(createdBlob.getUri(), listedBlob.getUri());

        // Compare Properties
        BlobProperties props1 = createdBlob.getProperties();
        BlobProperties props2 = listedBlob.getProperties();
        assertEquals(props1.getBlobType(), props2.getBlobType());
        assertEquals(props1.getContentDisposition(), props2.getContentDisposition());
        assertEquals(props1.getContentEncoding(), props2.getContentEncoding());
        assertEquals(props1.getContentLanguage(), props2.getContentLanguage());

        if (props1.getContentType() == null) {
            assertEquals("application/octet-stream", props2.getContentType());
        }
        else {
            assertEquals(props1.getContentType(), props2.getContentType());
        }

        if (props1.getContentMD5() != null) {
            assertEquals(props1.getContentMD5(), props2.getContentMD5());
        }
        assertEquals(props1.getEtag(), props2.getEtag());

        assertEquals(props1.getLeaseStatus(), props2.getLeaseStatus());

        if (props1.getLeaseState() != null) {
            assertEquals(props1.getLeaseState(), props2.getLeaseState());
        }

        assertEquals(length, props2.getLength());
        assertEquals(props1.getLastModified(), props2.getLastModified());
        assertEquals(props1.getCacheControl(), props2.getCacheControl());

        // Compare CopyState
        CopyState state1 = props1.getCopyState();
        CopyState state2 = props2.getCopyState();
        if (state1 == null && state2 == null) {
            return;
        }
        else {
            assertEquals(new Long(length), state2.getBytesCopied());
            assertNotNull(state2.getCompletionTime());
            assertEquals(state1.getCopyId(), state2.getCopyId());
            assertNotNull(state2.getSource());
            assertEquals(state1.getStatus(), state2.getStatus());
            assertEquals(new Long(length), state2.getTotalBytes());
        }
    }
}